@acronis-platform/ui-react 5.1.1 → 5.3.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/area-chart/area-chart.js +107 -107
- package/dist/components/ui/area-chart/area-chart.js.map +1 -1
- package/dist/components/ui/bar-chart/bar-chart.js +162 -162
- package/dist/components/ui/bar-chart/bar-chart.js.map +1 -1
- package/dist/components/ui/category-bar/category-bar.js +50 -50
- package/dist/components/ui/category-bar/category-bar.js.map +1 -1
- package/dist/components/ui/chart/chart.js +53 -40
- package/dist/components/ui/chart/chart.js.map +1 -1
- 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/composed-chart/composed-chart.js +146 -146
- package/dist/components/ui/composed-chart/composed-chart.js.map +1 -1
- package/dist/components/ui/confidence-cone/confidence-cone.js +97 -97
- package/dist/components/ui/confidence-cone/confidence-cone.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/funnel-chart/funnel-chart.js +120 -120
- package/dist/components/ui/funnel-chart/funnel-chart.js.map +1 -1
- package/dist/components/ui/histogram/histogram.js +58 -58
- package/dist/components/ui/histogram/histogram.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/line-chart/line-chart.js +91 -91
- package/dist/components/ui/line-chart/line-chart.js.map +1 -1
- package/dist/components/ui/number-field/number-field.js.map +1 -1
- package/dist/components/ui/pie-chart/pie-chart.js +92 -92
- package/dist/components/ui/pie-chart/pie-chart.js.map +1 -1
- package/dist/components/ui/radar-chart/radar-chart.js +37 -37
- package/dist/components/ui/radar-chart/radar-chart.js.map +1 -1
- package/dist/components/ui/radial-bar-chart/radial-bar-chart.js +112 -112
- package/dist/components/ui/radial-bar-chart/radial-bar-chart.js.map +1 -1
- package/dist/components/ui/sankey-chart/sankey-chart.js +78 -78
- package/dist/components/ui/sankey-chart/sankey-chart.js.map +1 -1
- package/dist/components/ui/scatter-chart/scatter-chart.js +57 -57
- package/dist/components/ui/scatter-chart/scatter-chart.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/components/ui/treemap/treemap.js +87 -87
- package/dist/components/ui/treemap/treemap.js.map +1 -1
- package/dist/index.js +111 -111
- package/dist/llms/checkbox.md +63 -12
- package/dist/llms/sidebar-primary.md +18 -1
- package/dist/llms.txt +1 -1
- package/dist/react.js +111 -111
- package/dist/src/components/ui/chart/chart.d.ts +1 -0
- package/dist/src/components/ui/chart/index.d.ts +1 -1
- package/dist/src/components/ui/radial-bar-chart/radial-bar-chart.d.ts +3 -2
- package/dist/src/components/ui/sankey-chart/sankey-chart.d.ts +1 -1
- package/dist/src/components/ui/sidebar-primary/sidebar-primary.d.ts +23 -0
- package/dist/src/components/ui/treemap/treemap.d.ts +3 -2
- 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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"}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn as e } from "../../../lib/utils.js";
|
|
3
|
-
import { ChartContainer as t, ChartLegendContent as n, ChartStyle as r, ChartTooltip as i, ChartTooltipContent as a, resolveChartColors as o } from "../chart/chart.js";
|
|
4
|
-
import { resolveAnimation as
|
|
5
|
-
import * as
|
|
6
|
-
import { jsx as
|
|
7
|
-
import { Treemap as
|
|
3
|
+
import { ChartContainer as t, ChartLegendContent as n, ChartStyle as r, ChartTooltip as i, ChartTooltipContent as a, resolveChartColors as o, toCssKey as s } from "../chart/chart.js";
|
|
4
|
+
import { resolveAnimation as c } from "../chart/chart-format.js";
|
|
5
|
+
import * as l from "react";
|
|
6
|
+
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
7
|
+
import { Treemap as f } from "recharts";
|
|
8
8
|
//#region src/components/ui/treemap/treemap.tsx
|
|
9
|
-
var
|
|
9
|
+
var p = {
|
|
10
10
|
type: "diverging",
|
|
11
11
|
pair: "blue-orange"
|
|
12
|
-
},
|
|
13
|
-
function
|
|
12
|
+
}, m = 2, h = 0, g = 64, _ = 43, v = 58;
|
|
13
|
+
function y(e) {
|
|
14
14
|
let { row: t, keys: n, separator: r, formatter: i } = e, a = n.flatMap((e, n) => {
|
|
15
15
|
let r = t[e];
|
|
16
16
|
if (r == null || r === "") return [];
|
|
@@ -19,127 +19,127 @@ function v(e) {
|
|
|
19
19
|
});
|
|
20
20
|
return a.length ? a.join(r) : void 0;
|
|
21
21
|
}
|
|
22
|
-
var
|
|
22
|
+
var b = {
|
|
23
23
|
"bottom-start": "justify-end text-start",
|
|
24
24
|
"top-start": "justify-start text-start",
|
|
25
25
|
center: "justify-center text-center"
|
|
26
26
|
};
|
|
27
|
-
function
|
|
28
|
-
if (!a) return /* @__PURE__ */
|
|
29
|
-
let
|
|
30
|
-
return /* @__PURE__ */
|
|
31
|
-
x:
|
|
32
|
-
y:
|
|
33
|
-
width:
|
|
34
|
-
height:
|
|
35
|
-
rx:
|
|
36
|
-
style: { fill: `var(--color-${a})` }
|
|
37
|
-
}),
|
|
38
|
-
x:
|
|
39
|
-
y:
|
|
40
|
-
width:
|
|
41
|
-
height:
|
|
27
|
+
function x({ x: t = 0, y: n = 0, width: r = 0, height: i = 0, name: a, primaryLabel: o, secondaryLabel: c, showLabels: l = !0, labelAlign: f = "center", fillTone: p = "dark" }) {
|
|
28
|
+
if (!a) return /* @__PURE__ */ u("g", {});
|
|
29
|
+
let y = t + m, x = n + m, S = Math.max(0, r - 4), C = Math.max(0, i - 4), w = l && S > g && C >= _, T = w && c && C >= v ? c : void 0;
|
|
30
|
+
return /* @__PURE__ */ d("g", { children: [/* @__PURE__ */ u("rect", {
|
|
31
|
+
x: y,
|
|
32
|
+
y: x,
|
|
33
|
+
width: S,
|
|
34
|
+
height: C,
|
|
35
|
+
rx: h,
|
|
36
|
+
style: { fill: `var(--color-${s(a)})` }
|
|
37
|
+
}), w && /* @__PURE__ */ u("foreignObject", {
|
|
38
|
+
x: y,
|
|
39
|
+
y: x,
|
|
40
|
+
width: S,
|
|
41
|
+
height: C,
|
|
42
42
|
className: "pointer-events-none",
|
|
43
|
-
children: /* @__PURE__ */
|
|
44
|
-
className: e("flex size-full flex-col overflow-hidden p-3",
|
|
45
|
-
children: [/* @__PURE__ */
|
|
43
|
+
children: /* @__PURE__ */ d("div", {
|
|
44
|
+
className: e("flex size-full flex-col overflow-hidden p-3", p === "pale" ? "text-[var(--ui-text-on-surface-primary)]" : p === "inverts" ? "text-[var(--ui-text-on-status-strong-primary)]" : "text-[var(--ui-text-on-status-strong-neutral)]", b[f]),
|
|
45
|
+
children: [/* @__PURE__ */ u("span", {
|
|
46
46
|
className: "truncate text-sm",
|
|
47
47
|
children: o ?? a
|
|
48
|
-
}),
|
|
48
|
+
}), T && /* @__PURE__ */ u("span", {
|
|
49
49
|
className: "truncate text-[11px]",
|
|
50
|
-
children:
|
|
50
|
+
children: T
|
|
51
51
|
})]
|
|
52
52
|
})
|
|
53
53
|
})] });
|
|
54
54
|
}
|
|
55
|
-
var
|
|
56
|
-
let
|
|
57
|
-
animate:
|
|
58
|
-
animationDuration:
|
|
59
|
-
animationBegin:
|
|
60
|
-
animationEasing:
|
|
61
|
-
}),
|
|
62
|
-
let e = o(
|
|
63
|
-
return
|
|
64
|
-
let n = String(t[
|
|
55
|
+
var S = l.forwardRef(({ className: m, config: h, palette: g = p, data: _, dataKey: v, nameKey: b, aspectRatio: S = 4 / 3, showLabels: C = !0, labelAlign: w = "center", secondaryKeys: T, secondaryFormatter: E, secondarySeparator: D = " · ", showTooltip: O = !0, showLegend: k = !1, legendPos: A = "bottom", tooltipContent: j, animate: M, animationDuration: N, animationBegin: P, animationEasing: F, ...I }, L) => {
|
|
56
|
+
let R = c({
|
|
57
|
+
animate: M,
|
|
58
|
+
animationDuration: N,
|
|
59
|
+
animationBegin: P,
|
|
60
|
+
animationEasing: F
|
|
61
|
+
}), z = l.useMemo(() => {
|
|
62
|
+
let e = o(h, g);
|
|
63
|
+
return _.map((t) => {
|
|
64
|
+
let n = String(t[b]), r = T?.length ? y({
|
|
65
65
|
row: t,
|
|
66
|
-
keys:
|
|
67
|
-
separator:
|
|
68
|
-
formatter:
|
|
69
|
-
}) : void 0, i =
|
|
66
|
+
keys: T,
|
|
67
|
+
separator: D,
|
|
68
|
+
formatter: E
|
|
69
|
+
}) : void 0, i = h[n]?.label, a = (e[n]?.color ?? "").replace(/^var\(|\)$/g, ""), o = /-(?:a[12]|b[12]|sequential-[a-z]+-[12])$/.test(a), c = /-sequential-[a-z]+-[78]$/.test(a), l = o ? "pale" : c ? "inverts" : "dark";
|
|
70
70
|
return {
|
|
71
71
|
...t,
|
|
72
|
-
fill: `var(--color-${n})`,
|
|
73
|
-
fillTone:
|
|
72
|
+
fill: `var(--color-${s(n)})`,
|
|
73
|
+
fillTone: l,
|
|
74
74
|
...typeof i == "string" ? { primaryLabel: i } : {},
|
|
75
75
|
...r ? { secondaryLabel: r } : {}
|
|
76
76
|
};
|
|
77
77
|
});
|
|
78
78
|
}, [
|
|
79
|
-
m,
|
|
80
|
-
g,
|
|
81
|
-
y,
|
|
82
79
|
h,
|
|
80
|
+
_,
|
|
81
|
+
b,
|
|
82
|
+
g,
|
|
83
|
+
E,
|
|
83
84
|
T,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
]), z = c.useMemo(() => {
|
|
85
|
+
D
|
|
86
|
+
]), B = l.useMemo(() => {
|
|
87
87
|
let e = /* @__PURE__ */ new Set();
|
|
88
|
-
return
|
|
89
|
-
let n = String(t[
|
|
88
|
+
return z.flatMap((t) => {
|
|
89
|
+
let n = String(t[b]);
|
|
90
90
|
return e.has(n) ? [] : (e.add(n), [{
|
|
91
91
|
value: n,
|
|
92
|
-
dataKey:
|
|
92
|
+
dataKey: b,
|
|
93
93
|
type: "rect",
|
|
94
94
|
color: String(t.fill),
|
|
95
95
|
payload: t
|
|
96
96
|
}]);
|
|
97
97
|
});
|
|
98
|
-
}, [
|
|
99
|
-
"data-chart":
|
|
98
|
+
}, [b, z]), V = `chart-${l.useId().replace(/:/g, "")}`, H = k ? /* @__PURE__ */ d("div", {
|
|
99
|
+
"data-chart": V,
|
|
100
100
|
className: "text-xs",
|
|
101
|
-
children: [/* @__PURE__ */
|
|
102
|
-
id:
|
|
103
|
-
config: o(
|
|
104
|
-
}), /* @__PURE__ */
|
|
105
|
-
config:
|
|
106
|
-
payload:
|
|
107
|
-
verticalAlign:
|
|
108
|
-
nameKey:
|
|
101
|
+
children: [/* @__PURE__ */ u(r, {
|
|
102
|
+
id: V,
|
|
103
|
+
config: o(h, g)
|
|
104
|
+
}), /* @__PURE__ */ u(n, {
|
|
105
|
+
config: h,
|
|
106
|
+
payload: B,
|
|
107
|
+
verticalAlign: A,
|
|
108
|
+
nameKey: b
|
|
109
109
|
})]
|
|
110
110
|
}) : null;
|
|
111
|
-
return /* @__PURE__ */
|
|
112
|
-
ref:
|
|
113
|
-
className: e(
|
|
114
|
-
...
|
|
111
|
+
return /* @__PURE__ */ d("div", {
|
|
112
|
+
ref: L,
|
|
113
|
+
className: e(k && "flex flex-col", m),
|
|
114
|
+
...I,
|
|
115
115
|
children: [
|
|
116
|
-
|
|
117
|
-
/* @__PURE__ */
|
|
118
|
-
config:
|
|
119
|
-
palette:
|
|
120
|
-
className:
|
|
121
|
-
children: /* @__PURE__ */
|
|
122
|
-
data:
|
|
123
|
-
dataKey:
|
|
124
|
-
nameKey:
|
|
125
|
-
aspectRatio:
|
|
126
|
-
...
|
|
127
|
-
content: /* @__PURE__ */
|
|
128
|
-
showLabels:
|
|
129
|
-
labelAlign:
|
|
116
|
+
A === "top" && H,
|
|
117
|
+
/* @__PURE__ */ u(t, {
|
|
118
|
+
config: h,
|
|
119
|
+
palette: g,
|
|
120
|
+
className: k ? "min-h-0 w-full flex-1" : "size-full",
|
|
121
|
+
children: /* @__PURE__ */ u(f, {
|
|
122
|
+
data: z,
|
|
123
|
+
dataKey: v,
|
|
124
|
+
nameKey: b,
|
|
125
|
+
aspectRatio: S,
|
|
126
|
+
...R,
|
|
127
|
+
content: /* @__PURE__ */ u(x, {
|
|
128
|
+
showLabels: C,
|
|
129
|
+
labelAlign: w
|
|
130
130
|
}),
|
|
131
|
-
children:
|
|
132
|
-
nameKey:
|
|
131
|
+
children: O && /* @__PURE__ */ u(i, { content: j ?? /* @__PURE__ */ u(a, {
|
|
132
|
+
nameKey: b,
|
|
133
133
|
hideLabel: !0
|
|
134
134
|
}) })
|
|
135
135
|
})
|
|
136
136
|
}),
|
|
137
|
-
|
|
137
|
+
A === "bottom" && H
|
|
138
138
|
]
|
|
139
139
|
});
|
|
140
140
|
});
|
|
141
|
-
|
|
141
|
+
S.displayName = "Treemap";
|
|
142
142
|
//#endregion
|
|
143
|
-
export {
|
|
143
|
+
export { S as Treemap, x as TreemapCell, y as treemapSecondaryLabel };
|
|
144
144
|
|
|
145
145
|
//# sourceMappingURL=treemap.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"treemap.js","names":[],"sources":["../../../../src/components/ui/treemap/treemap.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { Treemap as RechartsTreemap } from 'recharts';\nimport type { LegendPayload } from 'recharts/types/component/DefaultLegendContent';\n\nimport { cn } from '@/lib/utils';\nimport {\n ChartContainer,\n ChartLegendContent,\n ChartStyle,\n resolveChartColors,\n ChartTooltip,\n ChartTooltipContent,\n resolveAnimation,\n type ChartConfig,\n type ChartPalette,\n type ChartAnimationProps,\n type TickFormatter,\n} from '../chart';\n\n// Treemap-specific default: the diverging palette shows the two-sided distribution\n// that a treemap is most commonly used for, and its adaptive text (auto-computed\n// from resolved token names below) works correctly out of the box.\nconst TREEMAP_DEFAULT_PALETTE: ChartPalette = { type: 'diverging', pair: 'blue-orange' };\n\n// A typed recharts composition over the shared `Chart` primitives. A treemap is\n// the odd one out: a single `Treemap` element with no axes/grid, themed through a\n// custom `content` cell renderer (recharts' default paints every cell the same\n// fill and has no token hooks) — and that renderer, not a `LabelList`, owns the\n// on-cell labels, so the fit/clamp logic lives here. Like Scatter/Composed/\n// RadialBar there's no CVA variant — a treemap's knobs are geometry\n// (`aspectRatio`) and the data, not a visual \"mode\". v1 is a flat treemap (a list\n// of leaves).\n\n/**\n * Where a cell's label block sits inside its rectangle. Named logically, not\n * physically: the corner variants anchor to the tile's *start* edge, so they\n * mirror under `dir=\"rtl\"`. `bottom-start` is the design's placement; `center`\n * is what the chart drew before the block had a second line.\n */\nexport type TreemapLabelAlign = 'bottom-start' | 'top-start' | 'center';\n\n// Cell shape: each tile is inset by the gap on every side, so neighbouring tiles\n// are separated by the surface showing through rather than by a stroke, and the\n// corners are rounded.\nconst CELL_GAP = 2;\nconst CELL_RADIUS = 0;\n\n// Label geometry, in px. The block's own layout is CSS (see the renderer); these\n// are the heights the tile has to clear for a label to fit *without being\n// clipped* — one line, or two.\n//\n// Line boxes, not font sizes: Tailwind's `--text-sm--line-height` is 4/3, so the\n// `text-sm` title renders a ~19px line box, and the second line inherits that same\n// ratio (an arbitrary `text-[11px]` sets font-size only) for ~14.7px. Measuring by\n// font size instead would let a tile pass the threshold and still have the block's\n// `overflow-hidden` eat the top of the first line.\nconst CELL_PADDING = 12;\nconst LINE_HEIGHT_RATIO = 4 / 3;\nconst TITLE_FONT_SIZE = 14; // text-sm\nconst SECONDARY_FONT_SIZE = 11;\nconst TITLE_LINE_HEIGHT = Math.ceil(TITLE_FONT_SIZE * LINE_HEIGHT_RATIO);\nconst SECONDARY_LINE_HEIGHT = Math.ceil(\n SECONDARY_FONT_SIZE * LINE_HEIGHT_RATIO\n);\n\n// The smallest tile that gets a label at all, and the height a second line\n// additionally needs. Below the first threshold a cell stays blank rather than\n// showing a stub — the label would be wider than the tile it names.\nconst MIN_LABEL_WIDTH = 64;\nconst MIN_LABEL_HEIGHT = CELL_PADDING * 2 + TITLE_LINE_HEIGHT;\nconst MIN_TWO_LINE_HEIGHT = MIN_LABEL_HEIGHT + SECONDARY_LINE_HEIGHT;\n\n/**\n * Compose a cell's second line out of the caller's `secondaryKeys`. Empty and\n * missing fields are skipped rather than printed as a gap, so one row without\n * the field doesn't leave a dangling separator.\n */\nexport function treemapSecondaryLabel(options: {\n row: Record<string, string | number>;\n keys: readonly string[];\n separator: string;\n formatter?: TickFormatter;\n}): string | undefined {\n const { row, keys, separator, formatter } = options;\n const parts = keys.flatMap((key, index) => {\n const value = row[key];\n if (value == null || value === '') return [];\n // Emptiness is re-checked *after* formatting, not just on the raw value: a\n // formatter that blanks a field it doesn't want to show (a zero, a sentinel)\n // would otherwise contribute an empty part and put back the dangling\n // separator this function exists to avoid.\n const part = formatter ? formatter(value, index) : String(value);\n return part === '' ? [] : [part];\n });\n return parts.length ? parts.join(separator) : undefined;\n}\n\ninterface TreemapCellProps {\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n // recharts injects the node's resolved `name` (from nameKey) + value, plus\n // every other field on the row — which is how the two stamped labels arrive.\n name?: string;\n /** The leaf's display name — its `config` label, stamped onto the row by `Treemap`. */\n primaryLabel?: string;\n /** The label's precomputed second line, stamped onto each row by `Treemap`. */\n secondaryLabel?: string;\n showLabels?: boolean;\n labelAlign?: TreemapLabelAlign;\n /**\n * How the leaf's fill relates to text contrast. Stamped by `Treemap` onto each\n * row before recharts hands it to the cell renderer. Auto-computed from the\n * resolved token name suffix:\n *\n * - `'dark'` — saturated/dark in both themes → always white text.\n * - `'pale'` — pale in both themes → always dark text (via theme-adaptive token).\n * - `'inverts'` — dark in light, pale in dark (sequential stops 7–8 whose\n * `light-dark()` values mirror across themes) → white text in light, dark text\n * in dark, achieved by a `light-dark()` color value in CSS.\n */\n fillTone?: 'dark' | 'pale' | 'inverts';\n}\n\n// How the label block sits in its tile. `text-start`/`text-center` and the\n// block-axis `justify-*` are what make the corner-anchored variants mirror under\n// `dir=\"rtl\"` — the same logical utilities every other component uses, which is\n// why the label is HTML in a `foreignObject` rather than SVG `<text>`:\n// `x`/`text-anchor` are physical, so a mirrored label would need the direction\n// read in JS and applied by hand.\n//\n// Every variant leaves the cross axis on the default `items-stretch`, including\n// `center`. Centering the line is `text-center`'s job, not `items-center`'s:\n// `items-center` would size each line to its own text instead of to the tile, and\n// a `truncate` line only ellipsizes when it is the *tile* that constrains it — a\n// shrink-to-fit line is never narrower than its text, so it would silently\n// overflow and be hard-clipped on both edges instead.\nconst LABEL_ALIGN_CLASS: Record<TreemapLabelAlign, string> = {\n 'bottom-start': 'justify-end text-start',\n 'top-start': 'justify-start text-start',\n center: 'justify-center text-center',\n};\n\n// Cell renderer: fill each rounded rect from its `--color-<name>` var, inset it so\n// the surface shows through between tiles, and lay the label block over it — the\n// leaf's name, plus the stamped second line when the tile is tall enough. Passed to\n// `Treemap.content`; recharts clones it with each node's geometry + row fields.\nexport function TreemapCell({\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n name,\n primaryLabel,\n secondaryLabel,\n showLabels = true,\n labelAlign = 'center',\n fillTone = 'dark',\n}: TreemapCellProps) {\n // recharts invokes `content` for the synthetic root node too (full chart\n // dimensions, empty name). Skip any name-less node — otherwise its rect has no\n // `--color-<name>` fill and paints the SVG-default black behind everything\n // (invisible when leaves cover it, a black box on empty data).\n if (!name) return <g />;\n // The tile, not the node: the label sits inside the inset rect, so the gutter\n // never eats into its padding.\n const tileX = x + CELL_GAP;\n const tileY = y + CELL_GAP;\n const tileWidth = Math.max(0, width - CELL_GAP * 2);\n const tileHeight = Math.max(0, height - CELL_GAP * 2);\n\n // `>=` on the heights: they are exact fits (padding + line boxes), so a tile\n // that measures one of them exactly has room for that label. The width is a\n // judgement call about how narrow a label is still worth reading, not a fit, so\n // it stays a strict `>`.\n const canLabel =\n showLabels && tileWidth > MIN_LABEL_WIDTH && tileHeight >= MIN_LABEL_HEIGHT;\n const secondary =\n canLabel && secondaryLabel && tileHeight >= MIN_TWO_LINE_HEIGHT\n ? secondaryLabel\n : undefined;\n\n return (\n <g>\n <rect\n x={tileX}\n y={tileY}\n width={tileWidth}\n height={tileHeight}\n rx={CELL_RADIUS}\n style={{ fill: `var(--color-${name})` }}\n />\n {canLabel && (\n // Decorative: the hover that opens the tooltip belongs to the tile, so the\n // label must not swallow it.\n <foreignObject\n x={tileX}\n y={tileY}\n width={tileWidth}\n height={tileHeight}\n className=\"pointer-events-none\"\n >\n <div\n className={cn(\n // Each line truncates with a real ellipsis — CSS measures the text,\n // so nothing has to estimate how much of it fits.\n 'flex size-full flex-col overflow-hidden p-3',\n // Adaptive text — three tones:\n // • dark: saturated in both themes → white text (on-strong-neutral).\n // • pale: pale in both themes → on-surface-primary (dark↔light).\n // • inverts: dark in light, pale in dark (sequential 7–8 whose\n // light-dark() values mirror) → on-strong-primary, which is\n // light-dark(near-white, near-black) — the exact inverse.\n fillTone === 'pale'\n ? 'text-[var(--ui-text-on-surface-primary)]'\n : fillTone === 'inverts'\n ? 'text-[var(--ui-text-on-status-strong-primary)]'\n : 'text-[var(--ui-text-on-status-strong-neutral)]',\n LABEL_ALIGN_CLASS[labelAlign]\n )}\n >\n <span className=\"truncate text-sm\">\n {primaryLabel ?? name}\n </span>\n {secondary && (\n <span className=\"truncate text-[11px]\">{secondary}</span>\n )}\n </div>\n </foreignObject>\n )}\n </g>\n );\n}\n\nexport interface TreemapProps\n extends Omit<React.ComponentProps<'div'>, 'children'>, ChartAnimationProps {\n /**\n * The dataviz palette this chart's series are painted from. Series that\n * state no `color` of their own take a stop of it. See `ChartPalette`.\n */\n palette?: ChartPalette;\n /**\n * Row-per-leaf data. Each object holds the leaf's `nameKey` label + its\n * `dataKey` numeric size.\n *\n * `fill`, `primaryLabel` and `secondaryLabel` are reserved: the chart stamps\n * them onto a copy of each row to carry the cell color and the two label lines\n * through to the renderer (recharts hands the whole row to `content`, so\n * anything the cell needs has to be on it). A row of your own carrying one of\n * those names may end up driving what the tile paints.\n */\n data: ReadonlyArray<Record<string, string | number>>;\n /**\n * Per-leaf map of `label` / `icon` / `tone`, keyed by the leaf's `nameKey`\n * value (imported from the shared `Chart` primitives). Turned into\n * `--color-<name>` custom properties. Series take their colour from the\n * container's `palette`; each entry maps a key to a `label` and an optional\n * `tone`.\n */\n config: ChartConfig;\n /** Numeric field that sizes each leaf's rectangle. */\n dataKey: string;\n /**\n * Label field that names each leaf (drives the on-cell label, legend, tooltip,\n * and `--color-<name>` lookup). Values should be unique and CSS-safe (they\n * become part of a custom-property name).\n */\n nameKey: string;\n /** Width-to-height ratio the tiling targets. */\n aspectRatio?: number;\n /** Render each leaf's label inside its cell (when it fits). */\n showLabels?: boolean;\n /**\n * Where a cell's label sits. Defaults to `center` — the Figma-canonical placement.\n * `bottom-start` anchors the block to the tile's bottom start corner; `top-start`\n * to the top start corner. The corner variants are named for the tile's *start* edge\n * because they mirror under `dir=\"rtl\"`.\n */\n labelAlign?: TreemapLabelAlign;\n /**\n * Fields whose values make up a second label line under the leaf's name — e.g.\n * `[dataKey]` for the size, or `['size', 'count']` for a value and a count. The\n * line is dropped on cells too short to hold it.\n */\n secondaryKeys?: readonly string[];\n /**\n * Format each secondary value. Receives the field's index in `secondaryKeys`\n * as its second argument, so one formatter can cover fields of different kinds.\n */\n secondaryFormatter?: TickFormatter;\n /** Separator drawn between multiple secondary values. */\n secondarySeparator?: string;\n showTooltip?: boolean;\n /**\n * Render the legend — one entry per leaf. Off by default: a treemap labels its\n * cells, so the legend is for the case where the tiles are too small to.\n */\n showLegend?: boolean;\n /** Which edge the legend sits on. */\n legendPos?: 'top' | 'bottom';\n /**\n * Replace the default tooltip. Pass a configured `ChartTooltipContent`\n * (imported from this library) — e.g. with a `formatter` / `labelFormatter` —\n * to customize formatting, per-cell rows, or extra fields without composing\n * recharts yourself. Ignored when `showTooltip` is false.\n */\n tooltipContent?: React.ComponentProps<typeof ChartTooltip>['content'];\n}\n\nconst Treemap = React.forwardRef<HTMLDivElement, TreemapProps>(\n (\n {\n className,\n config,\n palette = TREEMAP_DEFAULT_PALETTE,\n data,\n dataKey,\n nameKey,\n aspectRatio = 4 / 3,\n showLabels = true,\n labelAlign = 'center',\n secondaryKeys,\n secondaryFormatter,\n secondarySeparator = ' · ',\n showTooltip = true,\n showLegend = false,\n legendPos = 'bottom',\n tooltipContent,\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n ...props\n },\n ref\n ) => {\n const animation = resolveAnimation({\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n });\n // Stamp each row with its `fill` (the shadcn data-driven pattern) so a real\n // hover resolves the cell color in the tooltip — recharts' Treemap carries no\n // per-cell color on the tooltip payload item — and with its `secondaryLabel`,\n // which reaches the cell renderer the same way: recharts hands the whole row\n // to `content`, so a composed label has to be on the row before it gets there.\n const seriesData: Record<string, string | number | boolean>[] =\n React.useMemo(\n () => {\n // Auto fill-tone detection: resolve each leaf's token name and classify\n // it by suffix. Token values are light-dark() pairs, so a runtime\n // luminance check would be theme-fragile — the suffix is stable.\n const resolvedColors = resolveChartColors(config, palette);\n return data.map((row) => {\n const name = String(row[nameKey]);\n const secondaryLabel = secondaryKeys?.length\n ? treemapSecondaryLabel({\n row,\n keys: secondaryKeys,\n separator: secondarySeparator,\n formatter: secondaryFormatter,\n })\n : undefined;\n // The on-cell name is the leaf's `config` label, so a cell reads like its\n // legend entry and tooltip row do. It matters more here than elsewhere:\n // the raw `nameKey` value has to be CSS-safe (it becomes part of\n // `--color-<name>`), so a leaf whose display name has a space in it is\n // keyed by a slug — and the slug is not what belongs on the tile. Only a\n // string label can go in SVG text; a `ReactNode` one falls back to the key.\n const label = config[name]?.label;\n const rawToken = (resolvedColors[name]?.color ?? '').replace(\n /^var\\(|\\)$/g,\n ''\n );\n // Diverging: a1/a2/b1/b2 are pale; a3/b3 are dark.\n // Sequential: stops 1–2 are pale (in light) / dark (in dark); stops\n // 7–8 invert (dark in light, pale in dark). Stops 3–6 stay dark in\n // both themes.\n const isPale = /-(?:a[12]|b[12]|sequential-[a-z]+-[12])$/.test(rawToken);\n const inverts = /-sequential-[a-z]+-[78]$/.test(rawToken);\n const fillTone: 'dark' | 'pale' | 'inverts' = isPale\n ? 'pale'\n : inverts\n ? 'inverts'\n : 'dark';\n return {\n ...row,\n fill: `var(--color-${name})`,\n fillTone,\n ...(typeof label === 'string' ? { primaryLabel: label } : {}),\n ...(secondaryLabel ? { secondaryLabel } : {}),\n };\n });\n },\n [\n config,\n data,\n nameKey,\n palette,\n secondaryFormatter,\n secondaryKeys,\n secondarySeparator,\n ]\n );\n\n // recharts 3 builds the legend payload from the graphical item, and `Treemap`\n // — unlike Bar/Line/Area/Pie/Radar/RadialBar/Scatter — never registers one,\n // so a `<Legend>` inside a `Treemap` renders empty. The payload is synthesized\n // from the leaves instead and handed to the shared `ChartLegendContent`, which\n // keeps the treemap on the same legend markers, labels and `config` lookup as\n // every other chart. `payload` carries the row so each entry resolves its own\n // `config` entry via `nameKey`.\n //\n // One entry per *distinct* leaf name: same-named leaves deliberately share a\n // `--color-<name>`/`config` entry, so a second entry would repeat the first\n // verbatim — and `ChartLegendContent` keys its entries on `value`, so it would\n // also be a duplicate React key.\n const legendPayload = React.useMemo<LegendPayload[]>(() => {\n const seen = new Set<string>();\n return seriesData.flatMap((row) => {\n const value = String(row[nameKey]);\n if (seen.has(value)) return [];\n seen.add(value);\n return [\n {\n value,\n dataKey: nameKey,\n type: 'rect' as const,\n color: String(row.fill),\n payload: row,\n },\n ];\n });\n }, [nameKey, seriesData]);\n\n // Every other chart type lets recharts shrink the plot area by the legend's\n // measured size, but recharts' `Treemap` tiles the *full* chart surface and\n // ignores the legend entirely — a `<Legend>` inside one paints over the tiles.\n // So the legend is not a `<Legend>` at all: the shared `ChartLegendContent` is\n // rendered straight into a row of its own beside the plot, on the same markers,\n // labels and `config` lookup as every other chart's legend, and normal flow\n // gives it the height it needs (one row, or several once it wraps) and takes\n // that height off the tiled surface.\n //\n // Rendering it here rather than inside the plot is also what keeps the tiling\n // correct: recharts' `Treemap` reads its container **once** (it opts out of the\n // responsive path, because its own stroke-width rounding makes that loop), and a\n // `<Legend>` can only render after the chart has a size — so the box it read\n // would always be the one from before its own legend existed, a legend-row too\n // tall, and the bottom row of tiles would be laid out under the clip. The row is\n // its final height on the first render instead, so that one read is the right one.\n //\n // The row sits outside `ChartContainer`, so it takes `config` as a prop (the\n // container's context doesn't reach it) and re-emits the `--color-<name>`\n // properties its swatches resolve against under its own `data-chart` id.\n const legendChartId = `chart-${React.useId().replace(/:/g, '')}`;\n const legendRow = showLegend ? (\n <div data-chart={legendChartId} className=\"text-xs\">\n <ChartStyle\n id={legendChartId}\n config={resolveChartColors(config, palette)}\n />\n <ChartLegendContent\n config={config}\n payload={legendPayload}\n verticalAlign={legendPos}\n nameKey={nameKey}\n />\n </div>\n ) : null;\n\n return (\n <div\n ref={ref}\n className={cn(showLegend && 'flex flex-col', className)}\n {...props}\n >\n {legendPos === 'top' && legendRow}\n <ChartContainer\n config={config}\n palette={palette}\n // `size-full` fills the caller's box; with a legend row beside it the\n // height comes from the flex line instead, so the tiles give up exactly\n // the room the legend takes.\n className={showLegend ? 'min-h-0 w-full flex-1' : 'size-full'}\n >\n <RechartsTreemap\n data={seriesData}\n dataKey={dataKey}\n nameKey={nameKey}\n aspectRatio={aspectRatio}\n {...animation}\n content={\n <TreemapCell showLabels={showLabels} labelAlign={labelAlign} />\n }\n >\n {showTooltip && (\n <ChartTooltip\n content={\n tooltipContent ?? (\n <ChartTooltipContent nameKey={nameKey} hideLabel />\n )\n }\n />\n )}\n </RechartsTreemap>\n </ChartContainer>\n {legendPos === 'bottom' && legendRow}\n </div>\n );\n }\n);\nTreemap.displayName = 'Treemap';\n\nexport { Treemap };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"treemap.js","names":[],"sources":["../../../../src/components/ui/treemap/treemap.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { Treemap as RechartsTreemap } from 'recharts';\nimport type { LegendPayload } from 'recharts/types/component/DefaultLegendContent';\n\nimport { cn } from '@/lib/utils';\nimport {\n ChartContainer,\n ChartLegendContent,\n ChartStyle,\n resolveChartColors,\n ChartTooltip,\n ChartTooltipContent,\n resolveAnimation,\n toCssKey,\n type ChartConfig,\n type ChartPalette,\n type ChartAnimationProps,\n type TickFormatter,\n} from '../chart';\n\n// Treemap-specific default: the diverging palette shows the two-sided distribution\n// that a treemap is most commonly used for, and its adaptive text (auto-computed\n// from resolved token names below) works correctly out of the box.\nconst TREEMAP_DEFAULT_PALETTE: ChartPalette = { type: 'diverging', pair: 'blue-orange' };\n\n// A typed recharts composition over the shared `Chart` primitives. A treemap is\n// the odd one out: a single `Treemap` element with no axes/grid, themed through a\n// custom `content` cell renderer (recharts' default paints every cell the same\n// fill and has no token hooks) — and that renderer, not a `LabelList`, owns the\n// on-cell labels, so the fit/clamp logic lives here. Like Scatter/Composed/\n// RadialBar there's no CVA variant — a treemap's knobs are geometry\n// (`aspectRatio`) and the data, not a visual \"mode\". v1 is a flat treemap (a list\n// of leaves).\n\n/**\n * Where a cell's label block sits inside its rectangle. Named logically, not\n * physically: the corner variants anchor to the tile's *start* edge, so they\n * mirror under `dir=\"rtl\"`. `bottom-start` is the design's placement; `center`\n * is what the chart drew before the block had a second line.\n */\nexport type TreemapLabelAlign = 'bottom-start' | 'top-start' | 'center';\n\n// Cell shape: each tile is inset by the gap on every side, so neighbouring tiles\n// are separated by the surface showing through rather than by a stroke, and the\n// corners are rounded.\nconst CELL_GAP = 2;\nconst CELL_RADIUS = 0;\n\n// Label geometry, in px. The block's own layout is CSS (see the renderer); these\n// are the heights the tile has to clear for a label to fit *without being\n// clipped* — one line, or two.\n//\n// Line boxes, not font sizes: Tailwind's `--text-sm--line-height` is 4/3, so the\n// `text-sm` title renders a ~19px line box, and the second line inherits that same\n// ratio (an arbitrary `text-[11px]` sets font-size only) for ~14.7px. Measuring by\n// font size instead would let a tile pass the threshold and still have the block's\n// `overflow-hidden` eat the top of the first line.\nconst CELL_PADDING = 12;\nconst LINE_HEIGHT_RATIO = 4 / 3;\nconst TITLE_FONT_SIZE = 14; // text-sm\nconst SECONDARY_FONT_SIZE = 11;\nconst TITLE_LINE_HEIGHT = Math.ceil(TITLE_FONT_SIZE * LINE_HEIGHT_RATIO);\nconst SECONDARY_LINE_HEIGHT = Math.ceil(\n SECONDARY_FONT_SIZE * LINE_HEIGHT_RATIO\n);\n\n// The smallest tile that gets a label at all, and the height a second line\n// additionally needs. Below the first threshold a cell stays blank rather than\n// showing a stub — the label would be wider than the tile it names.\nconst MIN_LABEL_WIDTH = 64;\nconst MIN_LABEL_HEIGHT = CELL_PADDING * 2 + TITLE_LINE_HEIGHT;\nconst MIN_TWO_LINE_HEIGHT = MIN_LABEL_HEIGHT + SECONDARY_LINE_HEIGHT;\n\n/**\n * Compose a cell's second line out of the caller's `secondaryKeys`. Empty and\n * missing fields are skipped rather than printed as a gap, so one row without\n * the field doesn't leave a dangling separator.\n */\nexport function treemapSecondaryLabel(options: {\n row: Record<string, string | number>;\n keys: readonly string[];\n separator: string;\n formatter?: TickFormatter;\n}): string | undefined {\n const { row, keys, separator, formatter } = options;\n const parts = keys.flatMap((key, index) => {\n const value = row[key];\n if (value == null || value === '') return [];\n // Emptiness is re-checked *after* formatting, not just on the raw value: a\n // formatter that blanks a field it doesn't want to show (a zero, a sentinel)\n // would otherwise contribute an empty part and put back the dangling\n // separator this function exists to avoid.\n const part = formatter ? formatter(value, index) : String(value);\n return part === '' ? [] : [part];\n });\n return parts.length ? parts.join(separator) : undefined;\n}\n\ninterface TreemapCellProps {\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n // recharts injects the node's resolved `name` (from nameKey) + value, plus\n // every other field on the row — which is how the two stamped labels arrive.\n name?: string;\n /** The leaf's display name — its `config` label, stamped onto the row by `Treemap`. */\n primaryLabel?: string;\n /** The label's precomputed second line, stamped onto each row by `Treemap`. */\n secondaryLabel?: string;\n showLabels?: boolean;\n labelAlign?: TreemapLabelAlign;\n /**\n * How the leaf's fill relates to text contrast. Stamped by `Treemap` onto each\n * row before recharts hands it to the cell renderer. Auto-computed from the\n * resolved token name suffix:\n *\n * - `'dark'` — saturated/dark in both themes → always white text.\n * - `'pale'` — pale in both themes → always dark text (via theme-adaptive token).\n * - `'inverts'` — dark in light, pale in dark (sequential stops 7–8 whose\n * `light-dark()` values mirror across themes) → white text in light, dark text\n * in dark, achieved by a `light-dark()` color value in CSS.\n */\n fillTone?: 'dark' | 'pale' | 'inverts';\n}\n\n// How the label block sits in its tile. `text-start`/`text-center` and the\n// block-axis `justify-*` are what make the corner-anchored variants mirror under\n// `dir=\"rtl\"` — the same logical utilities every other component uses, which is\n// why the label is HTML in a `foreignObject` rather than SVG `<text>`:\n// `x`/`text-anchor` are physical, so a mirrored label would need the direction\n// read in JS and applied by hand.\n//\n// Every variant leaves the cross axis on the default `items-stretch`, including\n// `center`. Centering the line is `text-center`'s job, not `items-center`'s:\n// `items-center` would size each line to its own text instead of to the tile, and\n// a `truncate` line only ellipsizes when it is the *tile* that constrains it — a\n// shrink-to-fit line is never narrower than its text, so it would silently\n// overflow and be hard-clipped on both edges instead.\nconst LABEL_ALIGN_CLASS: Record<TreemapLabelAlign, string> = {\n 'bottom-start': 'justify-end text-start',\n 'top-start': 'justify-start text-start',\n center: 'justify-center text-center',\n};\n\n// Cell renderer: fill each rounded rect from its `--color-<name>` var, inset it so\n// the surface shows through between tiles, and lay the label block over it — the\n// leaf's name, plus the stamped second line when the tile is tall enough. Passed to\n// `Treemap.content`; recharts clones it with each node's geometry + row fields.\nexport function TreemapCell({\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n name,\n primaryLabel,\n secondaryLabel,\n showLabels = true,\n labelAlign = 'center',\n fillTone = 'dark',\n}: TreemapCellProps) {\n // recharts invokes `content` for the synthetic root node too (full chart\n // dimensions, empty name). Skip any name-less node — otherwise its rect has no\n // `--color-<name>` fill and paints the SVG-default black behind everything\n // (invisible when leaves cover it, a black box on empty data).\n if (!name) return <g />;\n // The tile, not the node: the label sits inside the inset rect, so the gutter\n // never eats into its padding.\n const tileX = x + CELL_GAP;\n const tileY = y + CELL_GAP;\n const tileWidth = Math.max(0, width - CELL_GAP * 2);\n const tileHeight = Math.max(0, height - CELL_GAP * 2);\n\n // `>=` on the heights: they are exact fits (padding + line boxes), so a tile\n // that measures one of them exactly has room for that label. The width is a\n // judgement call about how narrow a label is still worth reading, not a fit, so\n // it stays a strict `>`.\n const canLabel =\n showLabels && tileWidth > MIN_LABEL_WIDTH && tileHeight >= MIN_LABEL_HEIGHT;\n const secondary =\n canLabel && secondaryLabel && tileHeight >= MIN_TWO_LINE_HEIGHT\n ? secondaryLabel\n : undefined;\n\n return (\n <g>\n <rect\n x={tileX}\n y={tileY}\n width={tileWidth}\n height={tileHeight}\n rx={CELL_RADIUS}\n style={{ fill: `var(--color-${toCssKey(name)})` }}\n />\n {canLabel && (\n // Decorative: the hover that opens the tooltip belongs to the tile, so the\n // label must not swallow it.\n <foreignObject\n x={tileX}\n y={tileY}\n width={tileWidth}\n height={tileHeight}\n className=\"pointer-events-none\"\n >\n <div\n className={cn(\n // Each line truncates with a real ellipsis — CSS measures the text,\n // so nothing has to estimate how much of it fits.\n 'flex size-full flex-col overflow-hidden p-3',\n // Adaptive text — three tones:\n // • dark: saturated in both themes → white text (on-strong-neutral).\n // • pale: pale in both themes → on-surface-primary (dark↔light).\n // • inverts: dark in light, pale in dark (sequential 7–8 whose\n // light-dark() values mirror) → on-strong-primary, which is\n // light-dark(near-white, near-black) — the exact inverse.\n fillTone === 'pale'\n ? 'text-[var(--ui-text-on-surface-primary)]'\n : fillTone === 'inverts'\n ? 'text-[var(--ui-text-on-status-strong-primary)]'\n : 'text-[var(--ui-text-on-status-strong-neutral)]',\n LABEL_ALIGN_CLASS[labelAlign]\n )}\n >\n <span className=\"truncate text-sm\">\n {primaryLabel ?? name}\n </span>\n {secondary && (\n <span className=\"truncate text-[11px]\">{secondary}</span>\n )}\n </div>\n </foreignObject>\n )}\n </g>\n );\n}\n\nexport interface TreemapProps\n extends Omit<React.ComponentProps<'div'>, 'children'>, ChartAnimationProps {\n /**\n * The dataviz palette this chart's series are painted from. Series that\n * state no `color` of their own take a stop of it. See `ChartPalette`.\n */\n palette?: ChartPalette;\n /**\n * Row-per-leaf data. Each object holds the leaf's `nameKey` label + its\n * `dataKey` numeric size.\n *\n * `fill`, `primaryLabel` and `secondaryLabel` are reserved: the chart stamps\n * them onto a copy of each row to carry the cell color and the two label lines\n * through to the renderer (recharts hands the whole row to `content`, so\n * anything the cell needs has to be on it). A row of your own carrying one of\n * those names may end up driving what the tile paints.\n */\n data: ReadonlyArray<Record<string, string | number>>;\n /**\n * Per-leaf map of `label` / `icon` / `tone`, keyed by the leaf's `nameKey`\n * value (imported from the shared `Chart` primitives). Turned into\n * `--color-<name>` custom properties. Series take their colour from the\n * container's `palette`; each entry maps a key to a `label` and an optional\n * `tone`.\n */\n config: ChartConfig;\n /** Numeric field that sizes each leaf's rectangle. */\n dataKey: string;\n /**\n * Label field that names each leaf (drives the on-cell label, legend, tooltip,\n * and `--color-<name>` lookup). Values should be unique; they are sanitized\n * automatically to a valid CSS custom-property fragment (CSS-safe keys are\n * preferred to avoid dev-mode warnings).\n */\n nameKey: string;\n /** Width-to-height ratio the tiling targets. */\n aspectRatio?: number;\n /** Render each leaf's label inside its cell (when it fits). */\n showLabels?: boolean;\n /**\n * Where a cell's label sits. Defaults to `center` — the Figma-canonical placement.\n * `bottom-start` anchors the block to the tile's bottom start corner; `top-start`\n * to the top start corner. The corner variants are named for the tile's *start* edge\n * because they mirror under `dir=\"rtl\"`.\n */\n labelAlign?: TreemapLabelAlign;\n /**\n * Fields whose values make up a second label line under the leaf's name — e.g.\n * `[dataKey]` for the size, or `['size', 'count']` for a value and a count. The\n * line is dropped on cells too short to hold it.\n */\n secondaryKeys?: readonly string[];\n /**\n * Format each secondary value. Receives the field's index in `secondaryKeys`\n * as its second argument, so one formatter can cover fields of different kinds.\n */\n secondaryFormatter?: TickFormatter;\n /** Separator drawn between multiple secondary values. */\n secondarySeparator?: string;\n showTooltip?: boolean;\n /**\n * Render the legend — one entry per leaf. Off by default: a treemap labels its\n * cells, so the legend is for the case where the tiles are too small to.\n */\n showLegend?: boolean;\n /** Which edge the legend sits on. */\n legendPos?: 'top' | 'bottom';\n /**\n * Replace the default tooltip. Pass a configured `ChartTooltipContent`\n * (imported from this library) — e.g. with a `formatter` / `labelFormatter` —\n * to customize formatting, per-cell rows, or extra fields without composing\n * recharts yourself. Ignored when `showTooltip` is false.\n */\n tooltipContent?: React.ComponentProps<typeof ChartTooltip>['content'];\n}\n\nconst Treemap = React.forwardRef<HTMLDivElement, TreemapProps>(\n (\n {\n className,\n config,\n palette = TREEMAP_DEFAULT_PALETTE,\n data,\n dataKey,\n nameKey,\n aspectRatio = 4 / 3,\n showLabels = true,\n labelAlign = 'center',\n secondaryKeys,\n secondaryFormatter,\n secondarySeparator = ' · ',\n showTooltip = true,\n showLegend = false,\n legendPos = 'bottom',\n tooltipContent,\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n ...props\n },\n ref\n ) => {\n const animation = resolveAnimation({\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n });\n // Stamp each row with its `fill` (the shadcn data-driven pattern) so a real\n // hover resolves the cell color in the tooltip — recharts' Treemap carries no\n // per-cell color on the tooltip payload item — and with its `secondaryLabel`,\n // which reaches the cell renderer the same way: recharts hands the whole row\n // to `content`, so a composed label has to be on the row before it gets there.\n const seriesData: Record<string, string | number | boolean>[] =\n React.useMemo(\n () => {\n // Auto fill-tone detection: resolve each leaf's token name and classify\n // it by suffix. Token values are light-dark() pairs, so a runtime\n // luminance check would be theme-fragile — the suffix is stable.\n const resolvedColors = resolveChartColors(config, palette);\n return data.map((row) => {\n const name = String(row[nameKey]);\n const secondaryLabel = secondaryKeys?.length\n ? treemapSecondaryLabel({\n row,\n keys: secondaryKeys,\n separator: secondarySeparator,\n formatter: secondaryFormatter,\n })\n : undefined;\n // The on-cell name is the leaf's `config` label, so a cell reads like its\n // legend entry and tooltip row do. It matters more here than elsewhere:\n // the raw `nameKey` value is sanitized before use in `--color-<name>`,\n // so a leaf whose display name has a space in it can be keyed by that name\n // while the human-readable label still comes from the `config` entry. Only\n // a string label can go in SVG text; a `ReactNode` one falls back to the key.\n const label = config[name]?.label;\n const rawToken = (resolvedColors[name]?.color ?? '').replace(\n /^var\\(|\\)$/g,\n ''\n );\n // Diverging: a1/a2/b1/b2 are pale; a3/b3 are dark.\n // Sequential: stops 1–2 are pale (in light) / dark (in dark); stops\n // 7–8 invert (dark in light, pale in dark). Stops 3–6 stay dark in\n // both themes.\n const isPale = /-(?:a[12]|b[12]|sequential-[a-z]+-[12])$/.test(rawToken);\n const inverts = /-sequential-[a-z]+-[78]$/.test(rawToken);\n const fillTone: 'dark' | 'pale' | 'inverts' = isPale\n ? 'pale'\n : inverts\n ? 'inverts'\n : 'dark';\n return {\n ...row,\n fill: `var(--color-${toCssKey(name)})`,\n fillTone,\n ...(typeof label === 'string' ? { primaryLabel: label } : {}),\n ...(secondaryLabel ? { secondaryLabel } : {}),\n };\n });\n },\n [\n config,\n data,\n nameKey,\n palette,\n secondaryFormatter,\n secondaryKeys,\n secondarySeparator,\n ]\n );\n\n // recharts 3 builds the legend payload from the graphical item, and `Treemap`\n // — unlike Bar/Line/Area/Pie/Radar/RadialBar/Scatter — never registers one,\n // so a `<Legend>` inside a `Treemap` renders empty. The payload is synthesized\n // from the leaves instead and handed to the shared `ChartLegendContent`, which\n // keeps the treemap on the same legend markers, labels and `config` lookup as\n // every other chart. `payload` carries the row so each entry resolves its own\n // `config` entry via `nameKey`.\n //\n // One entry per *distinct* leaf name: same-named leaves deliberately share a\n // `--color-<name>`/`config` entry, so a second entry would repeat the first\n // verbatim — and `ChartLegendContent` keys its entries on `value`, so it would\n // also be a duplicate React key.\n const legendPayload = React.useMemo<LegendPayload[]>(() => {\n const seen = new Set<string>();\n return seriesData.flatMap((row) => {\n const value = String(row[nameKey]);\n if (seen.has(value)) return [];\n seen.add(value);\n return [\n {\n value,\n dataKey: nameKey,\n type: 'rect' as const,\n color: String(row.fill),\n payload: row,\n },\n ];\n });\n }, [nameKey, seriesData]);\n\n // Every other chart type lets recharts shrink the plot area by the legend's\n // measured size, but recharts' `Treemap` tiles the *full* chart surface and\n // ignores the legend entirely — a `<Legend>` inside one paints over the tiles.\n // So the legend is not a `<Legend>` at all: the shared `ChartLegendContent` is\n // rendered straight into a row of its own beside the plot, on the same markers,\n // labels and `config` lookup as every other chart's legend, and normal flow\n // gives it the height it needs (one row, or several once it wraps) and takes\n // that height off the tiled surface.\n //\n // Rendering it here rather than inside the plot is also what keeps the tiling\n // correct: recharts' `Treemap` reads its container **once** (it opts out of the\n // responsive path, because its own stroke-width rounding makes that loop), and a\n // `<Legend>` can only render after the chart has a size — so the box it read\n // would always be the one from before its own legend existed, a legend-row too\n // tall, and the bottom row of tiles would be laid out under the clip. The row is\n // its final height on the first render instead, so that one read is the right one.\n //\n // The row sits outside `ChartContainer`, so it takes `config` as a prop (the\n // container's context doesn't reach it) and re-emits the `--color-<name>`\n // properties its swatches resolve against under its own `data-chart` id.\n const legendChartId = `chart-${React.useId().replace(/:/g, '')}`;\n const legendRow = showLegend ? (\n <div data-chart={legendChartId} className=\"text-xs\">\n <ChartStyle\n id={legendChartId}\n config={resolveChartColors(config, palette)}\n />\n <ChartLegendContent\n config={config}\n payload={legendPayload}\n verticalAlign={legendPos}\n nameKey={nameKey}\n />\n </div>\n ) : null;\n\n return (\n <div\n ref={ref}\n className={cn(showLegend && 'flex flex-col', className)}\n {...props}\n >\n {legendPos === 'top' && legendRow}\n <ChartContainer\n config={config}\n palette={palette}\n // `size-full` fills the caller's box; with a legend row beside it the\n // height comes from the flex line instead, so the tiles give up exactly\n // the room the legend takes.\n className={showLegend ? 'min-h-0 w-full flex-1' : 'size-full'}\n >\n <RechartsTreemap\n data={seriesData}\n dataKey={dataKey}\n nameKey={nameKey}\n aspectRatio={aspectRatio}\n {...animation}\n content={\n <TreemapCell showLabels={showLabels} labelAlign={labelAlign} />\n }\n >\n {showTooltip && (\n <ChartTooltip\n content={\n tooltipContent ?? (\n <ChartTooltipContent nameKey={nameKey} hideLabel />\n )\n }\n />\n )}\n </RechartsTreemap>\n </ChartContainer>\n {legendPos === 'bottom' && legendRow}\n </div>\n );\n }\n);\nTreemap.displayName = 'Treemap';\n\nexport { Treemap };\n"],"mappings":"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"}
|