// Copyright (c) Meta Platforms, Inc. and affiliates. 'use client'; /** * @file ButtonGroup.tsx * @input Uses React, StyleX, Button/IconButton children * @output Exports ButtonGroup component, context, and types * @position Groups buttons with connected styling; consumed by index.ts * * Children (Button, IconButton) consume the ButtonGroup context to apply * position-aware styles in pure CSS — no cloneElement or wrapper divs needed. * The end-cap radius rules live in Button.tsx; see IS_LAST_ITEM there for why * the trailing edge cannot use :last-child (#2508). * * SYNC: When modified, update these files to stay in sync: * - /packages/core/src/ButtonGroup/ButtonGroup.doc.mjs (props table, features) * - /packages/core/src/ButtonGroup/ButtonGroup.test.tsx (tests) * - /packages/core/src/ButtonGroup/index.ts (exports if types change) * - /apps/storybook/stories/ButtonGroup.stories.tsx (storybook stories) * - /packages/cli/assets/templates/blocks/components/ButtonGroup/ (showcase blocks) */ import { useMemo, type ReactNode } from 'react'; import * as stylex from "../style-runtime.js"; import type { ButtonSize } from "../Button/index.js"; import "../theme/tokens.stylex.js"; import { radiusVars, shadowVars } from "../theme/tokens.stylex.js"; import type { Elevation } from "../utils/types.js"; import { SizeProvider, useSize } from "../SizeContext/SizeContext.js"; import { useListFocus } from "../hooks/useListFocus.js"; import { mergeProps, composeEventHandlers } from "../utils/index.js"; import type { BaseProps } from "../BaseProps.js"; import { ButtonGroupContext } from "./ButtonGroupContext.js"; import type { ButtonGroupOrientation } from "./ButtonGroupContext.js"; import { themeProps } from "../utils/themeProps.js"; import { useMergedRefs } from "../hooks/useMergedRefs.js"; // ============================================================================= // Props // ============================================================================= export interface ButtonGroupProps extends BaseProps { /** Ref forwarded to the root element. */ ref?: React.Ref; /** * Button or IconButton children. */ children: ReactNode; /** * Accessible label for the group (used as aria-label). */ label: string; /** * Orientation of the button group. * @default 'horizontal' */ orientation?: ButtonGroupOrientation; /** * Default size for buttons in the group. * Individual buttons can override this with their own `size` prop. * @default 'md' */ size?: ButtonSize; /** * Resting elevation for the group. The connected buttons share one surface, * so the shadow sits on the group and lifts them together. Use for a floating * group of actions above content. * @default 'none' */ elevation?: Elevation; /** * Whether all buttons in the group are disabled. * @default false */ isDisabled?: boolean; /** * Test ID for testing frameworks. */ 'data-testid'?: string; } // ============================================================================= // Styles // ============================================================================= const styles = { group: { k1xSpc: "x3nfvp2", kGNEyG: "x1qjc9v5", $$css: true as const }, vertical: { kXwgrk: "xdt5ytf", $$css: true as const } }; // Resting elevation for the whole group — the connected buttons share one // surface, so the shadow sits on the group wrapper and lifts them as a unit. // The wrapper takes the element radius so the shadow follows the group's // rounded outer corners rather than a bare rectangle. 'none' is the default. const elevationStyles = { none: { kGVxlE: "x1gnnqk1", $$css: true as const }, low: { kGVxlE: "x1i5ehqx", kaIpWk: "xh6dtrn", $$css: true as const }, med: { kGVxlE: "x14hfi27", kaIpWk: "xh6dtrn", $$css: true as const }, high: { kGVxlE: "x1kcpxr7", kaIpWk: "xh6dtrn", $$css: true as const } }; // ============================================================================= // Component // ============================================================================= /** * Groups buttons with connected styling — shared borders, proper border-radius * handling (only on outer edges), and horizontal or vertical orientation. * * Children automatically detect the group via context and apply position-aware * styles in pure CSS. * * The group is a single tab stop: arrow keys move between members along the * orientation, Home/End jump to the ends, focus wraps, and disabled members are * skipped (the APG roving tabindex technique, via `useListFocus`). * * Members that render their own layer — a Button with a `tooltip`, or a * DropdownMenu — compose correctly, including as the trailing member. * * @example * ``` * *