// Copyright (c) Meta Platforms, Inc. and affiliates. 'use client'; /** * @file Toolbar.tsx * @input Uses Section, SizeContext, useListFocus, useKeyboardHint, StyleX, spacingVars, sizeVars * @output Exports Toolbar component and ToolbarProps * @position Core implementation; consumed by index.ts * * SYNC: When modified, update these files to stay in sync: * - /packages/core/src/Toolbar/Toolbar.doc.mjs * - /packages/core/src/Toolbar/Toolbar.test.tsx * - /packages/core/src/Toolbar/index.ts * - /apps/storybook/stories/Toolbar.stories.tsx * - /packages/cli/assets/templates/blocks/components/Toolbar/ (showcase blocks) */ import { useCallback, type ReactNode } from 'react'; import type { BaseProps } from "../BaseProps.js"; import type { SectionVariant } from "../Section/Section.js"; import type { SpacingStep } from "../utils/types.js"; import type { ElementSize } from "../SizeContext/SizeContext.js"; import * as stylex from "../style-runtime.js"; import "../theme/tokens.stylex.js"; import { spacingVars, sizeVars } from "../theme/tokens.stylex.js"; import { mergeProps } from "../utils/index.js"; import { Section } from "../Section/Section.js"; import { useListFocus } from "../hooks/useListFocus.js"; import { useKeyboardHint } from "../hooks/useKeyboardHint.js"; import { SizeProvider } from "../SizeContext/SizeContext.js"; import { edgeCompSlot } from "../Layout/edgeCompensation.stylex.js"; import { themeProps } from "../utils/themeProps.js"; /** * Map SpacingStep values to spacingVars keys. */ const spacingStepToVar: Record = { 0: '--spacing-0', 0.5: '--spacing-0-5', 1: '--spacing-1', 1.5: '--spacing-1-5', 2: '--spacing-2', 3: '--spacing-3', 4: '--spacing-4', 5: '--spacing-5', 6: '--spacing-6', 8: '--spacing-8', 10: '--spacing-10' }; const styles = { baseFlex: { k1xSpc: "x78zum5", kGNEyG: "x6s0dn4", kjj79g: "x1qughib", $$css: true as const }, baseGrid: { k1xSpc: "xrvj5dj", kumcoG: "x134kloy", kGNEyG: "x6s0dn4", $$css: true as const }, vertical: { kXwgrk: "xdt5ytf", kGNEyG: "x1qjc9v5", $$css: true as const }, startSlot: { k1xSpc: "x78zum5", kGNEyG: "x6s0dn4", $$css: true as const }, centerSlot: { k1xSpc: "x78zum5", kGNEyG: "x6s0dn4", kjj79g: "xl56j7k", k7Eaqz: "xeuugli", kVQacm: "xb3r6kr", $$css: true as const }, endSlot: { k1xSpc: "x78zum5", kGNEyG: "x6s0dn4", kjj79g: "x13a6bvl", $$css: true as const }, startOnly: { kUk6DE: "x845mor", $$css: true as const }, endOnly: { keTefX: "xvc5jky", $$css: true as const } }; const sizeStyles = { base: { kAzted: "x1gdqauq", $$css: true as const } }; // Dynamic styles for configurable gap and tab indicator offset const dynamicStyles = { gap: (gapValue: string) => [{ kOIVth: gapValue != null ? "x1pidvrl" : gapValue, $$css: true as const }, { "--x-gap": (val => typeof val === "number" ? val + "px" : val != null ? val : undefined)(gapValue) }], tabIndicatorBottom: (offset: string) => [{ "--_tab-indicator-bottom": offset != null ? "xbrl7fg" : offset, $$css: true as const }, { "--x---_tab-indicator-bottom": offset != null ? offset : undefined }] }; /** * Default block padding per toolbar size. Inline padding comes from the * parent container (Card, Section, LayoutHeader) via the Section's * theme default — the toolbar only controls its vertical tightness. */ const defaultBlockPaddingForSize: Record = { sm: 2, md: 2, lg: 2 }; const blockPaddingVarForSize: Record = { sm: spacingVars['--spacing-2'], md: spacingVars['--spacing-2'], lg: spacingVars['--spacing-2'] }; /** * Edge compensation inset amount per toolbar size. * Equals container-inline-padding minus the toolbar's block padding, * creating even spacing around edge-compensated items (ghost buttons, tabs). */ const edgeCompInsetForSize: Record = { sm: `calc(var(--container-padding-inline-start, ${spacingVars['--spacing-4']}) - ${spacingVars['--spacing-2']})`, md: `calc(var(--container-padding-inline-start, ${spacingVars['--spacing-4']}) - ${spacingVars['--spacing-2']})`, lg: `calc(var(--container-padding-inline-start, ${spacingVars['--spacing-4']}) - ${spacingVars['--spacing-2']})` }; export type ToolbarSize = ElementSize; export interface ToolbarProps extends BaseProps { /** Ref forwarded to the root Section element */ ref?: React.Ref; /** * Content aligned to the start (left in LTR). */ startContent?: ReactNode; /** * Content centered between start and end. * When provided, switches layout to CSS grid (1fr auto 1fr). */ centerContent?: ReactNode; /** * Content aligned to the end (right in LTR). */ endContent?: ReactNode; /** * Accessible label for the toolbar. * Applied as aria-label on the inner toolbar element. */ label: string; /** * Size of the toolbar. Coordinates with Button, TextInput, TabList, and Selector — * children inherit this size as their default via SizeContext. * * - `'sm'`: Compact — fits sm buttons/inputs (28px elements) * - `'md'`: Standard — fits md buttons/inputs (32px elements) * - `'lg'`: Spacious — fits lg buttons/inputs (36px elements) * @default 'md' */ size?: ToolbarSize; /** * Gap between items within each slot, using the spacing scale. * @default 1 */ gap?: SpacingStep; /** * Orientation of the toolbar for keyboard navigation. * Controls which arrow keys navigate between items. * @default 'horizontal' */ orientation?: 'horizontal' | 'vertical'; /** * Visual variant passed through to Section. * @default 'transparent' */ variant?: SectionVariant; /** * Which sides should have divider borders. * Passed through to Section. * @example * ``` * dividers={['bottom']} * ``` */ dividers?: ('top' | 'bottom' | 'start' | 'end')[]; } /** * General-purpose toolbar with start, center, and end content slots. * * Built on Section, provides flex/grid layout with roving tabindex * keyboard navigation via useListFocus. Cascades `size` to child components * (Button, TextInput, TabList, Selector) via SizeContext, and applies * edge compensation so ghost buttons align flush at container edges. * * @example * ``` * } * endContent={