// Copyright (c) Meta Platforms, Inc. and affiliates. 'use client'; /** * @file LayoutPanel.tsx * @input Uses React StyleX, LayoutAreaContext, LayoutSlotsContext * @output Exports LayoutPanel component and LayoutPanelProps * @position Sidebar panel for Layout start/end slots. Use for navigation panels, * settings sidebars, detail panels, or any fixed-width side content. * * SYNC: When modified, update these files to stay in sync: * - /packages/core/src/Layout/Layout.doc.mjs * - /apps/storybook/stories/Layout.stories.tsx * - /packages/cli/assets/templates/blocks/components/Layout/ (showcase blocks) */ import type { AriaRole, ReactNode } from 'react'; import type { BaseProps } from "../BaseProps.js"; import { use } from 'react'; import * as stylex from "../style-runtime.js"; import "../theme/tokens.stylex.js"; import { colorVars, spacingVars } from "../theme/tokens.stylex.js"; import { LayoutAreaContext } from "./LayoutAreaContext.js"; import { LayoutSlotsContext } from "./LayoutSlotsContext.js"; import { mergeProps } from "../utils/index.js"; import type { SizeValue, SpacingStep } from "../utils/types.js"; import type { ResizableProps } from "../Resizable/useResizable.js"; import { themeProps } from "../utils/themeProps.js"; import { paddingStyles, containerPaddingInlineVarStyles, containerPaddingBlockStartVarStyles, containerPaddingBlockEndVarStyles } from "./padding.stylex.js"; const styles = { panel: { kB7OPa: "x9f619", kmuXW: "x2lah0s", kVQacm: "x7giv3", kZCmMZ: "xwjyata", kwRFfy: "x1peupej", kLKAdn: "xqty4a", kGO01o: "xg476vw", "--container-padding-inline-start": "x408pgh", "--container-padding-inline-end": "xikqloz", "--container-padding-block-start": "xjmgx01", "--container-padding-block-end": "xi9ns85", $$css: true as const }, startPanel: { kZCmMZ: "x139j0dd", $$css: true as const }, endPanel: { kwRFfy: "xpc6k2p", $$css: true as const }, noHeader: { kLKAdn: "x81pis9", $$css: true as const }, noFooter: { kGO01o: "xon7vh3", $$css: true as const }, fullBleed: { kZCmMZ: "x1c1uobl", kwRFfy: "xyri2b", kLKAdn: "xexx8yu", kGO01o: "x18d9i69", "--container-padding-inline-start": "xrhngw9", "--container-padding-inline-end": "xjsfl84", "--container-padding-block-start": "x1047aw6", "--container-padding-block-end": "xax9j7h", $$css: true as const }, scrollable: { kVQacm: "xysyzu8", $$css: true as const }, dividerEnd: { ke9TFa: "x1lun4ml", k8ry5P: "x18b5jzi", kBCPoo: "x1gejf6u", $$css: true as const }, dividerStart: { k2ei4v: "xpilrb4", kVhnKS: "x1t7ytsu", kGJrpR: "x1j92z86", $$css: true as const }, collapseStart: { keTefX: "x1wim8z0", $$css: true as const }, collapseEnd: { k71WvV: "x1kpg4um", $$css: true as const } }; // Dynamic styles for sizing props const dynamicStyles = { sizing: (width: SizeValue | null) => [{ kzqmXN: width != null ? "x5lhr3w" : width, $$css: true as const }, { "--x-width": (val => typeof val === "number" ? val + "px" : val != null ? val : undefined)(width) }] }; export interface LayoutPanelProps extends BaseProps { ref?: React.Ref; /** * Content to render inside the panel. */ children?: ReactNode; /** * Adds a themed border on the appropriate edge. * - Start panel: border on end edge (right in LTR) * - End panel: border on start edge (left in LTR) * When false, spacing collapse is applied automatically for seamless visual flow. * * Note: When using `resizable` with an adjacent `ResizeHandle hasDivider`, * set this to `false` to avoid a double-line artifact. * @default false */ hasDivider?: boolean; /** * Internal padding of the panel using the spacing scale. * Accepts numeric spacing steps: 0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8, 10. * Overrides the default padding from the layout container. */ padding?: SpacingStep; /** * Enables scrollable overflow for the panel. * Set to false for auto-height layouts where sticky positioning * needs to work with parent containers. * @default true */ isScrollable?: boolean; /** * Accessible label for the landmark. * Required when role is set and multiple landmarks of the same type exist. */ label?: string; /** * ARIA landmark role for accessibility. * Use 'navigation' or 'complementary' only for top-level layouts (not nested). */ role?: AriaRole; /** * Width of the panel. * Numbers are treated as pixels, strings are used as-is. * When `resizable` is provided, this is ignored — the hook controls width. */ width?: SizeValue; /** * Resize props from `useResizable()`. When provided, the panel width * is driven by the hook and a resize handle should be placed adjacent * to this panel. * * @example * ``` * const sidebar = useResizable({ defaultSize: 250, minSize: 200 }); * * * * * ``` */ resizable?: ResizableProps; } /** * Sidebar or side panel for Layout. Use in the `start` slot for left navigation * or in the `end` slot for detail/inspector panels. * Renders with optional divider and context-aware padding. * Divider position is auto-detected based on which slot the panel is in. * * Already provides its own padding and scroll — don't add padding or * overflow to children. Use `padding={0}` if you need edge-to-edge content. * * @example * ``` * * * * * } * content={Main content} * end={ * * * * } * /> * * ``` */ export function LayoutPanel({ children, hasDivider = false, isScrollable = true, label, padding, role, width, resizable, xstyle, className, style, ref, ...props }: LayoutPanelProps) { const area = use(LayoutAreaContext); const { hasHeader, hasFooter } = use(LayoutSlotsContext); // When resizable props are provided, use the hook-driven size const effectiveWidth = resizable ? resizable._size : width; // Determine panel position const isStartPanel = area === 'start'; const isEndPanel = area === 'end'; const isZeroPadding = padding === 0; // When no divider, collapse spacing for seamless visual flow const shouldCollapseSpacing = !hasDivider && !isZeroPadding && padding == null; // Select divider style based on position const dividerStyle = isStartPanel ? styles.dividerEnd : isEndPanel ? styles.dividerStart : null; // Select collapse style based on position (collapse the side where divider would be) const collapseStyle = isStartPanel ? styles.collapseEnd : isEndPanel ? styles.collapseStart : null; return
{children}
; } LayoutPanel.displayName = 'LayoutPanel';