// Copyright (c) Meta Platforms, Inc. and affiliates. 'use client'; /** * @file DialogHeader.tsx * @input Uses React, StyleX, LayoutHeader, Button, Icon, Heading, Text, DialogContext, mergeProps, themeProps * @output Exports DialogHeader component and DialogHeaderProps * @position Dialog header component; used with Dialog and Layout * * SYNC: When modified, update these files to stay in sync: * - /packages/core/src/Dialog/Dialog.doc.mjs * - /packages/core/src/Dialog/DialogHeader.test.tsx * - /packages/core/src/Dialog/index.ts * - /apps/storybook/stories/Dialog.stories.tsx * - /packages/cli/assets/templates/blocks/components/Dialog/ (showcase blocks) */ import { useEffect, useRef, type ReactNode } from 'react'; import * as stylex from "../style-runtime.js"; import "../theme/tokens.stylex.js"; import { spacingVars, sizeVars, typeScaleVars } from "../theme/tokens.stylex.js"; import { LayoutHeader } from "../Layout/LayoutHeader.js"; import { Button } from "../Button/index.js"; import { Icon } from "../Icon/index.js"; import { Heading } from "../Heading/Heading.js"; import { Text } from "../Text/Text.js"; import type { BaseProps } from "../BaseProps.js"; import { mergeProps } from "../utils/index.js"; import { themeProps } from "../utils/themeProps.js"; import { useDialogContext } from "./DialogContext.js"; import { useTranslator } from "../i18n/index.js"; const styles = { titleFocusable: { kI3sdo: "x1a2a7pz", $$css: true as const } }; export interface DialogHeaderProps extends BaseProps { /** Ref forwarded to the root element */ ref?: React.Ref; /** * The title of the dialog. * This title receives focus when the dialog opens for screen reader * accessibility, and names the parent Dialog via aria-labelledby unless the * consumer passes an explicit aria-label/aria-labelledby to the Dialog. */ title: string; /** * Optional subtitle displayed below the title in smaller, secondary text. */ subtitle?: string; /** * Callback fired when the dialog visibility changes. * Called with `false` when the close button is clicked. * If not provided, no close button will be rendered. */ onOpenChange?: (isOpen: boolean) => unknown; /** * Content to render before the title (e.g., a back button). */ startContent?: ReactNode; /** * Content to render after the title, before the close button (e.g., action buttons). */ endContent?: ReactNode; /** * Adds a themed border at the bottom edge. * When false, spacing collapse is applied automatically for seamless visual flow. * Defaults to the parent Layout's `defaultHasDividers` context value. */ hasDivider?: boolean; } /** * Header component designed specifically for Dialog. * * Renders a title that receives focus when a modal dialog opens (for screen reader accessibility) * and an optional close button. Inline documentation previews suppress this autofocus. * The title is an h2 element with tabIndex={-1} so it can be programmatically focused but * doesn't appear in the tab order. The title also names the parent Dialog via * aria-labelledby (unless the Dialog receives an explicit aria-label/aria-labelledby). * * Uses LayoutHeader internally for consistent styling with other layout headers. * * @example * ``` * setIsOpen(open)}> * setIsOpen(open)} />} * content={Content} * footer={Actions} * /> * * ``` */ export function DialogHeader({ title, subtitle, onOpenChange, startContent, endContent, hasDivider, xstyle, className, style, ref, ...rest }: DialogHeaderProps) { const t = useTranslator(); const titleRef = useRef(null); const dialogContext = useDialogContext(); const shouldAutoFocus = dialogContext?.isInline !== true; const titleId = dialogContext?.titleId; // Auto-focus the title when mounted for screen reader accessibility. // Inline dialogs are documentation/showcase previews, so suppress focus to // avoid stealing scroll position from the surrounding page. // The parent Dialog detects this title (by `titleId`) via a callback ref to // set its default aria-labelledby — no registration handshake needed here. useEffect(() => { if (shouldAutoFocus && titleRef.current) { titleRef.current.focus(); } }, [shouldAutoFocus]); return
{startContent &&
{startContent}
}
{title} {subtitle && {subtitle} }
{(endContent || onOpenChange) &&
{endContent} {onOpenChange &&
}
; } DialogHeader.displayName = 'DialogHeader';