@acronis-platform/ui-react 5.1.1 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ui/checkbox/checkbox.js +1 -1
- package/dist/components/ui/checkbox/checkbox.js.map +1 -1
- package/dist/components/ui/combobox/combobox.js +1 -1
- package/dist/components/ui/combobox/combobox.js.map +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.js +2 -2
- package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
- package/dist/components/ui/input-num-picker/input-num-picker.js.map +1 -1
- package/dist/components/ui/input-select/input-select.js +1 -1
- package/dist/components/ui/input-select/input-select.js.map +1 -1
- package/dist/components/ui/number-field/number-field.js.map +1 -1
- package/dist/components/ui/sidebar-primary/sidebar-primary.js +10 -5
- package/dist/components/ui/sidebar-primary/sidebar-primary.js.map +1 -1
- package/dist/llms/checkbox.md +63 -12
- package/dist/llms/sidebar-primary.md +18 -1
- package/dist/llms.txt +1 -1
- package/dist/src/components/ui/sidebar-primary/sidebar-primary.d.ts +23 -0
- package/dist/ui-react.css +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidebar-primary.js","names":[],"sources":["../../../../src/components/ui/sidebar-primary/sidebar-primary.tsx"],"sourcesContent":["import * as React from 'react';\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { SquareArrowUpRightIcon } from '@acronis-platform/icons-react/stroke-mono';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/lib/utils';\nimport { useDocDir } from '@/lib/use-doc-dir';\n\nimport { ScrollArea } from '../scroll-area';\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip';\n\n// Composable SidebarPrimary primitives mirroring the Figma \"SidebarPrimary\"\n// component set (node 2092:4359, variant expanded|collapsed). Every color and\n// metric is wired to a next-gen `--ui-sidebar-primary-*` token from\n// @acronis-platform/tokens-pd — no hex, no invented tokens.\n//\n// expanded/collapsed is a width-reflow state, not a panel show/hide, so it is\n// modelled as a controlled/uncontrolled `expanded` prop (default true) that sets\n// `data-state=\"expanded|collapsed\"` on the root. Every metric token has a\n// distinct `collapsed-*` / `expanded-*` name, switched via `data-[state=…]`\n// attribute selectors — Base UI Collapsible would fight this (it hides a panel;\n// the rail never hides). Layout state is shared to descendants through a small\n// context so menu items know whether to show or `sr-only` their label/extras.\n//\n// R2 (menu-item border-radius): the token tier ships no radius token. Verified\n// against the Figma node — the menu-item COMPONENT and its container frame both\n// report cornerRadius=None (the only radius in the node is the COMPONENT_SET's\n// own variant-container chrome). Rows are therefore SQUARE; no `rounded-*` is\n// applied. The focus ring uses the shared `--ui-focus-brand` (no sidebar focus\n// token exists — R1), matching Button/Breadcrumb.\n\ninterface SidebarPrimaryContextValue {\n expanded: boolean;\n /** Flip the rail width — drives the controlled/uncontrolled `expanded` state. */\n toggleExpanded: () => void;\n}\n\nconst SidebarPrimaryContext =\n React.createContext<SidebarPrimaryContextValue | null>(null);\n\nfunction useSidebarPrimaryContext(): SidebarPrimaryContextValue {\n // Default to expanded so parts render standalone (e.g. in isolation tests /\n // stories) without a wrapping root; the toggle is a no-op outside a root.\n return (\n React.useContext(SidebarPrimaryContext) ?? {\n expanded: true,\n toggleExpanded: () => {},\n }\n );\n}\n\n/**\n * Controlled + uncontrolled boolean state (the Base UI idiom). When `controlled`\n * is provided it wins and the setter only emits the change callback; otherwise\n * the setter updates internal state. `onChange` is ALWAYS invoked with the next\n * value so a consumer can react in either mode.\n */\nfunction useControllableBoolean(\n controlled: boolean | undefined,\n defaultValue: boolean,\n onChange?: (next: boolean) => void\n): [boolean, (next: boolean) => void] {\n const [uncontrolled, setUncontrolled] = React.useState(defaultValue);\n const isControlled = controlled !== undefined;\n const value = isControlled ? controlled : uncontrolled;\n const setValue = React.useCallback(\n (next: boolean) => {\n if (!isControlled) setUncontrolled(next);\n onChange?.(next);\n },\n [isControlled, onChange]\n );\n return [value, setValue];\n}\n\n/**\n * Tracks whether `ref`'s element is clipping its own content (`scrollWidth >\n * clientWidth`) — used to gate a tooltip so it only opens when a truncated\n * label is actually cut off. Re-measures via `ResizeObserver` (covers the\n * rail's expand/collapse width transition); `enabled` skips measurement\n * entirely (e.g. while collapsed, where the label is `sr-only`).\n */\nfunction useIsOverflowing<T extends Element>(\n ref: React.RefObject<T | null>,\n { enabled }: { enabled: boolean }\n): boolean {\n const [isOverflowing, setIsOverflowing] = React.useState(false);\n\n React.useEffect(() => {\n const element = ref.current;\n if (!enabled || !element) {\n setIsOverflowing(false);\n return;\n }\n const measure = () =>\n setIsOverflowing(element.scrollWidth > element.clientWidth);\n measure();\n const observer = new ResizeObserver(measure);\n observer.observe(element);\n return () => observer.disconnect();\n }, [ref, enabled]);\n\n return isOverflowing;\n}\n\nexport interface SidebarPrimaryProps\n extends React.ComponentPropsWithoutRef<'nav'> {\n /** Controlled expanded (rail width) state. */\n expanded?: boolean;\n /** Uncontrolled initial expanded state. Defaults to `true` (full width). */\n defaultExpanded?: boolean;\n /** Fires when the expanded state changes (e.g. a consumer toggle). */\n onExpandedChange?: (expanded: boolean) => void;\n /**\n * Replace the rendered `<nav>` with another element or component\n * (Base UI composition). Accepts a React element or a render function.\n */\n render?: useRender.RenderProp;\n}\n\nconst SidebarPrimary = React.forwardRef<HTMLElement, SidebarPrimaryProps>(\n (\n {\n className,\n expanded: expandedProp,\n defaultExpanded = true,\n onExpandedChange,\n 'aria-label': ariaLabel = 'Primary',\n render,\n children,\n ...props\n },\n ref\n ) => {\n const [expanded, setExpanded] = useControllableBoolean(\n expandedProp,\n defaultExpanded,\n onExpandedChange\n );\n\n // Collapse is driven by the consumer through the layout context — the\n // `SidebarPrimaryCollapseTrigger` (the Figma \"Collapse menu\" footer item)\n // calls `toggleExpanded`, which updates uncontrolled state and always emits\n // `onExpandedChange`. Controlled consumers ignore the internal state and\n // react to the callback.\n const context = React.useMemo<SidebarPrimaryContextValue>(\n () => ({ expanded, toggleExpanded: () => setExpanded(!expanded) }),\n [expanded, setExpanded]\n );\n\n const element = useRender({\n render,\n ref,\n defaultTagName: 'nav',\n props: mergeProps<'nav'>(\n {\n 'aria-label': ariaLabel,\n // `data-state` drives every collapsed/expanded token switch via\n // attribute selectors; typed loosely because React's nav attribute\n // map doesn't include arbitrary data-* keys as literals.\n ...({ 'data-state': expanded ? 'expanded' : 'collapsed' } as Record<\n string,\n string\n >),\n className: cn(\n 'group/sidebar flex h-full flex-col bg-[var(--ui-sidebar-primary-global-container-color)] w-[var(--ui-sidebar-primary-collapsed-container-width)] data-[state=expanded]:w-[var(--ui-sidebar-primary-expanded-container-width)] transition-[width]',\n className\n ),\n children,\n },\n props\n ),\n });\n\n return (\n <SidebarPrimaryContext.Provider value={context}>\n {element}\n </SidebarPrimaryContext.Provider>\n );\n }\n);\nSidebarPrimary.displayName = 'SidebarPrimary';\n\nexport interface SidebarPrimaryHeaderProps\n extends React.ComponentPropsWithoutRef<'div'> {\n /**\n * Logo content shown while the rail is expanded (e.g. a full lockup).\n * Pairs with `collapsedLogo` to swap distinct graphics per rail state\n * instead of resizing one. When neither `logo` nor `collapsedLogo` is\n * given, `children` renders in both states (the original single-slot\n * behavior).\n */\n logo?: React.ReactNode;\n /**\n * Logo content shown while the rail is collapsed (e.g. a monogram mark).\n * Falls back to `logo` when omitted.\n */\n collapsedLogo?: React.ReactNode;\n}\n\nconst SidebarPrimaryHeader = React.forwardRef<\n HTMLDivElement,\n SidebarPrimaryHeaderProps\n>(({ className, logo, collapsedLogo, children, ...props }, ref) => {\n // Hosts a consumer-provided logo (R7 — no Logo part is built). Padding and the\n // logo height switch on expanded/collapsed; `[&_*]:h-…` sizes whatever the\n // consumer slots in. The logo color token tints any `currentColor` mark.\n const { expanded } = useSidebarPrimaryContext();\n const hasTwinLogos = logo != null || collapsedLogo != null;\n const content = hasTwinLogos\n ? expanded\n ? (logo ?? collapsedLogo)\n : (collapsedLogo ?? logo)\n : children;\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex items-center shrink-0 text-[var(--ui-sidebar-primary-global-logo-color)] transition-[padding]',\n 'px-[var(--ui-sidebar-primary-collapsed-container-header-padding-x)] py-[var(--ui-sidebar-primary-collapsed-container-header-padding-y)]',\n // `logo`/`collapsedLogo` are two separate elements swapped by a JS\n // conditional (not one element whose size CSS-transitions) — the new\n // one mounts at its final height instantly, while `padding-y` is\n // still mid-transition. Since the two states' (padding*2 + logo\n // height) sums only match at the transition's start/end, that instant\n // pop makes the row briefly overshoot/undershoot its resting height\n // and \"jump\". Pinning the row to the larger of the two sums makes the\n // outer box's height constant across the whole transition regardless\n // of that mismatch — padding/logo-height still animate cosmetically\n // inside it.\n 'h-[max(calc(var(--ui-sidebar-primary-collapsed-container-header-padding-y)*2+var(--ui-sidebar-primary-collapsed-logo-height)),calc(var(--ui-sidebar-primary-expanded-container-header-padding-y)*2+var(--ui-sidebar-primary-expanded-logo-height)))]',\n // Animate the logo's own height alongside the header's padding transition\n // (both keyed off the same `data-state` flip) — without it the logo swaps\n // size instantly while the rail width is still animating, reading as a jump.\n '[&_:where(img,svg)]:h-[var(--ui-sidebar-primary-collapsed-logo-height)] [&_:where(img,svg)]:w-auto [&_:where(img,svg)]:transition-[height]',\n 'group-data-[state=expanded]/sidebar:px-[var(--ui-sidebar-primary-expanded-container-header-padding-x)] group-data-[state=expanded]/sidebar:py-[var(--ui-sidebar-primary-expanded-container-header-padding-y)]',\n 'group-data-[state=expanded]/sidebar:[&_:where(img,svg)]:h-[var(--ui-sidebar-primary-expanded-logo-height)]',\n className\n )}\n {...props}\n >\n {content}\n </div>\n );\n});\nSidebarPrimaryHeader.displayName = 'SidebarPrimaryHeader';\n\nconst SidebarPrimaryContent = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<'div'>\n>(({ className, children, ...props }, ref) => (\n // The section list scrolls inside a ScrollArea: its overlay scrollbar floats\n // over the content and reserves no gutter, so the full-bleed selected row is\n // never cropped — on every OS, unlike a native `overflow` scrollbar.\n //\n // Base UI's Scroll Area Content sets `min-width: fit-content` inline (it\n // needs the content's true intrinsic width to detect horizontal overflow),\n // which forces every row wider than the rail — overriding `min-w-0` +\n // `truncate` on a long label, since intrinsic-width calculations ignore\n // `text-overflow`. We only ever scroll vertically here, so cancel it: the\n // `!` (important) is required to beat the inline style's specificity.\n <ScrollArea\n ref={ref}\n className={cn(\n 'min-h-0 flex-1 [&_[data-slot=scroll-area-content]]:min-w-0!',\n className\n )}\n {...props}\n >\n <div className=\"flex flex-col gap-[var(--ui-sidebar-primary-global-section-list-gap)]\">\n {children}\n </div>\n </ScrollArea>\n));\nSidebarPrimaryContent.displayName = 'SidebarPrimaryContent';\n\nconst SidebarPrimaryFooter = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<'div'>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'flex flex-col shrink-0 gap-[var(--ui-sidebar-primary-global-footer-list-gap)]',\n 'border-t border-[var(--ui-sidebar-primary-global-container-footer-border-color)] [border-top-width:var(--ui-sidebar-primary-global-container-footer-border-width)]',\n className\n )}\n {...props}\n />\n));\nSidebarPrimaryFooter.displayName = 'SidebarPrimaryFooter';\n\nconst SidebarPrimarySection = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<'div'>\n>(({ className, ...props }, ref) => (\n // position (`:not(:first-child)`) instead of a prop.\n <div\n ref={ref}\n className={cn(\n 'flex flex-col pb-[var(--ui-sidebar-primary-section-container-padding-y)]',\n '[&:not(:first-child)]:border-t [&:not(:first-child)]:border-[var(--ui-sidebar-primary-section-container-border-color)] [&:not(:first-child)]:[border-top-width:var(--ui-sidebar-primary-section-container-border-width)] [&:not(:first-child)]:pt-[var(--ui-sidebar-primary-section-container-padding-y)]',\n className\n )}\n {...props}\n />\n));\nSidebarPrimarySection.displayName = 'SidebarPrimarySection';\n\nconst SidebarPrimaryMenu = React.forwardRef<\n HTMLUListElement,\n React.ComponentPropsWithoutRef<'ul'>\n>(({ className, ...props }, ref) => (\n <ul\n ref={ref}\n className={cn(\n 'flex flex-col gap-[var(--ui-sidebar-primary-section-menu-item-list-gap)]',\n className\n )}\n {...props}\n />\n));\nSidebarPrimaryMenu.displayName = 'SidebarPrimaryMenu';\n\n// Primary recolors the container, the icon AND the label per variant\n// (selected|unselected) × interaction state (idle/hover/active). Every state is\n// wired to its own token even where acronis's value is unchanged, because these\n// are runtime `var()` references — a brand override is only honored if the\n// matching state token is referenced. (This is why Primary cannot share a cva\n// with Secondary, which recolors only the container — DESIGN §6.2.)\nconst sidebarPrimaryMenuItemVariants = cva(\n 'group/menu-item flex w-full items-center gap-[var(--ui-sidebar-primary-menu-item-global-container-gap)] h-[var(--ui-sidebar-primary-menu-item-global-container-height)] px-[var(--ui-sidebar-primary-menu-item-global-container-padding-x)] py-[var(--ui-sidebar-primary-menu-item-global-container-padding-y)] no-underline cursor-pointer ui-sidebar-primary-menu-item-global-label-text-style transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-focus-brand)] focus-visible:ring-inset [&_svg]:shrink-0 [&_svg]:size-[var(--ui-sidebar-primary-menu-item-global-icon-size)]',\n {\n variants: {\n variant: {\n unselected:\n 'bg-[var(--ui-sidebar-primary-menu-item-unselected-container-color-idle)] text-[var(--ui-sidebar-primary-menu-item-unselected-label-color-idle)] [&_svg]:text-[var(--ui-sidebar-primary-menu-item-unselected-icon-color-idle)] hover:bg-[var(--ui-sidebar-primary-menu-item-unselected-container-color-hover)] hover:text-[var(--ui-sidebar-primary-menu-item-unselected-label-color-hover)] hover:[&_svg]:text-[var(--ui-sidebar-primary-menu-item-unselected-icon-color-hover)] active:bg-[var(--ui-sidebar-primary-menu-item-unselected-container-color-active)] active:text-[var(--ui-sidebar-primary-menu-item-unselected-label-color-active)] active:[&_svg]:text-[var(--ui-sidebar-primary-menu-item-unselected-icon-color-active)]',\n selected:\n 'bg-[var(--ui-sidebar-primary-menu-item-selected-container-color-idle)] text-[var(--ui-sidebar-primary-menu-item-selected-label-color-idle)] [&_svg]:text-[var(--ui-sidebar-primary-menu-item-selected-icon-color-idle)] hover:bg-[var(--ui-sidebar-primary-menu-item-selected-container-color-hover)] hover:text-[var(--ui-sidebar-primary-menu-item-selected-label-color-hover)] hover:[&_svg]:text-[var(--ui-sidebar-primary-menu-item-selected-icon-color-hover)] active:bg-[var(--ui-sidebar-primary-menu-item-selected-container-color-active)] active:text-[var(--ui-sidebar-primary-menu-item-selected-label-color-active)] active:[&_svg]:text-[var(--ui-sidebar-primary-menu-item-selected-icon-color-active)]',\n },\n },\n defaultVariants: {\n variant: 'unselected',\n },\n }\n);\n\ninterface SidebarPrimaryMenuItemBaseProps\n extends Omit<React.ComponentPropsWithoutRef<'a'>, 'children'>,\n Omit<VariantProps<typeof sidebarPrimaryMenuItemVariants>, 'variant'> {\n /** Marks the current route: sets the `selected` variant + `aria-current=\"page\"`. */\n selected?: boolean;\n children?: React.ReactNode;\n /**\n * Trailing affordance — a `SidebarPrimaryMenuItemExtras` element. Rendered as\n * a flex sibling of the label (its own `gap` from `_global/container/gap`),\n * not nested inside the label's truncating span — nesting it there collapses\n * the row gap and lets `truncate` clip the affordance. Hidden in collapsed/rail\n * mode regardless of node type — the parent wraps it, so this holds even for\n * a raw node rather than `SidebarPrimaryMenuItemExtras`.\n */\n extras?: React.ReactNode;\n /**\n * Replace the rendered `<a>` with another element or component (e.g. a router\n * `Link` or a `<button>`) via Base UI composition.\n */\n render?: useRender.RenderProp;\n}\n\n// A nav rail row is icon-only when collapsed, so `icon` is required by default\n// (UX: every row must stay legible in rail mode). `noIcon` is the explicit,\n// rare escape hatch for a consumer that truly has no icon for a row.\nexport type SidebarPrimaryMenuItemProps = SidebarPrimaryMenuItemBaseProps &\n (\n | {\n /** Leading 16px icon (from `@acronis-platform/icons-react`). */\n icon: Exclude<React.ReactNode, undefined>;\n noIcon?: never;\n }\n | { icon?: undefined; /** Explicit opt-out for the rare row with no icon. */ noIcon: true }\n );\n\nconst SidebarPrimaryMenuItem = React.forwardRef<\n HTMLAnchorElement,\n SidebarPrimaryMenuItemProps\n>(\n (\n {\n className,\n selected = false,\n icon,\n noIcon,\n render,\n children,\n extras,\n ...props\n },\n ref\n ) => {\n const { expanded } = useSidebarPrimaryContext();\n const dir = useDocDir();\n const rowRef = React.useRef<HTMLAnchorElement>(null);\n const labelRef = React.useRef<HTMLSpanElement>(null);\n const isOverflowing = useIsOverflowing(labelRef, { enabled: expanded });\n\n const inner = useRender({\n render,\n ref: [ref, rowRef],\n defaultTagName: 'a',\n props: mergeProps<'a'>(\n {\n className: cn(\n sidebarPrimaryMenuItemVariants({\n variant: selected ? 'selected' : 'unselected',\n }),\n className\n ),\n 'aria-current': selected ? 'page' : undefined,\n onKeyDown: (e: React.KeyboardEvent<HTMLAnchorElement>) => {\n if (e.key === ' ') {\n e.preventDefault();\n e.currentTarget.click();\n }\n },\n children: (\n <>\n {icon != null && (\n <span className=\"flex shrink-0 items-center self-start mt-[var(--ui-sidebar-primary-menu-item-global-icon-margin-t)]\">\n {icon}\n </span>\n )}\n {/* Keep the label in the DOM in collapsed/rail mode as `sr-only` so\n the icon-only row keeps an accessible name (a11y §7) — never\n `display:none` the text. Extras are visually dropped when\n collapsed but stay queryable for the same reason. The tooltip\n trigger is the label span itself, not the row — it must not\n open when hovering the icon or extras, and only ever opens\n when the label is actually clipped. It's anchored to the row\n (not the label span) so it lines up with the sidebar's right\n edge regardless of how much the label shrinks for extras. */}\n <Tooltip disabled={!isOverflowing}>\n <TooltipTrigger\n render={\n <span\n ref={labelRef}\n className={cn(\n 'min-w-0 flex-1 truncate text-start',\n !expanded && 'sr-only'\n )}\n />\n }\n >\n {children}\n </TooltipTrigger>\n <TooltipContent\n anchor={rowRef}\n side={dir === 'rtl' ? 'left' : 'right'}\n >\n {children}\n </TooltipContent>\n </Tooltip>\n {extras != null && (\n <span className={cn(!expanded && 'hidden')}>{extras}</span>\n )}\n </>\n ),\n },\n props\n ),\n });\n\n return (\n <li className=\"contents\">\n {/* In collapsed/rail mode the label above is `sr-only` (zero-size), so\n it never receives the hover that would open its tooltip — wrap the\n whole row so the icon itself is hoverable and always shows the\n item's label as a tooltip while collapsed. */}\n <Tooltip disabled={expanded}>\n <TooltipTrigger render={inner} />\n <TooltipContent side={dir === 'rtl' ? 'left' : 'right'}>\n {children}\n </TooltipContent>\n </Tooltip>\n </li>\n );\n }\n);\nSidebarPrimaryMenuItem.displayName = 'SidebarPrimaryMenuItem';\n\nexport interface SidebarPrimaryMenuItemExtrasProps\n extends React.ComponentPropsWithoutRef<'span'> {\n /** Which trailing affordance to render. */\n variant: 'tag' | 'externalLink' | 'shortcut' | 'tag-externalLink';\n /** Shortcut text (e.g. \"⌘H\") for the `shortcut` variant. */\n shortcut?: string;\n /**\n * Tag content for the `tag` / `tag-externalLink` variants. Figma constrains\n * this slot's `Tag` instance to `size=\"sm\"` (no other size is offered on\n * MenuItemExtras) — always pass a `<Tag variant=\"info\" size=\"sm\">`, never the\n * default size.\n */\n tag?: React.ReactNode;\n}\n\nconst SidebarPrimaryMenuItemExtras = React.forwardRef<\n HTMLSpanElement,\n SidebarPrimaryMenuItemExtrasProps\n>(({ className, variant, shortcut, tag, children, ...props }, ref) => {\n const { expanded } = useSidebarPrimaryContext();\n const showTag = variant === 'tag' || variant === 'tag-externalLink';\n const showExternal =\n variant === 'externalLink' || variant === 'tag-externalLink';\n const showShortcut = variant === 'shortcut';\n\n return (\n <span\n ref={ref}\n // The extras cluster is rail-mode noise — drop it when collapsed.\n className={cn(\n 'inline-flex items-center gap-[var(--ui-sidebar-primary-menu-item-extras-global-container-gap)]',\n !expanded && 'hidden',\n className\n )}\n {...props}\n >\n {showTag && (tag ?? children)}\n {showShortcut && (\n <span className=\"ui-sidebar-primary-menu-item-extras-global-shortcut-text-style text-[var(--ui-sidebar-primary-menu-item-extras-global-shortcut-color)]\">\n {shortcut ?? children}\n </span>\n )}\n {showExternal && (\n <SquareArrowUpRightIcon\n size={16}\n className=\"text-[var(--ui-sidebar-primary-menu-item-extras-global-external-icon-color)] size-[var(--ui-sidebar-primary-menu-item-extras-global-external-icon-size)]\"\n />\n )}\n </span>\n );\n});\nSidebarPrimaryMenuItemExtras.displayName = 'SidebarPrimaryMenuItemExtras';\n\nexport interface SidebarPrimaryCollapseTriggerProps\n extends Omit<React.ComponentPropsWithoutRef<'button'>, 'children'> {\n /** Leading 16px icon (e.g. a panel-left glyph). Rotates 180° between expanded/collapsed. */\n icon?: React.ReactNode;\n children?: React.ReactNode;\n /**\n * Trailing affordance — a `SidebarPrimaryMenuItemExtras` element (Figma\n * shows a `⌘H` shortcut here). Rendered as a flex sibling of the label, same\n * as `SidebarPrimaryMenuItem`'s `extras` prop.\n */\n extras?: React.ReactNode;\n /** Tooltip text shown when the rail is collapsed. Defaults to `'Expand'`. */\n expandTooltip?: React.ReactNode;\n}\n\n// The footer \"Collapse menu\" affordance (Figma). A menu-item-styled `<button>`\n// that flips the rail width via the layout context — this is the live wiring\n// for the controllable `expanded` state (B1). In collapsed mode it keeps its\n// label as `sr-only` like every other row, and (same as\n// `SidebarSecondaryCollapseTrigger`) wraps the whole button in a tooltip\n// showing `expandTooltip` — the `sr-only` label can't receive the hover that\n// would open its own tooltip.\nconst SidebarPrimaryCollapseTrigger = React.forwardRef<\n HTMLButtonElement,\n SidebarPrimaryCollapseTriggerProps\n>(\n (\n { className, icon, children, extras, expandTooltip = 'Expand', onClick, ...props },\n ref\n ) => {\n const { expanded, toggleExpanded } = useSidebarPrimaryContext();\n const dir = useDocDir();\n const buttonRef = React.useRef<HTMLButtonElement>(null);\n const labelRef = React.useRef<HTMLSpanElement>(null);\n const isOverflowing = useIsOverflowing(labelRef, { enabled: expanded });\n\n const button = (\n <button\n ref={(node) => {\n buttonRef.current = node;\n if (typeof ref === 'function') ref(node);\n else if (ref) ref.current = node;\n }}\n type=\"button\"\n aria-expanded={expanded}\n className={cn(\n sidebarPrimaryMenuItemVariants({ variant: 'unselected' }),\n 'text-start',\n className\n )}\n onClick={(event) => {\n onClick?.(event);\n if (!event.defaultPrevented) toggleExpanded();\n }}\n {...props}\n >\n {icon != null && (\n <span\n className={cn(\n 'flex shrink-0 items-center self-start mt-[var(--ui-sidebar-primary-menu-item-global-icon-margin-t)] transition-transform',\n expanded ? 'rtl:rotate-180' : 'ltr:rotate-180'\n )}\n >\n {icon}\n </span>\n )}\n <Tooltip disabled={!isOverflowing}>\n <TooltipTrigger\n render={\n <span\n ref={labelRef}\n className={cn(\n 'min-w-0 flex-1 truncate',\n !expanded && 'sr-only'\n )}\n />\n }\n >\n {children}\n </TooltipTrigger>\n <TooltipContent\n anchor={buttonRef}\n side={dir === 'rtl' ? 'left' : 'right'}\n >\n {children}\n </TooltipContent>\n </Tooltip>\n {extras != null && (\n <span className={cn(!expanded && 'hidden')}>{extras}</span>\n )}\n </button>\n );\n\n return (\n <li className=\"contents\">\n <Tooltip disabled={expanded}>\n <TooltipTrigger render={button} />\n <TooltipContent side={dir === 'rtl' ? 'left' : 'right'}>\n {expandTooltip}\n </TooltipContent>\n </Tooltip>\n </li>\n );\n }\n);\nSidebarPrimaryCollapseTrigger.displayName = 'SidebarPrimaryCollapseTrigger';\n\nexport {\n SidebarPrimary,\n SidebarPrimaryHeader,\n SidebarPrimaryContent,\n SidebarPrimaryFooter,\n SidebarPrimarySection,\n SidebarPrimaryMenu,\n SidebarPrimaryMenuItem,\n SidebarPrimaryMenuItemExtras,\n SidebarPrimaryCollapseTrigger,\n useIsOverflowing,\n sidebarPrimaryMenuItemVariants,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sidebar-primary.js","names":[],"sources":["../../../../src/components/ui/sidebar-primary/sidebar-primary.tsx"],"sourcesContent":["import * as React from 'react';\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { SquareArrowUpRightIcon } from '@acronis-platform/icons-react/stroke-mono';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/lib/utils';\nimport { useDocDir } from '@/lib/use-doc-dir';\n\nimport { ScrollArea } from '../scroll-area';\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip';\n\n// Composable SidebarPrimary primitives mirroring the Figma \"SidebarPrimary\"\n// component set (node 2092:4359, variant expanded|collapsed). Every color and\n// metric is wired to a next-gen `--ui-sidebar-primary-*` token from\n// @acronis-platform/tokens-pd — no hex, no invented tokens.\n//\n// expanded/collapsed is a width-reflow state, not a panel show/hide, so it is\n// modelled as a controlled/uncontrolled `expanded` prop (default true) that sets\n// `data-state=\"expanded|collapsed\"` on the root. Every metric token has a\n// distinct `collapsed-*` / `expanded-*` name, switched via `data-[state=…]`\n// attribute selectors — Base UI Collapsible would fight this (it hides a panel;\n// the rail never hides). Layout state is shared to descendants through a small\n// context so menu items know whether to show or `sr-only` their label/extras.\n//\n// R2 (menu-item border-radius): the token tier ships no radius token. Verified\n// against the Figma node — the menu-item COMPONENT and its container frame both\n// report cornerRadius=None (the only radius in the node is the COMPONENT_SET's\n// own variant-container chrome). Rows are therefore SQUARE; no `rounded-*` is\n// applied. The focus ring uses the shared `--ui-focus-brand` (no sidebar focus\n// token exists — R1), matching Button/Breadcrumb.\n\ninterface SidebarPrimaryContextValue {\n expanded: boolean;\n /** Flip the rail width — drives the controlled/uncontrolled `expanded` state. */\n toggleExpanded: () => void;\n}\n\nconst SidebarPrimaryContext =\n React.createContext<SidebarPrimaryContextValue | null>(null);\n\nfunction useSidebarPrimaryContext(): SidebarPrimaryContextValue {\n // Default to expanded so parts render standalone (e.g. in isolation tests /\n // stories) without a wrapping root; the toggle is a no-op outside a root.\n return (\n React.useContext(SidebarPrimaryContext) ?? {\n expanded: true,\n toggleExpanded: () => {},\n }\n );\n}\n\n/**\n * Controlled + uncontrolled boolean state (the Base UI idiom). When `controlled`\n * is provided it wins and the setter only emits the change callback; otherwise\n * the setter updates internal state. `onChange` is ALWAYS invoked with the next\n * value so a consumer can react in either mode.\n */\nfunction useControllableBoolean(\n controlled: boolean | undefined,\n defaultValue: boolean,\n onChange?: (next: boolean) => void\n): [boolean, (next: boolean) => void] {\n const [uncontrolled, setUncontrolled] = React.useState(defaultValue);\n const isControlled = controlled !== undefined;\n const value = isControlled ? controlled : uncontrolled;\n const setValue = React.useCallback(\n (next: boolean) => {\n if (!isControlled) setUncontrolled(next);\n onChange?.(next);\n },\n [isControlled, onChange]\n );\n return [value, setValue];\n}\n\n/**\n * Tracks whether `ref`'s element is clipping its own content (`scrollWidth >\n * clientWidth`) — used to gate a tooltip so it only opens when a truncated\n * label is actually cut off. Re-measures via `ResizeObserver` (covers the\n * rail's expand/collapse width transition); `enabled` skips measurement\n * entirely (e.g. while collapsed, where the label is `sr-only`).\n */\nfunction useIsOverflowing<T extends Element>(\n ref: React.RefObject<T | null>,\n { enabled }: { enabled: boolean }\n): boolean {\n const [isOverflowing, setIsOverflowing] = React.useState(false);\n\n React.useEffect(() => {\n const element = ref.current;\n if (!enabled || !element) {\n setIsOverflowing(false);\n return;\n }\n const measure = () =>\n setIsOverflowing(element.scrollWidth > element.clientWidth);\n measure();\n const observer = new ResizeObserver(measure);\n observer.observe(element);\n return () => observer.disconnect();\n }, [ref, enabled]);\n\n return isOverflowing;\n}\n\nexport interface SidebarPrimaryProps\n extends React.ComponentPropsWithoutRef<'nav'> {\n /** Controlled expanded (rail width) state. */\n expanded?: boolean;\n /** Uncontrolled initial expanded state. Defaults to `true` (full width). */\n defaultExpanded?: boolean;\n /** Fires when the expanded state changes (e.g. a consumer toggle). */\n onExpandedChange?: (expanded: boolean) => void;\n /**\n * Replace the rendered `<nav>` with another element or component\n * (Base UI composition). Accepts a React element or a render function.\n */\n render?: useRender.RenderProp;\n}\n\nconst SidebarPrimary = React.forwardRef<HTMLElement, SidebarPrimaryProps>(\n (\n {\n className,\n expanded: expandedProp,\n defaultExpanded = true,\n onExpandedChange,\n 'aria-label': ariaLabel = 'Primary',\n render,\n children,\n ...props\n },\n ref\n ) => {\n const [expanded, setExpanded] = useControllableBoolean(\n expandedProp,\n defaultExpanded,\n onExpandedChange\n );\n\n // Collapse is driven by the consumer through the layout context — the\n // `SidebarPrimaryCollapseTrigger` (the Figma \"Collapse menu\" footer item)\n // calls `toggleExpanded`, which updates uncontrolled state and always emits\n // `onExpandedChange`. Controlled consumers ignore the internal state and\n // react to the callback.\n const context = React.useMemo<SidebarPrimaryContextValue>(\n () => ({ expanded, toggleExpanded: () => setExpanded(!expanded) }),\n [expanded, setExpanded]\n );\n\n const element = useRender({\n render,\n ref,\n defaultTagName: 'nav',\n props: mergeProps<'nav'>(\n {\n 'aria-label': ariaLabel,\n // `data-state` drives every collapsed/expanded token switch via\n // attribute selectors; typed loosely because React's nav attribute\n // map doesn't include arbitrary data-* keys as literals.\n ...({ 'data-state': expanded ? 'expanded' : 'collapsed' } as Record<\n string,\n string\n >),\n className: cn(\n 'group/sidebar flex h-full flex-col bg-[var(--ui-sidebar-primary-global-container-color)] w-[var(--ui-sidebar-primary-collapsed-container-width)] data-[state=expanded]:w-[var(--ui-sidebar-primary-expanded-container-width)] transition-[width]',\n className\n ),\n children,\n },\n props\n ),\n });\n\n return (\n <SidebarPrimaryContext.Provider value={context}>\n {element}\n </SidebarPrimaryContext.Provider>\n );\n }\n);\nSidebarPrimary.displayName = 'SidebarPrimary';\n\nexport interface SidebarPrimaryHeaderProps\n extends React.ComponentPropsWithoutRef<'div'> {\n /**\n * Logo content shown while the rail is expanded (e.g. a full lockup).\n * Pairs with `collapsedLogo` to swap distinct graphics per rail state\n * instead of resizing one. When neither `logo` nor `collapsedLogo` is\n * given, `children` renders in both states (the original single-slot\n * behavior).\n */\n logo?: React.ReactNode;\n /**\n * Logo content shown while the rail is collapsed (e.g. a monogram mark).\n * Falls back to `logo` when omitted.\n */\n collapsedLogo?: React.ReactNode;\n /**\n * Overrides the expanded-state logo height in pixels, for a consumer logo\n * asset authored taller than the design system's default 48px slot (e.g. a\n * tenant-branded logo). Omit to use the design system default.\n *\n * Affects **both** rail states' header height: the header's row height is\n * pinned to whichever state's (padding-y * 2 + logo height) sum is larger,\n * so that the row doesn't visually jump during the collapse/expand\n * transition. Raising this past the collapsed state's sum therefore grows\n * the collapsed header row too, even though the collapsed logo itself is\n * unchanged.\n */\n logoHeight?: number;\n /**\n * Overrides the collapsed-state logo height in pixels. Omit to use the\n * design system default (32px).\n *\n * Affects **both** rail states' header height, for the same reason as\n * `logoHeight`: the shared row height is the larger of the two states'\n * (padding-y * 2 + logo height) sums, so raising this past the expanded\n * state's sum grows the expanded header row as well.\n */\n collapsedLogoHeight?: number;\n}\n\nconst SidebarPrimaryHeader = React.forwardRef<\n HTMLDivElement,\n SidebarPrimaryHeaderProps\n>(\n (\n {\n className,\n logo,\n collapsedLogo,\n logoHeight,\n collapsedLogoHeight,\n style,\n children,\n ...props\n },\n ref\n ) => {\n // Hosts a consumer-provided logo (R7 — no Logo part is built). Padding and the\n // logo height switch on expanded/collapsed; `[&_*]:h-…` sizes whatever the\n // consumer slots in. The logo color token tints any `currentColor` mark.\n const { expanded } = useSidebarPrimaryContext();\n const hasTwinLogos = logo != null || collapsedLogo != null;\n const content = hasTwinLogos\n ? expanded\n ? (logo ?? collapsedLogo)\n : (collapsedLogo ?? logo)\n : children;\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex items-center shrink-0 text-[var(--ui-sidebar-primary-global-logo-color)] transition-[padding]',\n 'px-[var(--ui-sidebar-primary-collapsed-container-header-padding-x)] py-[var(--ui-sidebar-primary-collapsed-container-header-padding-y)]',\n // `logo`/`collapsedLogo` are two separate elements swapped by a JS\n // conditional (not one element whose size CSS-transitions) — the new\n // one mounts at its final height instantly, while `padding-y` is\n // still mid-transition. Since the two states' (padding*2 + logo\n // height) sums only match at the transition's start/end, that instant\n // pop makes the row briefly overshoot/undershoot its resting height\n // and \"jump\". Pinning the row to the larger of the two sums makes the\n // outer box's height constant across the whole transition regardless\n // of that mismatch — padding/logo-height still animate cosmetically\n // inside it.\n 'h-[max(calc(var(--ui-sidebar-primary-collapsed-container-header-padding-y)*2+var(--ui-sidebar-primary-collapsed-logo-height)),calc(var(--ui-sidebar-primary-expanded-container-header-padding-y)*2+var(--ui-sidebar-primary-expanded-logo-height)))]',\n // Animate the logo's own height alongside the header's padding transition\n // (both keyed off the same `data-state` flip) — without it the logo swaps\n // size instantly while the rail width is still animating, reading as a jump.\n '[&_:where(img,svg)]:h-[var(--ui-sidebar-primary-collapsed-logo-height)] [&_:where(img,svg)]:w-auto [&_:where(img,svg)]:transition-[height]',\n 'group-data-[state=expanded]/sidebar:px-[var(--ui-sidebar-primary-expanded-container-header-padding-x)] group-data-[state=expanded]/sidebar:py-[var(--ui-sidebar-primary-expanded-container-header-padding-y)]',\n 'group-data-[state=expanded]/sidebar:[&_:where(img,svg)]:h-[var(--ui-sidebar-primary-expanded-logo-height)]',\n className\n )}\n style={\n {\n ...(logoHeight != null && {\n '--ui-sidebar-primary-expanded-logo-height': `${logoHeight}px`,\n }),\n ...(collapsedLogoHeight != null && {\n '--ui-sidebar-primary-collapsed-logo-height': `${collapsedLogoHeight}px`,\n }),\n ...style,\n } as React.CSSProperties\n }\n {...props}\n >\n {content}\n </div>\n );\n }\n);\nSidebarPrimaryHeader.displayName = 'SidebarPrimaryHeader';\n\nconst SidebarPrimaryContent = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<'div'>\n>(({ className, children, ...props }, ref) => (\n // The section list scrolls inside a ScrollArea: its overlay scrollbar floats\n // over the content and reserves no gutter, so the full-bleed selected row is\n // never cropped — on every OS, unlike a native `overflow` scrollbar.\n //\n // Base UI's Scroll Area Content sets `min-width: fit-content` inline (it\n // needs the content's true intrinsic width to detect horizontal overflow),\n // which forces every row wider than the rail — overriding `min-w-0` +\n // `truncate` on a long label, since intrinsic-width calculations ignore\n // `text-overflow`. We only ever scroll vertically here, so cancel it: the\n // `!` (important) is required to beat the inline style's specificity.\n <ScrollArea\n ref={ref}\n className={cn(\n 'min-h-0 flex-1 [&_[data-slot=scroll-area-content]]:min-w-0!',\n className\n )}\n {...props}\n >\n <div className=\"flex flex-col gap-[var(--ui-sidebar-primary-global-section-list-gap)]\">\n {children}\n </div>\n </ScrollArea>\n));\nSidebarPrimaryContent.displayName = 'SidebarPrimaryContent';\n\nconst SidebarPrimaryFooter = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<'div'>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'flex flex-col shrink-0 gap-[var(--ui-sidebar-primary-global-footer-list-gap)]',\n 'border-t border-[var(--ui-sidebar-primary-global-container-footer-border-color)] [border-top-width:var(--ui-sidebar-primary-global-container-footer-border-width)]',\n className\n )}\n {...props}\n />\n));\nSidebarPrimaryFooter.displayName = 'SidebarPrimaryFooter';\n\nconst SidebarPrimarySection = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<'div'>\n>(({ className, ...props }, ref) => (\n // position (`:not(:first-child)`) instead of a prop.\n <div\n ref={ref}\n className={cn(\n 'flex flex-col pb-[var(--ui-sidebar-primary-section-container-padding-y)]',\n '[&:not(:first-child)]:border-t [&:not(:first-child)]:border-[var(--ui-sidebar-primary-section-container-border-color)] [&:not(:first-child)]:[border-top-width:var(--ui-sidebar-primary-section-container-border-width)] [&:not(:first-child)]:pt-[var(--ui-sidebar-primary-section-container-padding-y)]',\n className\n )}\n {...props}\n />\n));\nSidebarPrimarySection.displayName = 'SidebarPrimarySection';\n\nconst SidebarPrimaryMenu = React.forwardRef<\n HTMLUListElement,\n React.ComponentPropsWithoutRef<'ul'>\n>(({ className, ...props }, ref) => (\n <ul\n ref={ref}\n className={cn(\n 'flex flex-col gap-[var(--ui-sidebar-primary-section-menu-item-list-gap)]',\n className\n )}\n {...props}\n />\n));\nSidebarPrimaryMenu.displayName = 'SidebarPrimaryMenu';\n\n// Primary recolors the container, the icon AND the label per variant\n// (selected|unselected) × interaction state (idle/hover/active). Every state is\n// wired to its own token even where acronis's value is unchanged, because these\n// are runtime `var()` references — a brand override is only honored if the\n// matching state token is referenced. (This is why Primary cannot share a cva\n// with Secondary, which recolors only the container — DESIGN §6.2.)\nconst sidebarPrimaryMenuItemVariants = cva(\n 'group/menu-item flex w-full items-center gap-[var(--ui-sidebar-primary-menu-item-global-container-gap)] h-[var(--ui-sidebar-primary-menu-item-global-container-height)] px-[var(--ui-sidebar-primary-menu-item-global-container-padding-x)] py-[var(--ui-sidebar-primary-menu-item-global-container-padding-y)] no-underline cursor-pointer ui-sidebar-primary-menu-item-global-label-text-style transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-focus-brand)] focus-visible:ring-inset [&_svg]:shrink-0 [&_svg]:size-[var(--ui-sidebar-primary-menu-item-global-icon-size)]',\n {\n variants: {\n variant: {\n unselected:\n 'bg-[var(--ui-sidebar-primary-menu-item-unselected-container-color-idle)] text-[var(--ui-sidebar-primary-menu-item-unselected-label-color-idle)] [&_svg]:text-[var(--ui-sidebar-primary-menu-item-unselected-icon-color-idle)] hover:bg-[var(--ui-sidebar-primary-menu-item-unselected-container-color-hover)] hover:text-[var(--ui-sidebar-primary-menu-item-unselected-label-color-hover)] hover:[&_svg]:text-[var(--ui-sidebar-primary-menu-item-unselected-icon-color-hover)] active:bg-[var(--ui-sidebar-primary-menu-item-unselected-container-color-active)] active:text-[var(--ui-sidebar-primary-menu-item-unselected-label-color-active)] active:[&_svg]:text-[var(--ui-sidebar-primary-menu-item-unselected-icon-color-active)]',\n selected:\n 'bg-[var(--ui-sidebar-primary-menu-item-selected-container-color-idle)] text-[var(--ui-sidebar-primary-menu-item-selected-label-color-idle)] [&_svg]:text-[var(--ui-sidebar-primary-menu-item-selected-icon-color-idle)] hover:bg-[var(--ui-sidebar-primary-menu-item-selected-container-color-hover)] hover:text-[var(--ui-sidebar-primary-menu-item-selected-label-color-hover)] hover:[&_svg]:text-[var(--ui-sidebar-primary-menu-item-selected-icon-color-hover)] active:bg-[var(--ui-sidebar-primary-menu-item-selected-container-color-active)] active:text-[var(--ui-sidebar-primary-menu-item-selected-label-color-active)] active:[&_svg]:text-[var(--ui-sidebar-primary-menu-item-selected-icon-color-active)]',\n },\n },\n defaultVariants: {\n variant: 'unselected',\n },\n }\n);\n\ninterface SidebarPrimaryMenuItemBaseProps\n extends Omit<React.ComponentPropsWithoutRef<'a'>, 'children'>,\n Omit<VariantProps<typeof sidebarPrimaryMenuItemVariants>, 'variant'> {\n /** Marks the current route: sets the `selected` variant + `aria-current=\"page\"`. */\n selected?: boolean;\n children?: React.ReactNode;\n /**\n * Trailing affordance — a `SidebarPrimaryMenuItemExtras` element. Rendered as\n * a flex sibling of the label (its own `gap` from `_global/container/gap`),\n * not nested inside the label's truncating span — nesting it there collapses\n * the row gap and lets `truncate` clip the affordance. Hidden in collapsed/rail\n * mode regardless of node type — the parent wraps it, so this holds even for\n * a raw node rather than `SidebarPrimaryMenuItemExtras`.\n */\n extras?: React.ReactNode;\n /**\n * Replace the rendered `<a>` with another element or component (e.g. a router\n * `Link` or a `<button>`) via Base UI composition.\n */\n render?: useRender.RenderProp;\n}\n\n// A nav rail row is icon-only when collapsed, so `icon` is required by default\n// (UX: every row must stay legible in rail mode). `noIcon` is the explicit,\n// rare escape hatch for a consumer that truly has no icon for a row.\nexport type SidebarPrimaryMenuItemProps = SidebarPrimaryMenuItemBaseProps &\n (\n | {\n /** Leading 16px icon (from `@acronis-platform/icons-react`). */\n icon: Exclude<React.ReactNode, undefined>;\n noIcon?: never;\n }\n | { icon?: undefined; /** Explicit opt-out for the rare row with no icon. */ noIcon: true }\n );\n\nconst SidebarPrimaryMenuItem = React.forwardRef<\n HTMLAnchorElement,\n SidebarPrimaryMenuItemProps\n>(\n (\n {\n className,\n selected = false,\n icon,\n noIcon,\n render,\n children,\n extras,\n ...props\n },\n ref\n ) => {\n const { expanded } = useSidebarPrimaryContext();\n const dir = useDocDir();\n const rowRef = React.useRef<HTMLAnchorElement>(null);\n const labelRef = React.useRef<HTMLSpanElement>(null);\n const isOverflowing = useIsOverflowing(labelRef, { enabled: expanded });\n\n const inner = useRender({\n render,\n ref: [ref, rowRef],\n defaultTagName: 'a',\n props: mergeProps<'a'>(\n {\n className: cn(\n sidebarPrimaryMenuItemVariants({\n variant: selected ? 'selected' : 'unselected',\n }),\n className\n ),\n 'aria-current': selected ? 'page' : undefined,\n onKeyDown: (e: React.KeyboardEvent<HTMLAnchorElement>) => {\n if (e.key === ' ') {\n e.preventDefault();\n e.currentTarget.click();\n }\n },\n children: (\n <>\n {icon != null && (\n <span className=\"flex shrink-0 items-center self-start mt-[var(--ui-sidebar-primary-menu-item-global-icon-margin-t)]\">\n {icon}\n </span>\n )}\n {/* Keep the label in the DOM in collapsed/rail mode as `sr-only` so\n the icon-only row keeps an accessible name (a11y §7) — never\n `display:none` the text. Extras are visually dropped when\n collapsed but stay queryable for the same reason. The tooltip\n trigger is the label span itself, not the row — it must not\n open when hovering the icon or extras, and only ever opens\n when the label is actually clipped. It's anchored to the row\n (not the label span) so it lines up with the sidebar's right\n edge regardless of how much the label shrinks for extras. */}\n <Tooltip disabled={!isOverflowing}>\n <TooltipTrigger\n render={\n <span\n ref={labelRef}\n className={cn(\n 'min-w-0 flex-1 truncate text-start',\n !expanded && 'sr-only'\n )}\n />\n }\n >\n {children}\n </TooltipTrigger>\n <TooltipContent\n anchor={rowRef}\n side={dir === 'rtl' ? 'left' : 'right'}\n >\n {children}\n </TooltipContent>\n </Tooltip>\n {extras != null && (\n <span className={cn(!expanded && 'hidden')}>{extras}</span>\n )}\n </>\n ),\n },\n props\n ),\n });\n\n return (\n <li className=\"contents\">\n {/* In collapsed/rail mode the label above is `sr-only` (zero-size), so\n it never receives the hover that would open its tooltip — wrap the\n whole row so the icon itself is hoverable and always shows the\n item's label as a tooltip while collapsed. */}\n <Tooltip disabled={expanded}>\n <TooltipTrigger render={inner} />\n <TooltipContent side={dir === 'rtl' ? 'left' : 'right'}>\n {children}\n </TooltipContent>\n </Tooltip>\n </li>\n );\n }\n);\nSidebarPrimaryMenuItem.displayName = 'SidebarPrimaryMenuItem';\n\nexport interface SidebarPrimaryMenuItemExtrasProps\n extends React.ComponentPropsWithoutRef<'span'> {\n /** Which trailing affordance to render. */\n variant: 'tag' | 'externalLink' | 'shortcut' | 'tag-externalLink';\n /** Shortcut text (e.g. \"⌘H\") for the `shortcut` variant. */\n shortcut?: string;\n /**\n * Tag content for the `tag` / `tag-externalLink` variants. Figma constrains\n * this slot's `Tag` instance to `size=\"sm\"` (no other size is offered on\n * MenuItemExtras) — always pass a `<Tag variant=\"info\" size=\"sm\">`, never the\n * default size.\n */\n tag?: React.ReactNode;\n}\n\nconst SidebarPrimaryMenuItemExtras = React.forwardRef<\n HTMLSpanElement,\n SidebarPrimaryMenuItemExtrasProps\n>(({ className, variant, shortcut, tag, children, ...props }, ref) => {\n const { expanded } = useSidebarPrimaryContext();\n const showTag = variant === 'tag' || variant === 'tag-externalLink';\n const showExternal =\n variant === 'externalLink' || variant === 'tag-externalLink';\n const showShortcut = variant === 'shortcut';\n\n return (\n <span\n ref={ref}\n // The extras cluster is rail-mode noise — drop it when collapsed.\n className={cn(\n 'inline-flex items-center gap-[var(--ui-sidebar-primary-menu-item-extras-global-container-gap)]',\n !expanded && 'hidden',\n className\n )}\n {...props}\n >\n {showTag && (tag ?? children)}\n {showShortcut && (\n <span className=\"ui-sidebar-primary-menu-item-extras-global-shortcut-text-style text-[var(--ui-sidebar-primary-menu-item-extras-global-shortcut-color)]\">\n {shortcut ?? children}\n </span>\n )}\n {showExternal && (\n <SquareArrowUpRightIcon\n size={16}\n className=\"text-[var(--ui-sidebar-primary-menu-item-extras-global-external-icon-color)] size-[var(--ui-sidebar-primary-menu-item-extras-global-external-icon-size)]\"\n />\n )}\n </span>\n );\n});\nSidebarPrimaryMenuItemExtras.displayName = 'SidebarPrimaryMenuItemExtras';\n\nexport interface SidebarPrimaryCollapseTriggerProps\n extends Omit<React.ComponentPropsWithoutRef<'button'>, 'children'> {\n /** Leading 16px icon (e.g. a panel-left glyph). Rotates 180° between expanded/collapsed. */\n icon?: React.ReactNode;\n children?: React.ReactNode;\n /**\n * Trailing affordance — a `SidebarPrimaryMenuItemExtras` element (Figma\n * shows a `⌘H` shortcut here). Rendered as a flex sibling of the label, same\n * as `SidebarPrimaryMenuItem`'s `extras` prop.\n */\n extras?: React.ReactNode;\n /** Tooltip text shown when the rail is collapsed. Defaults to `'Expand'`. */\n expandTooltip?: React.ReactNode;\n}\n\n// The footer \"Collapse menu\" affordance (Figma). A menu-item-styled `<button>`\n// that flips the rail width via the layout context — this is the live wiring\n// for the controllable `expanded` state (B1). In collapsed mode it keeps its\n// label as `sr-only` like every other row, and (same as\n// `SidebarSecondaryCollapseTrigger`) wraps the whole button in a tooltip\n// showing `expandTooltip` — the `sr-only` label can't receive the hover that\n// would open its own tooltip.\nconst SidebarPrimaryCollapseTrigger = React.forwardRef<\n HTMLButtonElement,\n SidebarPrimaryCollapseTriggerProps\n>(\n (\n { className, icon, children, extras, expandTooltip = 'Expand', onClick, ...props },\n ref\n ) => {\n const { expanded, toggleExpanded } = useSidebarPrimaryContext();\n const dir = useDocDir();\n const buttonRef = React.useRef<HTMLButtonElement>(null);\n const labelRef = React.useRef<HTMLSpanElement>(null);\n const isOverflowing = useIsOverflowing(labelRef, { enabled: expanded });\n\n const button = (\n <button\n ref={(node) => {\n buttonRef.current = node;\n if (typeof ref === 'function') ref(node);\n else if (ref) ref.current = node;\n }}\n type=\"button\"\n aria-expanded={expanded}\n className={cn(\n sidebarPrimaryMenuItemVariants({ variant: 'unselected' }),\n 'text-start',\n className\n )}\n onClick={(event) => {\n onClick?.(event);\n if (!event.defaultPrevented) toggleExpanded();\n }}\n {...props}\n >\n {icon != null && (\n <span\n className={cn(\n 'flex shrink-0 items-center self-start mt-[var(--ui-sidebar-primary-menu-item-global-icon-margin-t)] transition-transform',\n expanded ? 'rtl:rotate-180' : 'ltr:rotate-180'\n )}\n >\n {icon}\n </span>\n )}\n <Tooltip disabled={!isOverflowing}>\n <TooltipTrigger\n render={\n <span\n ref={labelRef}\n className={cn(\n 'min-w-0 flex-1 truncate',\n !expanded && 'sr-only'\n )}\n />\n }\n >\n {children}\n </TooltipTrigger>\n <TooltipContent\n anchor={buttonRef}\n side={dir === 'rtl' ? 'left' : 'right'}\n >\n {children}\n </TooltipContent>\n </Tooltip>\n {extras != null && (\n <span className={cn(!expanded && 'hidden')}>{extras}</span>\n )}\n </button>\n );\n\n return (\n <li className=\"contents\">\n <Tooltip disabled={expanded}>\n <TooltipTrigger render={button} />\n <TooltipContent side={dir === 'rtl' ? 'left' : 'right'}>\n {expandTooltip}\n </TooltipContent>\n </Tooltip>\n </li>\n );\n }\n);\nSidebarPrimaryCollapseTrigger.displayName = 'SidebarPrimaryCollapseTrigger';\n\nexport {\n SidebarPrimary,\n SidebarPrimaryHeader,\n SidebarPrimaryContent,\n SidebarPrimaryFooter,\n SidebarPrimarySection,\n SidebarPrimaryMenu,\n SidebarPrimaryMenuItem,\n SidebarPrimaryMenuItemExtras,\n SidebarPrimaryCollapseTrigger,\n useIsOverflowing,\n sidebarPrimaryMenuItemVariants,\n};\n"],"mappings":"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"}
|
package/dist/llms/checkbox.md
CHANGED
|
@@ -79,12 +79,19 @@ them for a bare box, and name it with `aria-label`:
|
|
|
79
79
|
|
|
80
80
|
## States
|
|
81
81
|
|
|
82
|
-
| State | How | Visual
|
|
83
|
-
| ------------- | ----------------------------- |
|
|
84
|
-
| Unchecked | default | Idle border, no glyph
|
|
85
|
-
| Checked | `checked` / `default-checked` | Active fill + check glyph
|
|
86
|
-
| Indeterminate | `indeterminate` | Active fill + minus glyph, `mixed`
|
|
87
|
-
| Disabled | `disabled` | Disabled tokens; not interactive
|
|
82
|
+
| State | How | Visual |
|
|
83
|
+
| ------------- | ----------------------------- | ------------------------------------------------- |
|
|
84
|
+
| Unchecked | default | Idle border, no glyph |
|
|
85
|
+
| Checked | `checked` / `default-checked` | Active fill + check glyph |
|
|
86
|
+
| Indeterminate | `indeterminate` | Active fill + minus glyph, `mixed` |
|
|
87
|
+
| Disabled | `disabled` | Disabled tokens; not interactive |
|
|
88
|
+
| Read-only ⚠ | `read-only` | Still shows hover/active feedback; not toggleable |
|
|
89
|
+
| Required ⚠ | `required` | Unchanged visuals; `aria-required` |
|
|
90
|
+
|
|
91
|
+
⚠ **Known gap, not yet ratified:** neither state has a dedicated visual
|
|
92
|
+
affordance beyond its ARIA attribute — this is current behavior, not a
|
|
93
|
+
signed-off design decision. See the caveats in `behavior.md` (pending
|
|
94
|
+
design-lead sign-off before treated as final).
|
|
88
95
|
|
|
89
96
|
## Spec Files
|
|
90
97
|
|
|
@@ -106,6 +113,7 @@ them for a bare box, and name it with `aria-label`:
|
|
|
106
113
|
| `indeterminate` | `boolean` | `false` | no | Renders the mixed/indeterminate state (minus glyph, aria-checked="mixed"). Independent of `checked` — set by the consumer, not toggled by clicking. |
|
|
107
114
|
| `disabled` | `boolean` | `false` | no | Disables interaction; applies the disabled form tokens. |
|
|
108
115
|
| `required` | `boolean` | `false` | no | Marks the control as required in a form. |
|
|
116
|
+
| `read-only` | `boolean` | `false` | no | Prevents the user from changing the checked state; stays focusable. |
|
|
109
117
|
| `name` | `string` | — | no | Form field name submitted with the form. |
|
|
110
118
|
| `value` | `string` | — | no | Form field value when checked. |
|
|
111
119
|
| `label` | `ReactNode` | — | no | Optional label rendered beside the box. When provided, the whole row becomes a clickable `<label>` and the box is named via aria-labelledby. |
|
|
@@ -166,6 +174,21 @@ them for a bare box, and name it with `aria-label`:
|
|
|
166
174
|
**And** `checked-change` does not fire
|
|
167
175
|
**And** the disabled form tokens are applied
|
|
168
176
|
|
|
177
|
+
#### Read-only blocks the state change but not focus
|
|
178
|
+
|
|
179
|
+
**Given** a Checkbox with `read-only`
|
|
180
|
+
**When** the user clicks it (or presses Space while focused)
|
|
181
|
+
**Then** the state does not change
|
|
182
|
+
**And** `checked-change` does not fire
|
|
183
|
+
**And** the box remains focusable and keeps its normal (non-disabled) hover/
|
|
184
|
+
active/cursor styling — nothing in the current implementation visually
|
|
185
|
+
distinguishes it from an editable box beyond `aria-readonly="true"`, so a
|
|
186
|
+
consumer should pair it with copy that explains why it can't be changed
|
|
187
|
+
|
|
188
|
+
> Known gap, not yet ratified: the absence of a read-only visual affordance is
|
|
189
|
+
> the current behavior, not a signed-off design decision. It needs design-lead
|
|
190
|
+
> sign-off before it is treated as final.
|
|
191
|
+
|
|
169
192
|
---
|
|
170
193
|
|
|
171
194
|
### States
|
|
@@ -205,6 +228,24 @@ them for a bare box, and name it with `aria-label`:
|
|
|
205
228
|
**When** the form is submitted while checked
|
|
206
229
|
**Then** the field is included with its value
|
|
207
230
|
|
|
231
|
+
#### Required participates in form validation
|
|
232
|
+
|
|
233
|
+
**Given** a Checkbox with `required`
|
|
234
|
+
**When** it renders
|
|
235
|
+
**Then** `aria-required="true"` is set
|
|
236
|
+
**And** the hidden native input marks the field as required for submission
|
|
237
|
+
**And** the box itself renders no dedicated required-state token — nothing in
|
|
238
|
+
`packages/tokens-pd/css/Checkbox/*.css` distinguishes a required box from an
|
|
239
|
+
optional one beyond `aria-required`
|
|
240
|
+
|
|
241
|
+
> Known gap, not yet ratified: the absence of a required visual affordance is
|
|
242
|
+
> the current behavior, not a signed-off design decision. It needs
|
|
243
|
+
> design-lead sign-off before it is treated as final. Compare InputSelect,
|
|
244
|
+
> where `required` on `InputSelectLabel` renders a visual `*` marker
|
|
245
|
+
> (`--ui-input-select-global-required-color`) — design HAS modeled a required
|
|
246
|
+
> affordance for a sibling component, so Checkbox's lack of one is a gap, not
|
|
247
|
+
> a considered choice to omit it everywhere.
|
|
248
|
+
|
|
208
249
|
## Accessibility
|
|
209
250
|
|
|
210
251
|
Implements the [WAI-ARIA Checkbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/)
|
|
@@ -212,11 +253,19 @@ via Base UI's Checkbox primitive.
|
|
|
212
253
|
|
|
213
254
|
## ARIA Roles and Attributes
|
|
214
255
|
|
|
215
|
-
| Attribute | Value | Reason
|
|
216
|
-
| --------------- | ---------------------------- |
|
|
217
|
-
| `role` | `"checkbox"` | Identifies the control
|
|
218
|
-
| `aria-checked` | `"true" / "false" / "mixed"` | Reflects checked / unchecked / indeterminate
|
|
219
|
-
| `aria-disabled` | present when disabled | Conveyed via the disabled state
|
|
256
|
+
| Attribute | Value | Reason |
|
|
257
|
+
| --------------- | ---------------------------- | ----------------------------------------------- |
|
|
258
|
+
| `role` | `"checkbox"` | Identifies the control |
|
|
259
|
+
| `aria-checked` | `"true" / "false" / "mixed"` | Reflects checked / unchecked / indeterminate |
|
|
260
|
+
| `aria-disabled` | present when disabled | Conveyed via the disabled state |
|
|
261
|
+
| `aria-required` | present when `required` | Announces that the box must be ticked [^1] |
|
|
262
|
+
| `aria-readonly` | present when `read-only` | Announces that the state cannot be changed [^1] |
|
|
263
|
+
|
|
264
|
+
[^1]:
|
|
265
|
+
Known gap, not yet ratified: no dedicated sighted visual affordance
|
|
266
|
+
exists for this state beyond the ARIA attribute — this is current
|
|
267
|
+
behavior, not a signed-off design decision, pending design-lead sign-off
|
|
268
|
+
(see `behavior.md`).
|
|
220
269
|
|
|
221
270
|
The control must have an **accessible name** — provide a visible `<label>`
|
|
222
271
|
associated with it, or an `aria-label` / `aria-labelledby` when used standalone.
|
|
@@ -229,7 +278,9 @@ associated with it, or an `aria-label` / `aria-labelledby` when used standalone.
|
|
|
229
278
|
| Space | Toggles checked / unchecked |
|
|
230
279
|
|
|
231
280
|
The box is focusable in its enabled state and removed from the tab order when
|
|
232
|
-
disabled.
|
|
281
|
+
disabled. A **read-only** box stays focusable and in the tab order — Space
|
|
282
|
+
simply does not change its state, with no sighted visual cue distinguishing
|
|
283
|
+
it from an editable box (see the known-gap footnote above).
|
|
233
284
|
|
|
234
285
|
## Screen Reader Requirements
|
|
235
286
|
|
|
@@ -139,7 +139,22 @@ Render a menu item as a router link via the `render` prop:
|
|
|
139
139
|
|
|
140
140
|
The product logo is a **consumer-provided child** of the header — the component
|
|
141
141
|
does not ship a Logo part. The header just sizes whatever `img`/`svg` you slot
|
|
142
|
-
in (32px collapsed, 48px expanded) and tints it via the logo-color
|
|
142
|
+
in (32px collapsed, 48px expanded by default) and tints it via the logo-color
|
|
143
|
+
token. A consumer logo authored taller than that default (e.g. a
|
|
144
|
+
tenant-branded lockup) can override it in pixels via the `logoHeight`/
|
|
145
|
+
`collapsedLogoHeight` props instead of being force-downscaled to fit.
|
|
146
|
+
|
|
147
|
+
The two overrides are **not independent for header row height**. The header row
|
|
148
|
+
is pinned to `max(collapsed padding-y × 2 + collapsed logo height, expanded
|
|
149
|
+
padding-y × 2 + expanded logo height)` — unconditionally, across both rail
|
|
150
|
+
states — so the row can't jump while the two logo graphics swap. Each prop
|
|
151
|
+
sizes only its own state's logo, but whichever state yields the larger sum sets
|
|
152
|
+
the row height for both. With the defaults that's `max(16×2 + 32, 8×2 + 48)` =
|
|
153
|
+
64px; at `logoHeight={64}` it becomes `max(16×2 + 32, 8×2 + 64)` = 80px,
|
|
154
|
+
growing the _collapsed_ header too even though its 32px mark is unchanged. The
|
|
155
|
+
collapsed rail additionally caps the mark's width
|
|
156
|
+
at a 32px content box (48px width − 8px padding each side), so a taller
|
|
157
|
+
`collapsedLogo` should be authored portrait-or-narrower.
|
|
143
158
|
|
|
144
159
|
## Spec Files
|
|
145
160
|
|
|
@@ -168,6 +183,8 @@ in (32px collapsed, 48px expanded) and tints it via the logo-color token.
|
|
|
168
183
|
| `tag` | `ReactNode` | `undefined` | no | Tag content for the extras `tag` / `tag-externalLink` variants. |
|
|
169
184
|
| `logo` | `ReactNode` | `undefined` | no | Header content shown while the rail is expanded (e.g. a full brand lockup). Pairs with `collapsed-logo` to swap distinct graphics per rail state instead of resizing one. React: the `logo` prop on `SidebarPrimaryHeader`. |
|
|
170
185
|
| `collapsed-logo` | `ReactNode` | `undefined` | no | Header content shown while the rail is collapsed (e.g. a monogram mark). Falls back to `logo` when omitted. React: the `collapsedLogo` prop on `SidebarPrimaryHeader`. |
|
|
186
|
+
| `logo-height` | `number` | `undefined` | no | Overrides the expanded-state logo height in pixels, for a consumer logo asset authored taller than the design system's default 48px slot (e.g. a tenant-branded logo). Omit to use the design system default. Sizes only the expanded logo, but it can also change the *collapsed* header row height: the header row is pinned — in both rail states — to whichever state's (header padding-y × 2 + logo height) sum is larger, so that the row can't jump while the two logo graphics swap. With the defaults that is `max(16×2 + 32, 8×2 + 48)` = 64px; at `logo-height` 64 it becomes `max(16×2 + 32, 8×2 + 64)` = 80px in both states. React: the `logoHeight` prop on `SidebarPrimaryHeader`. |
|
|
187
|
+
| `collapsed-logo-height` | `number` | `undefined` | no | Overrides the collapsed-state logo height in pixels. Omit to use the design system default (32px). Sizes only the collapsed logo, but — symmetrically with `logo-height` — it can also change the *expanded* header row height, since the row is pinned in both states to the larger (header padding-y × 2 + logo height) sum. The collapsed logo is additionally width-capped by the collapsed rail's 32px content box (48px rail width − 8px header padding-x per side), so a taller collapsed logo should be authored portrait-or-narrower. React: the `collapsedLogoHeight` prop on `SidebarPrimaryHeader`. |
|
|
171
188
|
|
|
172
189
|
## Events
|
|
173
190
|
|
package/dist/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Acronis UI Kit for React — LLM Reference
|
|
2
2
|
|
|
3
|
-
> `@acronis-platform/ui-react` v5.
|
|
3
|
+
> `@acronis-platform/ui-react` v5.2.0
|
|
4
4
|
|
|
5
5
|
Machine-readable component reference compiled from framework-agnostic specs.
|
|
6
6
|
Each entry links to a self-contained markdown file with props, behavior,
|
|
@@ -39,6 +39,29 @@ export interface SidebarPrimaryHeaderProps extends React.ComponentPropsWithoutRe
|
|
|
39
39
|
* Falls back to `logo` when omitted.
|
|
40
40
|
*/
|
|
41
41
|
collapsedLogo?: React.ReactNode;
|
|
42
|
+
/**
|
|
43
|
+
* Overrides the expanded-state logo height in pixels, for a consumer logo
|
|
44
|
+
* asset authored taller than the design system's default 48px slot (e.g. a
|
|
45
|
+
* tenant-branded logo). Omit to use the design system default.
|
|
46
|
+
*
|
|
47
|
+
* Affects **both** rail states' header height: the header's row height is
|
|
48
|
+
* pinned to whichever state's (padding-y * 2 + logo height) sum is larger,
|
|
49
|
+
* so that the row doesn't visually jump during the collapse/expand
|
|
50
|
+
* transition. Raising this past the collapsed state's sum therefore grows
|
|
51
|
+
* the collapsed header row too, even though the collapsed logo itself is
|
|
52
|
+
* unchanged.
|
|
53
|
+
*/
|
|
54
|
+
logoHeight?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Overrides the collapsed-state logo height in pixels. Omit to use the
|
|
57
|
+
* design system default (32px).
|
|
58
|
+
*
|
|
59
|
+
* Affects **both** rail states' header height, for the same reason as
|
|
60
|
+
* `logoHeight`: the shared row height is the larger of the two states'
|
|
61
|
+
* (padding-y * 2 + logo height) sums, so raising this past the expanded
|
|
62
|
+
* state's sum grows the expanded header row as well.
|
|
63
|
+
*/
|
|
64
|
+
collapsedLogoHeight?: number;
|
|
42
65
|
}
|
|
43
66
|
declare const SidebarPrimaryHeader: React.ForwardRefExoticComponent<SidebarPrimaryHeaderProps & React.RefAttributes<HTMLDivElement>>;
|
|
44
67
|
declare const SidebarPrimaryContent: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|