// Copyright (c) Meta Platforms, Inc. and affiliates. 'use client'; /** * @file ChatComposerDrawer.tsx * @input Uses React, StyleX, theme tokens * @output Exports ChatComposerDrawer component * @position Collapsible drawer panel for ChatComposer. * Supports expanded (full content) and collapsed (count + label) states * with fade animation and grid-template-rows height transition. * The collapse toggle exposes aria-expanded + aria-controls linking it to * the content region (disclosure pattern). * * SYNC: When modified, update: * - /packages/core/src/Chat/ChatComposerDrawer.test.tsx * - /packages/core/src/Chat/ChatComposerDrawer.doc.mjs * - /packages/core/src/Chat/ChatComposerDrawer.spec.md * - /apps/storybook/stories/ChatComposerDrawer.stories.tsx * - /packages/core/src/Chat/index.ts (exports) * - /packages/cli/assets/templates/blocks/components/ChatComposerDrawer/ (block examples) */ import { useId, useState, type ReactNode } from 'react'; import type { StyleXStyles } from "../style-runtime.js"; import * as stylex from "../style-runtime.js"; import "../theme/tokens.stylex.js"; import { colorVars, spacingVars, radiusVars, durationVars, easeVars, typeScaleVars } from "../theme/tokens.stylex.js"; import { Badge } from "../Badge/index.js"; import { mergeProps } from "../utils/index.js"; import type { BaseProps } from "../BaseProps.js"; import { themeProps } from "../utils/themeProps.js"; import { focusOutlineProps } from "../utils/focusOutline.stylex.js"; import { useTranslator } from "../i18n/index.js"; export interface ChatComposerDrawerProps extends BaseProps { ref?: React.Ref; /** * Content to render inside the drawer — attachments, context chips, previews, etc. */ children: ReactNode; /** * Total item count — shown in the collapsed badge. * When omitted, the component doesn't support collapse. */ count?: number; /** * Label shown next to the count in collapsed state. * @default 'Items' */ label?: string; /** * Whether the drawer is collapsed. * Uncontrolled by default (internal toggle). */ isCollapsed?: boolean; /** * Default collapsed state for uncontrolled usage. * @default false */ defaultIsCollapsed?: boolean; /** * Callback when collapsed state changes. */ onCollapsedChange?: (isCollapsed: boolean) => void; /** * StyleX styles for layout customization (margins, positioning, sizing). * Must be a `stylex.create()` value — not an inline style object. * * @example * ``` * const styles = stylex.create({ wrapper: { marginTop: 8 } }); * * * * ``` */ xstyle?: StyleXStyles; className?: string; style?: React.CSSProperties; 'data-testid'?: string; } const styles = { root: { kVAEAm: "x1n2onr6", kY2c9j: "x1vjfegm", k1xSpc: "x78zum5", kXwgrk: "xdt5ytf", kVQacm: "xb3r6kr", kg3NbH: "x1pzlopt", kLKAdn: "x1vlblms", kGO01o: "xxbfulo", k1K539: "x1cdrix3", kWkggS: "x10xzikg", kKwaWg: "x1vrk509", krdFHd: "x1n70msy", kfmiAY: "xg0tfu", $$css: true as const }, toggleRow: { k1xSpc: "xrvj5dj", kumcoG: "x1y6fwsi", kGNEyG: "x6s0dn4", kZKoxP: "x1grt7ep", kg3NbH: "x1pzlopt", kUOVxO: "x1ng9b9t", kkrTdU: "x1ypdohk x16khyan", kfSwDN: "x87ps6o", $$css: true as const }, toggleCollapsed: { $$css: true as const } }; /** * Collapsible drawer panel for a chat composer. * Use for attachments, context chips, or any supplementary content above the input. * * @example * ``` * * * * * ``` */ export function ChatComposerDrawer({ ref, children, count, label: labelFromProps, isCollapsed: controlledCollapsed, defaultIsCollapsed = false, onCollapsedChange, xstyle, className, style, 'data-testid': testId, ...htmlProps }: ChatComposerDrawerProps): React.ReactElement { const t = useTranslator(); const label = labelFromProps ?? t('@astryx.chat.composerDrawer.label'); const [internalCollapsed, setInternalCollapsed] = useState(defaultIsCollapsed); const isControlled = controlledCollapsed !== undefined; const isCollapsed = isControlled ? controlledCollapsed : internalCollapsed; const canCollapse = count != null; // Links the toggle to the region it shows/hides so assistive tech can move // from the button to its controlled content (disclosure pattern). The // content stays mounted while collapsed (hidden via the grid-row collapse), // so the reference always resolves. const contentId = useId(); const toggle = () => { const next = !isCollapsed; if (!isControlled) { setInternalCollapsed(next); } onCollapsedChange?.(next); }; return
{canCollapse &&
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggle(); } }}>
{label}
}
; } ChatComposerDrawer.displayName = 'ChatComposerDrawer';