{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sidebar",
  "title": "Sidebar",
  "description": "App navigation beside the page that collapses to icons or out of view, and opens as a sheet on narrow screens.",
  "dependencies": [
    "@base-ui/react",
    "@hugeicons/core-free-icons",
    "@hugeicons/react",
    "@stylexjs/stylex"
  ],
  "registryDependencies": [
    "https://constella.aayush.cv/r/base.json",
    "https://constella.aayush.cv/r/button.json",
    "https://constella.aayush.cv/r/scroll-fade.json",
    "https://constella.aayush.cv/r/separator.json",
    "https://constella.aayush.cv/r/sheet.json",
    "https://constella.aayush.cv/r/skeleton.json",
    "https://constella.aayush.cv/r/tooltip.json"
  ],
  "files": [
    {
      "path": "src/components/primitives/sidebar.tsx",
      "content": "\"use client\";\n\nimport { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport {\n  Cancel01Icon,\n  Menu01Icon,\n  SidebarLeftIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport {\n  createContext,\n  use,\n  useCallback,\n  useEffect,\n  useId,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n  useSyncExternalStore,\n} from \"react\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { flushSync } from \"react-dom\";\n\nimport { Button } from \"@/components/primitives/button\";\nimport type { ButtonProps } from \"@/components/primitives/button\";\nimport { Separator } from \"@/components/primitives/separator\";\nimport type { SeparatorProps } from \"@/components/primitives/separator\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetTitle,\n} from \"@/components/primitives/sheet\";\nimport { Skeleton } from \"@/components/primitives/skeleton\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/primitives/tooltip\";\nimport { scrollFade } from \"@/lib/constella/scroll-fade\";\nimport type { Sx } from \"@/lib/constella/styles\";\nimport { typeStyles, visuallyHidden } from \"@/lib/constella/styles\";\nimport {\n  colors,\n  durations,\n  easings,\n  fontWeights,\n  media,\n  motion,\n  opacities,\n  radii,\n  sizes,\n  space,\n  strokes,\n} from \"@/lib/constella/tokens.stylex\";\n\nconst sidebarSides = [\"left\", \"right\"] as const;\nconst sidebarCollapsibles = [\"offcanvas\", \"icon\", \"none\"] as const;\nconst sidebarMenuButtonSizes = [\"sm\", \"default\", \"lg\"] as const;\nconst sidebarMenuButtonVariants = [\"default\", \"outline\"] as const;\nconst sidebarSectionDirections = [\"forward\", \"back\"] as const;\n\ntype SidebarSide = (typeof sidebarSides)[number];\ntype SidebarCollapsible = (typeof sidebarCollapsibles)[number];\ntype SidebarMenuButtonSize = (typeof sidebarMenuButtonSizes)[number];\ntype SidebarMenuButtonVariant = (typeof sidebarMenuButtonVariants)[number];\ntype SidebarSectionDirection = (typeof sidebarSectionDirections)[number];\n\ntype Styled<T> = Omit<T, \"className\" | \"style\"> & {\n  sx?: Sx;\n};\n\ninterface SidebarContextValue {\n  changeSection: (\n    direction: SidebarSectionDirection,\n    update: () => void | Promise<void>\n  ) => void;\n  isMobile: boolean;\n  open: boolean;\n  openMobile: boolean;\n  sectionChanging: boolean;\n  setOpen: (open: boolean) => void;\n  setOpenMobile: (open: boolean) => void;\n  state: \"collapsed\" | \"expanded\";\n  toggleSidebar: () => void;\n}\n\nconst SidebarContext = createContext<SidebarContextValue | null>(null);\n\nfunction useSidebar() {\n  const context = use(SidebarContext);\n  if (!context) {\n    throw new Error(\"useSidebar must be used within a SidebarProvider.\");\n  }\n  return context;\n}\n\n// Inside the mobile sheet, where the trigger closes it.\nconst InSheetContext = createContext(false);\n\n// `collapsing` from the moment an icon sidebar starts to narrow; `rail` once it has,\n// so until then the full layout stays and the width clips it. Tooltips point away\n// from the window's edge.\nconst RailContext = createContext<{\n  collapsing: boolean;\n  rail: boolean;\n  tooltipSide: \"left\" | \"right\";\n}>({ collapsing: false, rail: false, tooltipSide: \"right\" });\n\n// The trigger's own view-transition name while the section changes, so it holds still\n// instead of sliding out and back in with the header.\nconst TriggerNameContext = createContext<string | null>(null);\n\n// Toggles without the animation: a keyboard action is too frequent to animate.\nconst KeyboardToggleContext = createContext<(() => void) | null>(null);\n\n// Tells a row's button whether its label is cut off, so its tooltip can show it whole.\nconst LabelContext = createContext<((truncated: boolean) => void) | null>(null);\n\n// The media token is a full at-rule; matchMedia takes only its query.\nconst desktopQuery = media.md.slice(\"@media \".length);\n\nfunction subscribe(onChange: () => void) {\n  const query = window.matchMedia(desktopQuery);\n  query.addEventListener(\"change\", onChange);\n  return () => {\n    query.removeEventListener(\"change\", onChange);\n  };\n}\n\n// Without transition types the slide can't tell forward from back, and without\n// `match-element` two sidebars would share a name, so those browsers swap at once.\nfunction supportsSectionChange() {\n  return (\n    CSS.supports(\"selector(:active-view-transition-type(a))\") &&\n    CSS.supports(\"view-transition-name: match-element\")\n  );\n}\n\nfunction useIsMobile() {\n  return useSyncExternalStore(\n    subscribe,\n    () => !window.matchMedia(desktopQuery).matches,\n    () => false\n  );\n}\n\n// Measured, so a label that fits never shows faded. Faded until then: the server\n// can't measure, and a long label must never paint hard-clipped.\nfunction useTruncated(enabled: boolean) {\n  const ref = useRef<HTMLElement>(null);\n  const report = use(LabelContext);\n  const [truncated, setTruncated] = useState(enabled);\n  useLayoutEffect(() => {\n    const node = ref.current;\n    function measure() {\n      const next =\n        enabled && node !== null && node.scrollWidth > node.clientWidth;\n      setTruncated(next);\n      report?.(next);\n    }\n    measure();\n    // Its box changes width with the sidebar, and its text on a rename.\n    const resize = new ResizeObserver(measure);\n    const content = new MutationObserver(measure);\n    if (node !== null) {\n      resize.observe(node);\n      content.observe(node, {\n        characterData: true,\n        childList: true,\n        subtree: true,\n      });\n    }\n    return () => {\n      resize.disconnect();\n      content.disconnect();\n    };\n  }, [enabled, report]);\n  return { ref, truncated };\n}\n\n// Its own provider's `changeSection`, or one the app started with the same types, like a\n// router's; `sidebar-scoped` marks a provider's own, so other sidebars on the page stay put.\nconst inSectionChange =\n  \":is([data-section-change] *, :root:active-view-transition-type(sidebar-forward, sidebar-back):not(:active-view-transition-type(sidebar-scoped)) *)\";\n// Toggled from the keyboard: too frequent to animate.\nconst fromKeyboard = \":is([data-instant] *)\";\n// The collapsed selectors outrank it, so each close duration pairs with it to stay at once.\nconst iconOnly = \":is([data-slot='sidebar'][data-collapsible='icon'] *)\";\nconst expandingContent =\n  \":is([data-slot='sidebar'][data-expanding] [data-slot='sidebar-content'] *)\";\nconst fadeIn = stylex.keyframes({ from: { opacity: 0 } });\nconst inContentIconOnly =\n  \":is([data-slot='sidebar'][data-collapsible='icon'] [data-slot='sidebar-content'] *)\";\nconst inRail = \":is([data-slot='sidebar'][data-rail] *)\";\nconst highlighted = \":is([data-active])\";\nconst pressable = \":not(:disabled, [aria-disabled='true'])\";\nconst rtl = \":is([dir='rtl'] *)\";\nconst triggerHovered = \":is([data-slot='sidebar-trigger']:hover *)\";\nconst triggerFocused = \":is([data-slot='sidebar-trigger']:focus-visible *)\";\n\n// The whole row answers the pointer, so reaching for its action keeps it lit, and so\n// does an open menu from it.\nconst item = \"[data-slot='sidebar-menu-item']\";\n// Its own attribute, not `data-slot`: a menu trigger rendering the action writes its slot over it.\nconst action = \"[data-sidebar-menu-action]\";\n// Its own children only, so a sub-row doesn't light its parent.\nconst ownHover = `${item}:has(> :not([data-slot='sidebar-menu-sub']):hover)`;\nconst ownFocus = `${item}:has(> :focus-visible)`;\nconst rowHovered = `:is(${ownHover} > *)`;\n// The action's menu only: the row's own tooltip writes the same attribute.\nconst actionOpen = `${item}:has(> ${action}[data-popup-open])`;\n// Or the row's own popup, as a menu's trigger: its tooltip also writes `data-popup-open`,\n// and a collapsible's trigger is expanded without one.\nconst rowOpen = `:is(${actionOpen} > *, [aria-haspopup][aria-expanded='true'])`;\nconst idle = \":not([data-active])\";\nconst rowFill = {\n  default: \"transparent\",\n  [media.hover]: {\n    default: \"transparent\",\n    [`${rowHovered}${idle}`]: colors.fillSubtle,\n    [`${rowOpen}${idle}`]: colors.fillSubtle,\n    [highlighted]: colors.fill,\n  },\n  [`${rowOpen}${idle}`]: colors.fillSubtle,\n  [`:active${pressable}${idle}`]: colors.fillSubtle,\n  [highlighted]: colors.fill,\n};\n// An inset shadow, not a second fill, so hover stacks on the current row's `fill` and still fades.\nconst lit = `inset 0 0 0 100vmax ${colors.fillSubtle}`;\nconst unlit = \"inset 0 0 0 100vmax transparent\";\n\n// The label fades out before the row's end, or before an action drawn over it:\n// the action's width and inset, less the row's padding.\nconst actionRoom = `(${sizes.controlXs} + ${space.xxs} - ${space.xs})`;\nconst fadeAtEdge = (to: string) =>\n  `linear-gradient(to ${to}, black calc(100% - ${space.lg}), transparent)`;\nconst fadeBeforeAction = (to: string) =>\n  `linear-gradient(to ${to}, black calc(100% - ${actionRoom} - ${space.md}), transparent calc(100% - ${actionRoom}))`;\nconst withAction = `${item}:has(> ${action})`;\n// Whenever the action is drawn: always unless it waits for hover, and while its row is\n// hovered, focused or has its menu open.\nconst actionShown = `:is(${item}:has(> ${action}:not([data-show-on-hover])) *, ${withAction}${ownFocus} *, ${actionOpen} *)`;\nconst actionHovered = `:is(${withAction}${ownHover} *)`;\n\nconst styles = stylex.create({\n  wrapper: {\n    display: \"flex\",\n    minHeight: \"100svh\",\n    width: \"100%\",\n  },\n  // In the flow and sticky, so it sits beside the content and fills the window's height.\n  sidebar: {\n    // On the column, not its full-width inside, so the edge stays drawn while the width moves.\n    borderInlineEndColor: colors.borderSubtle,\n    borderInlineEndStyle: \"solid\",\n    borderInlineEndWidth: strokes.border,\n    color: colors.textPrimary,\n    // Rows size to it, so they narrow with the width instead of being cut at its edge.\n    containerType: \"inline-size\",\n    display: { default: \"none\", [media.md]: \"block\" },\n    flexShrink: 0,\n    height: \"100svh\",\n    insetBlockStart: 0,\n    // Lets a sub-menu's height fold with the width.\n    interpolateSize: \"allow-keywords\",\n    maxHeight: \"100%\",\n    overflow: \"hidden\",\n    position: \"sticky\",\n    transitionDuration: {\n      default: durations.sidebar,\n      \":is([data-collapsible])\": durations.sidebarExit,\n      [fromKeyboard]: \"0s\",\n      [`:is([data-collapsible])${fromKeyboard}`]: \"0s\",\n      [media.reducedMotion]: \"0s\",\n    },\n    transitionProperty: \"width\",\n    transitionTimingFunction: easings.inOut,\n    width: sizes.sidebar,\n  },\n  offcanvas: { borderInlineEndWidth: 0, borderInlineStartWidth: 0, width: 0 },\n  sidebarRight: {\n    borderInlineEndWidth: 0,\n    borderInlineStartColor: colors.borderSubtle,\n    borderInlineStartStyle: \"solid\",\n    borderInlineStartWidth: strokes.border,\n  },\n  icon: { width: sizes.sidebarIcon },\n  // Keeps its full width while the sidebar narrows, so its content is clipped, not reflowed.\n  inner: {\n    backgroundColor: colors.sidebar,\n    display: \"flex\",\n    flexDirection: \"column\",\n    height: \"100%\",\n    position: \"relative\",\n    // Inside the column's border.\n    width: {\n      default: `calc(${sizes.sidebar} - ${strokes.border})`,\n      [inRail]: `calc(${sizes.sidebarIcon} - ${strokes.border})`,\n    },\n  },\n  innerRight: { marginInlineStart: \"auto\" },\n  static: {\n    backgroundColor: colors.sidebar,\n    color: colors.textPrimary,\n    display: \"flex\",\n    flexDirection: \"column\",\n    height: \"100%\",\n    width: sizes.sidebar,\n  },\n  sheet: {\n    backgroundColor: colors.sidebar,\n    width: sizes.sidebar,\n  },\n  main: {\n    backgroundColor: colors.background,\n    display: \"flex\",\n    flex: 1,\n    flexDirection: \"column\",\n    minWidth: 0,\n    position: \"relative\",\n  },\n  rail: {\n    backgroundColor: \"transparent\",\n    borderBlockEndWidth: 0,\n    borderBlockStartWidth: 0,\n    borderInlineEndWidth: 0,\n    borderInlineStartWidth: 0,\n    cursor: \"ew-resize\",\n    insetBlockEnd: 0,\n    insetBlockStart: 0,\n    // On the edge that meets the page, whichever side the sidebar is on.\n    insetInlineEnd: {\n      default: `calc(-1 * ${space.xs})`,\n      \":is([data-side='right'] *)\": \"auto\",\n    },\n    insetInlineStart: {\n      default: \"auto\",\n      \":is([data-side='right'] *)\": `calc(-1 * ${space.xs})`,\n    },\n    paddingBlockEnd: 0,\n    paddingBlockStart: 0,\n    paddingInlineEnd: 0,\n    paddingInlineStart: 0,\n    position: \"absolute\",\n    width: space.md,\n    zIndex: 1,\n    \"::after\": {\n      backgroundColor: {\n        default: \"transparent\",\n        [media.hover]: { default: \"transparent\", \":hover\": colors.border },\n      },\n      content: \"''\",\n      insetBlockEnd: 0,\n      insetBlockStart: 0,\n      insetInlineStart: `calc(50% - ${strokes.border})`,\n      position: \"absolute\",\n      transitionDuration: durations.hover,\n      transitionProperty: \"background-color\",\n      width: strokes.indicator,\n    },\n  },\n  triggerName: (name: string) => ({\n    viewTransitionName: { default: null, [inSectionChange]: name },\n  }),\n  // Named only while its own sidebar changes section; base.css slides the snapshots.\n  swap: {\n    viewTransitionClass: { default: null, [inSectionChange]: \"sidebar-swap\" },\n    viewTransitionName: { default: null, [inSectionChange]: \"match-element\" },\n  },\n  // The page's header height, so the two line up across the edge.\n  header: {\n    alignItems: \"center\",\n    display: \"flex\",\n    flexShrink: 0,\n    gap: space.xxs,\n    height: sizes.header,\n    // Alone in the rail, the trigger keeps to the start, on the rows' icon column.\n    justifyContent: \"space-between\",\n    paddingInlineEnd: space.xs,\n    paddingInlineStart: space.xs,\n  },\n  headerActions: {\n    alignItems: \"center\",\n    display: \"flex\",\n    flexShrink: 0,\n    gap: space.xxs,\n  },\n  footer: {\n    display: \"flex\",\n    flexDirection: \"column\",\n    gap: space.xs,\n    paddingBlockEnd: space.xs,\n    paddingBlockStart: space.xs,\n    paddingInlineEnd: space.xs,\n    paddingInlineStart: space.xs,\n  },\n  content: {\n    display: \"flex\",\n    flex: 1,\n    flexDirection: \"column\",\n    gap: space.md,\n    minHeight: 0,\n    overflowX: \"hidden\",\n    overflowY: { default: \"auto\", [iconOnly]: \"hidden\" },\n    paddingBlockEnd: space.xs,\n    paddingInlineEnd: space.xs,\n    paddingInlineStart: space.xs,\n  },\n  separator: {\n    marginInlineEnd: space.xs,\n    marginInlineStart: space.xs,\n    width: \"auto\",\n  },\n  group: {\n    display: \"flex\",\n    flexDirection: \"column\",\n    minWidth: 0,\n    position: \"relative\",\n    width: \"100%\",\n  },\n  // Folds away with the width, rather than leaving the rows below to jump up when it goes.\n  groupLabel: {\n    alignItems: \"center\",\n    color: colors.textSecondary,\n    display: \"flex\",\n    flexShrink: 0,\n    height: sizes.controlXs,\n    marginBlockStart: {\n      default: 0,\n      [iconOnly]: `calc(-1 * ${sizes.controlXs})`,\n    },\n    // Fades out at its end like a row's label, not cut off.\n    maskImage: {\n      default: null,\n      \":is([data-truncated])\": fadeAtEdge(\"right\"),\n      [`:is([data-truncated])${rtl}`]: fadeAtEdge(\"left\"),\n    },\n    opacity: { default: 1, [iconOnly]: 0 },\n    overflow: \"hidden\",\n    paddingInlineEnd: space.xs,\n    paddingInlineStart: space.xs,\n    transitionDuration: {\n      default: durations.sidebar,\n      [iconOnly]: durations.sidebarExit,\n      [fromKeyboard]: \"0s\",\n      [`${iconOnly}${fromKeyboard}`]: \"0s\",\n      [media.reducedMotion]: \"0s\",\n    },\n    transitionProperty: \"margin, opacity\",\n    transitionTimingFunction: easings.inOut,\n    whiteSpace: \"nowrap\",\n  },\n  action: {\n    alignItems: \"center\",\n    // Stays lit while its menu is open, as a button's trigger does.\n    backgroundColor: {\n      default: \"transparent\",\n      \":is([data-popup-open])\": colors.fillSubtle,\n      [media.hover]: {\n        default: \"transparent\",\n        \":hover\": colors.fillSubtle,\n        \":is([data-popup-open])\": colors.fillSubtle,\n      },\n    },\n    borderBlockEndWidth: 0,\n    borderBlockStartWidth: 0,\n    borderInlineEndWidth: 0,\n    borderInlineStartWidth: 0,\n    // Concentric with the row: its radius less the inset it sits at.\n    borderStartStartRadius: radii.xs,\n    borderStartEndRadius: radii.xs,\n    borderEndStartRadius: radii.xs,\n    borderEndEndRadius: radii.xs,\n    color: colors.textSecondary,\n    cursor: \"pointer\",\n    display: { default: \"flex\", [inRail]: \"none\" },\n    height: sizes.controlXs,\n    insetInlineEnd: space.xxs,\n    justifyContent: \"center\",\n    paddingBlockEnd: 0,\n    paddingBlockStart: 0,\n    paddingInlineEnd: 0,\n    paddingInlineStart: 0,\n    position: \"absolute\",\n    transitionDuration: durations.hover,\n    transitionProperty: \"background-color, opacity\",\n    transitionTimingFunction: \"ease\",\n    width: sizes.controlXs,\n  },\n  groupAction: {\n    insetBlockStart: 0,\n  },\n  groupContent: {\n    width: \"100%\",\n  },\n  menu: {\n    display: \"flex\",\n    flexDirection: \"column\",\n    // Apart by a hairline, so a hovered row's fill never merges into the current one.\n    gap: space.xxxs,\n    listStyleType: \"none\",\n    marginBlockEnd: 0,\n    marginBlockStart: 0,\n    minWidth: 0,\n    paddingInlineStart: 0,\n    width: \"100%\",\n  },\n  menuItem: {\n    position: \"relative\",\n  },\n  // In the content it fades with the width, as it has no icon to keep in the rail; the\n  // header's stays, so its logo holds where the rail's mark takes over.\n  expandedOnly: {\n    // Mounted as the rail opens, it fades in with the labels instead of starting whole.\n    animationDuration: durations.sidebar,\n    animationName: { default: null, [expandingContent]: fadeIn },\n    animationTimingFunction: easings.inOut,\n    display: \"flex\",\n    flexDirection: \"column\",\n    minWidth: 0,\n    opacity: { default: 1, [inContentIconOnly]: 0 },\n    transitionDuration: {\n      default: durations.sidebar,\n      [iconOnly]: durations.sidebarExit,\n      [fromKeyboard]: \"0s\",\n      [`${iconOnly}${fromKeyboard}`]: \"0s\",\n      [media.reducedMotion]: \"0s\",\n    },\n    transitionProperty: \"opacity\",\n    transitionTimingFunction: easings.inOut,\n  },\n  button: {\n    alignItems: \"center\",\n    backgroundColor: rowFill,\n    borderBlockEndWidth: 0,\n    borderBlockStartWidth: 0,\n    borderInlineEndWidth: 0,\n    borderInlineStartWidth: 0,\n    borderStartStartRadius: radii.sm,\n    borderStartEndRadius: radii.sm,\n    borderEndStartRadius: radii.sm,\n    borderEndEndRadius: radii.sm,\n    boxShadow: {\n      default: null,\n      [highlighted]: {\n        default: unlit,\n        [media.hover]: { default: unlit, [rowHovered]: lit, [rowOpen]: lit },\n        [rowOpen]: lit,\n      },\n    },\n    color: colors.textPrimary,\n    cursor: \"pointer\",\n    display: \"flex\",\n    fontWeight: fontWeights.regular,\n    gap: space.xs,\n    height: sizes.controlMd,\n    opacity: {\n      default: 1,\n      \":is(:disabled, [aria-disabled='true'])\": opacities.disabled,\n    },\n    overflow: \"hidden\",\n    paddingBlockEnd: 0,\n    paddingBlockStart: 0,\n    paddingInlineEnd: space.xs,\n    paddingInlineStart: space.xs,\n    textAlign: \"start\",\n    textDecorationLine: \"none\",\n    transitionDuration: durations.hover,\n    transitionProperty: \"background-color, box-shadow\",\n    transitionTimingFunction: \"ease\",\n    whiteSpace: \"nowrap\",\n    width: {\n      default: `min(100%, 100cqi - 2 * ${space.xs})`,\n      [inRail]: sizes.controlMd,\n    },\n  },\n  // Room on the end for a badge.\n  buttonWithEnd: {\n    paddingInlineEnd: {\n      default: `calc(${space.xs} + ${sizes.controlXs} + ${space.xxs})`,\n      [inRail]: space.xs,\n    },\n  },\n  buttonOutline: {\n    backgroundColor: colors.background,\n    boxShadow: `0 0 0 ${strokes.border} ${colors.border}`,\n  },\n  buttonSm: {\n    height: sizes.controlSm,\n  },\n  buttonLg: {\n    height: { default: sizes.media, [inRail]: sizes.controlMd },\n    paddingInlineEnd: { default: space.xs, [inRail]: 0 },\n    paddingInlineStart: { default: space.xs, [inRail]: 0 },\n  },\n  // Stays put while the sidebar's edge passes over it; the rail's narrow button hides it.\n  label: {\n    // One mounted as the rail opens (a brand's name) fades in like the rest, not whole.\n    animationDuration: durations.sidebar,\n    animationName: {\n      default: null,\n      \":is([data-slot='sidebar'][data-expanding] *)\": fadeIn,\n    },\n    animationTimingFunction: easings.inOut,\n    flexGrow: 1,\n    maskImage: {\n      default: null,\n      \":is([data-truncated])\": fadeAtEdge(\"right\"),\n      [`:is([data-truncated])${rtl}`]: fadeAtEdge(\"left\"),\n      [actionShown]: fadeBeforeAction(\"right\"),\n      [`${actionShown}${rtl}`]: fadeBeforeAction(\"left\"),\n      [media.hover]: {\n        [actionHovered]: fadeBeforeAction(\"right\"),\n        [`${actionHovered}${rtl}`]: fadeBeforeAction(\"left\"),\n      },\n    },\n    minWidth: 0,\n    // On the width's own clock, so it's gone just as the edge reaches the icons.\n    opacity: { default: 1, [iconOnly]: 0 },\n    overflow: \"hidden\",\n    transitionDuration: {\n      default: durations.sidebar,\n      [iconOnly]: durations.sidebarExit,\n      [fromKeyboard]: \"0s\",\n      [`${iconOnly}${fromKeyboard}`]: \"0s\",\n      [media.reducedMotion]: \"0s\",\n    },\n    transitionProperty: \"opacity\",\n    transitionTimingFunction: easings.inOut,\n    whiteSpace: \"nowrap\",\n  },\n  // Drawn while its row is pointed at or focused, and kept in the layout when hidden,\n  // so the label's width never moves.\n  kbd: {\n    display: { default: \"flex\", [inRail]: \"none\" },\n    flexShrink: 0,\n    opacity: {\n      default: 0,\n      [`:is(${ownFocus} *)`]: 1,\n      [media.hover]: {\n        default: 0,\n        [`:is(${ownHover} *)`]: 1,\n      },\n    },\n    transitionDuration: durations.hover,\n    transitionProperty: \"opacity\",\n    transitionTimingFunction: \"ease\",\n  },\n  menuAction: {\n    insetBlockStart: `calc((${sizes.controlMd} - ${sizes.controlXs}) / 2)`,\n  },\n  // Shown while its row is hovered or focused, or while its menu is open; always on touch.\n  onHover: {\n    opacity: {\n      default: 1,\n      [media.hover]: {\n        default: 0,\n        [`:is(${ownHover} *, ${ownFocus} *)`]: 1,\n        \":is([data-popup-open])\": 1,\n      },\n    },\n  },\n  badge: {\n    alignItems: \"center\",\n    color: colors.textSecondary,\n    display: { default: \"flex\", [inRail]: \"none\" },\n    fontVariantNumeric: \"tabular-nums\",\n    fontWeight: fontWeights.medium,\n    height: sizes.controlXs,\n    insetBlockStart: `calc((${sizes.controlMd} - ${sizes.controlXs}) / 2)`,\n    insetInlineEnd: space.xxs,\n    justifyContent: \"center\",\n    minWidth: sizes.controlXs,\n    paddingInlineEnd: space.xxs,\n    paddingInlineStart: space.xxs,\n    pointerEvents: \"none\",\n    position: \"absolute\",\n    userSelect: \"none\",\n  },\n  skeleton: {\n    alignItems: \"center\",\n    display: \"flex\",\n    gap: space.xs,\n    height: sizes.controlMd,\n    paddingInlineEnd: space.xs,\n    paddingInlineStart: space.xs,\n  },\n  skeletonIcon: {\n    flexShrink: 0,\n    height: sizes.icon,\n    width: sizes.icon,\n  },\n  skeletonText: {\n    flex: 1,\n    height: sizes.icon,\n  },\n  sub: {\n    borderInlineStartColor: colors.borderSubtle,\n    borderInlineStartStyle: \"solid\",\n    borderInlineStartWidth: strokes.border,\n    display: \"flex\",\n    flexDirection: \"column\",\n    gap: space.xxxs,\n    // Folds shut with the width, so the rows under it close up in the same motion.\n    height: { default: \"auto\", [iconOnly]: 0 },\n    listStyleType: \"none\",\n    marginBlockEnd: 0,\n    marginBlockStart: 0,\n    marginInlineEnd: `calc(${space.xs} - ${space.xxxs})`,\n    // Under the parent row's icon.\n    marginInlineStart: `calc(${space.xs} + ${sizes.icon} / 2)`,\n    minWidth: 0,\n    opacity: { default: 1, [iconOnly]: 0 },\n    overflow: \"clip\",\n    overflowClipMargin: space.xxs,\n    paddingBlockEnd: { default: space.xxxs, [iconOnly]: 0 },\n    paddingBlockStart: { default: space.xxxs, [iconOnly]: 0 },\n    paddingInlineEnd: 0,\n    paddingInlineStart: `calc(${space.xs} - ${space.xxxs})`,\n    transitionDuration: {\n      default: durations.sidebar,\n      [iconOnly]: durations.sidebarExit,\n      [fromKeyboard]: \"0s\",\n      [`${iconOnly}${fromKeyboard}`]: \"0s\",\n      [media.reducedMotion]: \"0s\",\n    },\n    transitionProperty: \"height, padding-block, opacity\",\n    transitionTimingFunction: easings.inOut,\n  },\n  subButton: {\n    alignItems: \"center\",\n    backgroundColor: {\n      default: \"transparent\",\n      [media.hover]: {\n        default: \"transparent\",\n        [`:hover${pressable}`]: colors.fillSubtle,\n        [highlighted]: colors.fill,\n      },\n      [`:active${pressable}`]: colors.fillSubtle,\n      [highlighted]: colors.fill,\n    },\n    borderStartStartRadius: radii.sm,\n    borderStartEndRadius: radii.sm,\n    borderEndStartRadius: radii.sm,\n    borderEndEndRadius: radii.sm,\n    color: colors.textPrimary,\n    display: \"flex\",\n    gap: space.xs,\n    height: sizes.controlSm,\n    overflow: \"hidden\",\n    paddingInlineEnd: space.xs,\n    paddingInlineStart: space.xs,\n    textDecorationLine: \"none\",\n    transitionDuration: durations.hover,\n    transitionProperty: \"background-color\",\n    transitionTimingFunction: \"ease\",\n    whiteSpace: \"nowrap\",\n  },\n  // The rail's mark and the icon share one cell, and cross-fade as the trigger is pointed at.\n  markStack: {\n    display: \"inline-grid\",\n  },\n  markLayer: {\n    display: \"flex\",\n    gridArea: \"1 / 1\",\n    transitionDuration: {\n      default: durations.hover,\n      // Keyboard focus never animates.\n      [triggerFocused]: \"0s\",\n    },\n    transitionProperty: {\n      default: \"opacity, scale, filter\",\n      [media.reducedMotion]: \"opacity\",\n    },\n    transitionTimingFunction: easings.crossfade,\n  },\n  // The outgoing layer shrinks and softens, so the two never double-expose.\n  markIdle: {\n    filter: {\n      default: \"none\",\n      [triggerFocused]: `blur(${motion.crossfadeTextBlur})`,\n      [media.hover]: {\n        default: \"none\",\n        [triggerHovered]: `blur(${motion.crossfadeTextBlur})`,\n      },\n    },\n    opacity: {\n      default: 1,\n      [triggerFocused]: 0,\n      [media.hover]: { default: 1, [triggerHovered]: 0 },\n    },\n    scale: {\n      default: 1,\n      [triggerFocused]: motion.popoverScale,\n      [media.hover]: { default: 1, [triggerHovered]: motion.popoverScale },\n    },\n  },\n  markActive: {\n    filter: {\n      default: `blur(${motion.crossfadeTextBlur})`,\n      [triggerFocused]: \"none\",\n      [media.hover]: {\n        default: `blur(${motion.crossfadeTextBlur})`,\n        [triggerHovered]: \"none\",\n      },\n    },\n    opacity: {\n      default: 0,\n      [triggerFocused]: 1,\n      [media.hover]: { default: 0, [triggerHovered]: 1 },\n    },\n    scale: {\n      default: motion.popoverScale,\n      [triggerFocused]: 1,\n      [media.hover]: { default: motion.popoverScale, [triggerHovered]: 1 },\n    },\n  },\n});\n\n/** Holds the open state, toggles on ⌘B / Ctrl+B, and lays the sidebar out beside its inset. */\nfunction SidebarProvider({\n  defaultOpen = true,\n  onOpenChange,\n  open: openProp,\n  sx,\n  ...props\n}: Styled<ComponentProps<\"div\">> & {\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  open?: boolean;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = useState(false);\n  const [ownOpen, setOwnOpen] = useState(defaultOpen);\n  // A count, so a change started mid-slide isn't unnamed when the first one ends.\n  const [sectionChanges, setSectionChanges] = useState(0);\n  // Whether the last toggle came from ⌘B / Ctrl+B.\n  const [instant, setInstant] = useState(false);\n  const open = openProp ?? ownOpen;\n\n  const setOpen = useCallback(\n    (next: boolean) => {\n      setInstant(false);\n      if (onOpenChange) {\n        onOpenChange(next);\n      } else {\n        setOwnOpen(next);\n      }\n    },\n    [onOpenChange]\n  );\n\n  const toggleSidebar = useCallback(() => {\n    if (isMobile) {\n      setOpenMobile(!openMobile);\n    } else {\n      setOpen(!open);\n    }\n  }, [isMobile, open, openMobile, setOpen]);\n\n  const toggleFromKeyboard = useCallback(() => {\n    toggleSidebar();\n    setInstant(true);\n  }, [toggleSidebar]);\n\n  const changeSection = useCallback(\n    (\n      direction: SidebarSectionDirection,\n      update: () => void | Promise<void>\n    ) => {\n      if (!supportsSectionChange()) {\n        void update();\n        return;\n      }\n      flushSync(() => {\n        setSectionChanges((count) => count + 1);\n      });\n      const transition = document.startViewTransition({\n        types: [`sidebar-${direction}`, \"sidebar-scoped\"],\n        update: async () => {\n          await flushSync(update);\n        },\n      });\n      // `finished` rejects when `update` does; the names come off either way.\n      async function settle() {\n        await Promise.allSettled([transition.finished]);\n        setSectionChanges((count) => count - 1);\n      }\n      void settle();\n    },\n    []\n  );\n\n  useEffect(() => {\n    function onKeyDown(event: KeyboardEvent) {\n      if (event.key === \"b\" && (event.metaKey || event.ctrlKey)) {\n        event.preventDefault();\n        toggleFromKeyboard();\n      }\n    }\n    window.addEventListener(\"keydown\", onKeyDown);\n    return () => {\n      window.removeEventListener(\"keydown\", onKeyDown);\n    };\n  }, [toggleFromKeyboard]);\n\n  const context = useMemo<SidebarContextValue>(\n    () => ({\n      changeSection,\n      isMobile,\n      open,\n      openMobile,\n      sectionChanging: sectionChanges > 0,\n      setOpen,\n      setOpenMobile,\n      state: open ? \"expanded\" : \"collapsed\",\n      toggleSidebar,\n    }),\n    [\n      changeSection,\n      isMobile,\n      open,\n      openMobile,\n      sectionChanges,\n      setOpen,\n      toggleSidebar,\n    ]\n  );\n\n  return (\n    <SidebarContext value={context}>\n      {/* One group, so once a row's tooltip is open, the next opens at once. */}\n      <TooltipProvider>\n        <KeyboardToggleContext value={toggleFromKeyboard}>\n          <div\n            data-instant={instant || undefined}\n            data-slot=\"sidebar-wrapper\"\n            {...props}\n            {...stylex.props(styles.wrapper, sx)}\n          />\n        </KeyboardToggleContext>\n      </TooltipProvider>\n    </SidebarContext>\n  );\n}\n\nfunction Sidebar({\n  children,\n  collapsible = \"offcanvas\",\n  side = \"left\",\n  sx,\n  ...props\n}: Styled<Omit<ComponentProps<\"div\">, \"ref\">> & {\n  collapsible?: SidebarCollapsible;\n  side?: SidebarSide;\n}) {\n  const { isMobile, openMobile, sectionChanging, setOpenMobile, state } =\n    useSidebar();\n  const desktop = useRef<HTMLDivElement>(null);\n  const isCollapsed = state === \"collapsed\";\n  const [settled, setSettled] = useState(isCollapsed);\n  // Settles once the width transition ends; with none running (reduced motion) at once.\n  useEffect(() => {\n    const running = desktop.current?.getAnimations() ?? [];\n    let cancelled = false;\n    async function settle() {\n      await Promise.allSettled(\n        running.map(async (animation) => await animation.finished)\n      );\n      // The next toggle cancels this run, and its own run settles it. Any other end, like the\n      // column unmounting for the sheet, still settles, so the rail never waits on it.\n      if (!cancelled) {\n        setSettled(isCollapsed);\n      }\n    }\n    void settle();\n    return () => {\n      cancelled = true;\n    };\n  }, [isCollapsed]);\n  const collapsing = isCollapsed && collapsible === \"icon\";\n  const rail = collapsing && settled;\n  const tooltipSide: \"left\" | \"right\" = side === \"right\" ? \"left\" : \"right\";\n  const railState = useMemo(\n    () => ({ collapsing, rail, tooltipSide }),\n    [collapsing, rail, tooltipSide]\n  );\n  // On the sidebar itself, not the provider, so it reaches into the mobile sheet's portal.\n  const sectionChange = sectionChanging ? \"\" : undefined;\n  const triggerName = `sidebar-trigger${useId()}`;\n\n  if (collapsible === \"none\") {\n    return (\n      <div\n        data-section-change={sectionChange}\n        data-slot=\"sidebar\"\n        {...props}\n        {...stylex.props(styles.static, sx)}\n      >\n        <TriggerNameContext value={triggerName}>{children}</TriggerNameContext>\n      </div>\n    );\n  }\n\n  if (isMobile) {\n    return (\n      <Sheet onOpenChange={setOpenMobile} open={openMobile}>\n        <SheetContent\n          data-mobile\n          data-section-change={sectionChange}\n          data-slot=\"sidebar\"\n          showCloseButton={false}\n          side={side}\n          sx={[styles.sheet, sx]}\n        >\n          <SheetTitle sx={visuallyHidden.base}>Sidebar</SheetTitle>\n          <SheetDescription sx={visuallyHidden.base}>\n            Displays the mobile sidebar.\n          </SheetDescription>\n          <InSheetContext value>\n            <TriggerNameContext value={triggerName}>\n              {children}\n            </TriggerNameContext>\n          </InSheetContext>\n        </SheetContent>\n      </Sheet>\n    );\n  }\n\n  return (\n    <div\n      data-collapsible={isCollapsed ? collapsible : undefined}\n      data-expanding={\n        (collapsible === \"icon\" && !isCollapsed && settled) || undefined\n      }\n      data-rail={rail || undefined}\n      data-section-change={sectionChange}\n      data-side={side}\n      data-slot=\"sidebar\"\n      ref={desktop}\n      {...props}\n      {...stylex.props(\n        styles.sidebar,\n        side === \"right\" && styles.sidebarRight,\n        isCollapsed && collapsible === \"offcanvas\" && styles.offcanvas,\n        isCollapsed && collapsible === \"icon\" && styles.icon\n      )}\n    >\n      <div\n        data-slot=\"sidebar-inner\"\n        {...stylex.props(\n          styles.inner,\n          side === \"right\" && styles.innerRight,\n          sx\n        )}\n      >\n        <RailContext value={railState}>\n          <TriggerNameContext value={triggerName}>\n            {children}\n          </TriggerNameContext>\n        </RailContext>\n      </div>\n    </div>\n  );\n}\n\n/**\n * Collapses the sidebar, or opens it as a sheet on narrow screens; inside the sheet it\n * closes it. Its children default to `SidebarTriggerIcon`; `kbd` follows the label in its\n * tooltip.\n */\nfunction SidebarTrigger({\n  \"aria-label\": ariaLabel,\n  children,\n  kbd,\n  onClick,\n  size = \"icon\",\n  sx,\n  variant = \"ghost\",\n  ...props\n}: ButtonProps & { kbd?: ReactNode }) {\n  const { isMobile, open, setOpenMobile, toggleSidebar } = useSidebar();\n  const inSheet = use(InSheetContext);\n  const name = use(TriggerNameContext);\n  const triggerSx = [name !== null && styles.triggerName(name), sx];\n  const toggleFromKeyboard = use(KeyboardToggleContext);\n  const { rail, tooltipSide } = use(RailContext);\n\n  if (inSheet) {\n    return (\n      <Button\n        aria-label={ariaLabel ?? \"Close sidebar\"}\n        data-slot=\"sidebar-trigger\"\n        onClick={(event) => {\n          onClick?.(event);\n          setOpenMobile(false);\n        }}\n        size={size}\n        sx={triggerSx}\n        variant={variant}\n        {...props}\n      >\n        {children ?? <SidebarTriggerIcon />}\n      </Button>\n    );\n  }\n\n  let label = open ? \"Collapse sidebar\" : \"Expand sidebar\";\n  if (isMobile) {\n    label = \"Open sidebar\";\n  }\n  return (\n    <Tooltip>\n      <TooltipTrigger\n        render={\n          <Button\n            aria-expanded={isMobile ? undefined : open}\n            aria-label={ariaLabel ?? label}\n            data-slot=\"sidebar-trigger\"\n            onClick={(event) => {\n              onClick?.(event);\n              // Enter or Space: a keyboard toggle, so it skips the animation.\n              if (event.detail === 0 && toggleFromKeyboard) {\n                toggleFromKeyboard();\n              } else {\n                toggleSidebar();\n              }\n            }}\n            size={size}\n            sx={triggerSx}\n            variant={variant}\n            {...props}\n          >\n            {children ?? <SidebarTriggerIcon />}\n          </Button>\n        }\n      />\n      <TooltipContent side={rail ? tooltipSide : \"bottom\"}>\n        {ariaLabel ?? label}\n        {kbd}\n      </TooltipContent>\n    </Tooltip>\n  );\n}\n\n/** The trigger's glyph for where it is: close in the mobile sheet, a menu below 768px, the sidebar otherwise. */\nfunction SidebarTriggerIcon() {\n  const { isMobile } = useSidebar();\n  let icon = SidebarLeftIcon;\n  if (use(InSheetContext)) {\n    icon = Cancel01Icon;\n  } else if (isMobile) {\n    icon = Menu01Icon;\n  }\n  return <Glyph icon={icon} />;\n}\n\n/**\n * A mark, like your logo, that stands in for `SidebarTriggerIcon` in the icon rail until\n * the trigger is pointed at or focused. Anywhere else it's the icon.\n */\nfunction SidebarTriggerMark({ children }: { children: ReactNode }) {\n  if (!use(RailContext).rail) {\n    return <SidebarTriggerIcon />;\n  }\n  return (\n    <span {...stylex.props(styles.markStack)}>\n      <span {...stylex.props(styles.markLayer, styles.markIdle)}>\n        {children}\n      </span>\n      <span {...stylex.props(styles.markLayer, styles.markActive)}>\n        <SidebarTriggerIcon />\n      </span>\n    </span>\n  );\n}\n\nfunction Glyph({ icon }: { icon: typeof SidebarLeftIcon }) {\n  return (\n    <HugeiconsIcon\n      aria-hidden\n      icon={icon}\n      size={sizes.icon}\n      strokeWidth={Number(strokes.icon)}\n    />\n  );\n}\n\n/** A strip on the sidebar's edge that toggles it, for the pointer only. */\nfunction SidebarRail({ sx, ...props }: Styled<ComponentProps<\"button\">>) {\n  const { toggleSidebar } = useSidebar();\n  return (\n    <button\n      aria-label=\"Toggle Sidebar\"\n      data-slot=\"sidebar-rail\"\n      onClick={toggleSidebar}\n      tabIndex={-1}\n      title=\"Toggle Sidebar\"\n      type=\"button\"\n      {...props}\n      {...stylex.props(styles.rail, sx)}\n    />\n  );\n}\n\nfunction SidebarInset({ sx, ...props }: Styled<ComponentProps<\"main\">>) {\n  return (\n    <main\n      data-slot=\"sidebar-inset\"\n      {...props}\n      {...stylex.props(styles.main, sx)}\n    />\n  );\n}\n\n/** Renders its children except in the collapsed icon rail. */\nfunction SidebarExpandedOnly({\n  children,\n  sx,\n}: {\n  children: ReactNode;\n  sx?: Sx;\n}) {\n  if (use(RailContext).rail) {\n    return null;\n  }\n  return (\n    <div\n      data-slot=\"sidebar-expanded-only\"\n      {...stylex.props(styles.expandedOnly, sx)}\n    >\n      {children}\n    </div>\n  );\n}\n\n/** `swap`: whether it slides when the section changes. */\ntype SwapProps = Styled<ComponentProps<\"div\">> & { swap?: boolean };\n\n/** A row the page's header height: a brand or title first, `SidebarHeaderActions` last. */\nfunction SidebarHeader({ swap = true, sx, ...props }: SwapProps) {\n  return (\n    <div\n      data-slot=\"sidebar-header\"\n      {...props}\n      {...stylex.props(styles.header, swap && styles.swap, sx)}\n    />\n  );\n}\n\nfunction SidebarHeaderActions({ sx, ...props }: Styled<ComponentProps<\"div\">>) {\n  return (\n    <div\n      data-slot=\"sidebar-header-actions\"\n      {...props}\n      {...stylex.props(styles.headerActions, sx)}\n    />\n  );\n}\n\nfunction SidebarFooter({ swap = false, sx, ...props }: SwapProps) {\n  return (\n    <div\n      data-slot=\"sidebar-footer\"\n      {...props}\n      {...stylex.props(styles.footer, swap && styles.swap, sx)}\n    />\n  );\n}\n\nfunction SidebarSeparator({ sx, ...props }: SeparatorProps) {\n  return (\n    <Separator\n      data-slot=\"sidebar-separator\"\n      {...props}\n      sx={[styles.separator, sx]}\n    />\n  );\n}\n\nfunction SidebarContent({ swap = true, sx, ...props }: SwapProps) {\n  return (\n    <div\n      data-slot=\"sidebar-content\"\n      {...props}\n      {...stylex.props(scrollFade.y, styles.content, swap && styles.swap, sx)}\n    />\n  );\n}\n\nfunction SidebarGroup({ sx, ...props }: Styled<ComponentProps<\"div\">>) {\n  return (\n    <div\n      data-slot=\"sidebar-group\"\n      {...props}\n      {...stylex.props(styles.group, sx)}\n    />\n  );\n}\n\nfunction SidebarGroupLabel({\n  render,\n  sx,\n  ...props\n}: Styled<useRender.ComponentProps<\"div\">>) {\n  const { ref, truncated } = useTruncated(true);\n  return useRender({\n    defaultTagName: \"div\",\n    props: mergeProps<\"div\">(\n      props,\n      stylex.props([typeStyles.textSm, styles.groupLabel], sx)\n    ),\n    ref,\n    render,\n    // Written as `data-truncated` while its text runs past its box.\n    state: { slot: \"sidebar-group-label\", truncated },\n  });\n}\n\nfunction SidebarGroupAction({\n  render,\n  sx,\n  ...props\n}: Styled<useRender.ComponentProps<\"button\">>) {\n  return useRender({\n    defaultTagName: \"button\",\n    props: mergeProps<\"button\">(\n      { type: \"button\" },\n      props,\n      stylex.props(styles.action, styles.groupAction, sx)\n    ),\n    render,\n    state: { slot: \"sidebar-group-action\" },\n  });\n}\n\nfunction SidebarGroupContent({ sx, ...props }: Styled<ComponentProps<\"div\">>) {\n  return (\n    <div\n      data-slot=\"sidebar-group-content\"\n      {...props}\n      {...stylex.props([typeStyles.textSm, styles.groupContent], sx)}\n    />\n  );\n}\n\nfunction SidebarMenu({ sx, ...props }: Styled<ComponentProps<\"ul\">>) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu\"\n      {...props}\n      {...stylex.props(styles.menu, sx)}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ sx, ...props }: Styled<ComponentProps<\"li\">>) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-item\"\n      {...props}\n      {...stylex.props(styles.menuItem, sx)}\n    />\n  );\n}\n\nconst buttonSizeStyles = {\n  default: null,\n  lg: styles.buttonLg,\n  sm: [typeStyles.textSm, styles.buttonSm],\n} satisfies Record<SidebarMenuButtonSize, stylex.StyleXStyles | null>;\n\n/**\n * A row in the menu. `isActive` marks the current page; `tooltip` names it in the icon\n * rail, and whenever its `SidebarMenuLabel` is cut off.\n */\nfunction SidebarMenuButton({\n  isActive = false,\n  render,\n  size = \"default\",\n  sx,\n  tooltip,\n  variant = \"default\",\n  withEnd = false,\n  ...props\n}: Styled<useRender.ComponentProps<\"button\">> & {\n  isActive?: boolean;\n  size?: SidebarMenuButtonSize;\n  tooltip?: ReactNode;\n  variant?: SidebarMenuButtonVariant;\n  /** Leaves room for a `SidebarMenuBadge` at its end. */\n  withEnd?: boolean;\n}) {\n  const { rail, tooltipSide } = use(RailContext);\n  const [truncated, setTruncated] = useState(false);\n  const button = useRender({\n    defaultTagName: \"button\",\n    props: mergeProps<\"button\">(\n      props,\n      stylex.props(\n        [typeStyles.textMd, styles.button],\n        variant === \"outline\" && styles.buttonOutline,\n        buttonSizeStyles[size],\n        withEnd && styles.buttonWithEnd,\n        sx\n      )\n    ),\n    render: tooltip === undefined ? render : <TooltipTrigger render={render} />,\n    // Base UI writes each state key as a `data-*` attribute.\n    state: { active: isActive, size, slot: \"sidebar-menu-button\", variant },\n  });\n\n  if (tooltip === undefined) {\n    return <LabelContext value={null}>{button}</LabelContext>;\n  }\n\n  return (\n    <LabelContext value={setTruncated}>\n      <Tooltip disabled={!(rail || truncated)}>\n        {button}\n        <TooltipContent align=\"center\" side={tooltipSide}>\n          {tooltip}\n        </TooltipContent>\n      </Tooltip>\n    </LabelContext>\n  );\n}\n\n/** A row's label: fades at the edge when cut off, and out as the sidebar narrows to its icons. */\nfunction SidebarMenuLabel({ sx, ...props }: Styled<ComponentProps<\"span\">>) {\n  const { rail } = use(RailContext);\n  const { ref, truncated } = useTruncated(!rail);\n  return (\n    <span\n      data-slot=\"sidebar-menu-label\"\n      data-truncated={truncated || undefined}\n      ref={ref}\n      {...props}\n      {...stylex.props(styles.label, sx)}\n    />\n  );\n}\n\n/** A row's shortcut, after its label; shown while the row is pointed at or focused. */\nfunction SidebarMenuKbd({ sx, ...props }: Styled<ComponentProps<\"span\">>) {\n  return (\n    <span\n      aria-hidden\n      data-slot=\"sidebar-menu-kbd\"\n      {...props}\n      {...stylex.props(styles.kbd, sx)}\n    />\n  );\n}\n\nfunction SidebarMenuAction({\n  render,\n  showOnHover = false,\n  sx,\n  ...props\n}: Styled<useRender.ComponentProps<\"button\">> & { showOnHover?: boolean }) {\n  const marker = {\n    \"data-show-on-hover\": showOnHover ? \"\" : undefined,\n    \"data-sidebar-menu-action\": \"\",\n    type: \"button\" as const,\n  };\n  return useRender({\n    defaultTagName: \"button\",\n    props: mergeProps<\"button\">(\n      marker,\n      props,\n      stylex.props(\n        styles.action,\n        styles.menuAction,\n        showOnHover && styles.onHover,\n        sx\n      )\n    ),\n    render,\n    state: { slot: \"sidebar-menu-action\" },\n  });\n}\n\nfunction SidebarMenuBadge({ sx, ...props }: Styled<ComponentProps<\"div\">>) {\n  return (\n    <div\n      data-slot=\"sidebar-menu-badge\"\n      {...props}\n      {...stylex.props([typeStyles.captionLg, styles.badge], sx)}\n    />\n  );\n}\n\n/** A row's shape while its label loads. */\nfunction SidebarMenuSkeleton({\n  showIcon = false,\n  sx,\n  ...props\n}: Styled<ComponentProps<\"div\">> & { showIcon?: boolean }) {\n  return (\n    <div\n      data-slot=\"sidebar-menu-skeleton\"\n      {...props}\n      {...stylex.props(styles.skeleton, sx)}\n    >\n      {showIcon && <Skeleton sx={styles.skeletonIcon} />}\n      <Skeleton sx={styles.skeletonText} />\n    </div>\n  );\n}\n\nfunction SidebarMenuSub({ sx, ...props }: Styled<ComponentProps<\"ul\">>) {\n  return (\n    <ul\n      data-slot=\"sidebar-menu-sub\"\n      inert={use(RailContext).collapsing}\n      {...props}\n      {...stylex.props(styles.sub, sx)}\n    />\n  );\n}\n\nfunction SidebarMenuSubItem({ sx, ...props }: Styled<ComponentProps<\"li\">>) {\n  return (\n    <li\n      data-slot=\"sidebar-menu-sub-item\"\n      {...props}\n      {...stylex.props(styles.menuItem, sx)}\n    />\n  );\n}\n\nfunction SidebarMenuSubButton({\n  isActive = false,\n  render,\n  sx,\n  ...props\n}: Styled<useRender.ComponentProps<\"a\">> & {\n  isActive?: boolean;\n}) {\n  return useRender({\n    defaultTagName: \"a\",\n    props: mergeProps<\"a\">(\n      props,\n      stylex.props([typeStyles.textMd, styles.subButton], sx)\n    ),\n    render,\n    state: { active: isActive, slot: \"sidebar-menu-sub-button\" },\n  });\n}\n\nexport {\n  Sidebar,\n  sidebarCollapsibles,\n  SidebarContent,\n  SidebarExpandedOnly,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarHeaderActions,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  sidebarMenuButtonSizes,\n  sidebarMenuButtonVariants,\n  SidebarMenuItem,\n  SidebarMenuKbd,\n  SidebarMenuLabel,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  sidebarSectionDirections,\n  SidebarSeparator,\n  sidebarSides,\n  SidebarTrigger,\n  SidebarTriggerIcon,\n  SidebarTriggerMark,\n  useSidebar,\n};\nexport type {\n  SidebarCollapsible,\n  SidebarMenuButtonSize,\n  SidebarMenuButtonVariant,\n  SidebarSectionDirection,\n  SidebarSide,\n};\n",
      "type": "registry:component"
    }
  ],
  "docs": "Usage: https://constella.aayush.cv/docs/components/sidebar/index.md",
  "type": "registry:ui"
}