@seekalfred/primitives 0.4.0 → 0.5.1

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.
Files changed (99) hide show
  1. package/dist/accordion/accordion.d.ts +5 -5
  2. package/dist/accordion/accordion.js +13 -8
  3. package/dist/accordion/accordion.js.map +1 -1
  4. package/dist/alert/alert.d.ts +2 -2
  5. package/dist/alert/alert.js +4 -2
  6. package/dist/alert/alert.js.map +1 -1
  7. package/dist/alert-dialog/alert-dialog.d.ts +1 -1
  8. package/dist/alert-dialog/alert-dialog.js +4 -2
  9. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  10. package/dist/avatar/avatar.d.ts +1 -1
  11. package/dist/avatar/avatar.js +6 -4
  12. package/dist/avatar/avatar.js.map +1 -1
  13. package/dist/badge/badge.d.ts +2 -2
  14. package/dist/badge/badge.js +4 -2
  15. package/dist/badge/badge.js.map +1 -1
  16. package/dist/breadcrumb/breadcrumb.d.ts +3 -3
  17. package/dist/breadcrumb/breadcrumb.js +7 -4
  18. package/dist/breadcrumb/breadcrumb.js.map +1 -1
  19. package/dist/button/button.d.ts +3 -3
  20. package/dist/button/button.js +4 -2
  21. package/dist/button/button.js.map +1 -1
  22. package/dist/calendar/calendar.d.ts +2 -2
  23. package/dist/calendar/calendar.js +7 -5
  24. package/dist/calendar/calendar.js.map +1 -1
  25. package/dist/card/card.d.ts +6 -6
  26. package/dist/card/card.js +19 -12
  27. package/dist/card/card.js.map +1 -1
  28. package/dist/checkbox/checkbox.d.ts +2 -2
  29. package/dist/checkbox/checkbox.js +4 -2
  30. package/dist/checkbox/checkbox.js.map +1 -1
  31. package/dist/dialog/dialog.d.ts +3 -3
  32. package/dist/dialog/dialog.js +7 -4
  33. package/dist/dialog/dialog.js.map +1 -1
  34. package/dist/index.d.ts +2 -2
  35. package/dist/index.js +2 -2
  36. package/dist/input/input.d.ts +2 -2
  37. package/dist/input/input.js +4 -2
  38. package/dist/input/input.js.map +1 -1
  39. package/dist/label/label.d.ts +1 -1
  40. package/dist/label/label.js +4 -2
  41. package/dist/label/label.js.map +1 -1
  42. package/dist/lib/cn.d.ts +55 -5
  43. package/dist/lib/cn.js +64 -6
  44. package/dist/lib/cn.js.map +1 -1
  45. package/dist/lib/merge-refs.js +22 -0
  46. package/dist/lib/merge-refs.js.map +1 -0
  47. package/dist/menu/menu.d.ts +5 -5
  48. package/dist/menu/menu.js +10 -6
  49. package/dist/menu/menu.js.map +1 -1
  50. package/dist/popover/popover.d.ts +2 -2
  51. package/dist/popover/popover.js +4 -2
  52. package/dist/popover/popover.js.map +1 -1
  53. package/dist/progress/progress.d.ts +2 -2
  54. package/dist/progress/progress.js +7 -5
  55. package/dist/progress/progress.js.map +1 -1
  56. package/dist/radio-group/radio-group.d.ts +3 -3
  57. package/dist/radio-group/radio-group.js +9 -6
  58. package/dist/radio-group/radio-group.js.map +1 -1
  59. package/dist/scroll-area/scroll-area.d.ts +7 -6
  60. package/dist/scroll-area/scroll-area.js +6 -4
  61. package/dist/scroll-area/scroll-area.js.map +1 -1
  62. package/dist/select/select.d.ts +3 -3
  63. package/dist/select/select.js +9 -6
  64. package/dist/select/select.js.map +1 -1
  65. package/dist/separator/separator.d.ts +1 -1
  66. package/dist/separator/separator.js +4 -2
  67. package/dist/separator/separator.js.map +1 -1
  68. package/dist/sheet/sheet.d.ts +1 -1
  69. package/dist/sheet/sheet.js +7 -4
  70. package/dist/sheet/sheet.js.map +1 -1
  71. package/dist/skeleton/skeleton.d.ts +1 -1
  72. package/dist/skeleton/skeleton.js +4 -2
  73. package/dist/skeleton/skeleton.js.map +1 -1
  74. package/dist/slider/slider.d.ts +2 -2
  75. package/dist/slider/slider.js +6 -4
  76. package/dist/slider/slider.js.map +1 -1
  77. package/dist/spinner/spinner.d.ts +1 -1
  78. package/dist/spinner/spinner.js +4 -2
  79. package/dist/spinner/spinner.js.map +1 -1
  80. package/dist/styles.css +1 -1
  81. package/dist/switch/switch.d.ts +2 -2
  82. package/dist/switch/switch.js +4 -2
  83. package/dist/switch/switch.js.map +1 -1
  84. package/dist/table/table.d.ts +4 -4
  85. package/dist/table/table.js +18 -17
  86. package/dist/table/table.js.map +1 -1
  87. package/dist/tabs/tabs.d.ts +4 -4
  88. package/dist/tabs/tabs.js +13 -8
  89. package/dist/tabs/tabs.js.map +1 -1
  90. package/dist/textarea/textarea.d.ts +1 -1
  91. package/dist/textarea/textarea.js +4 -2
  92. package/dist/textarea/textarea.js.map +1 -1
  93. package/dist/toast/toast.d.ts +2 -2
  94. package/dist/toast/toast.js +4 -2
  95. package/dist/toast/toast.js.map +1 -1
  96. package/dist/tooltip/tooltip.d.ts +2 -2
  97. package/dist/tooltip/tooltip.js +4 -2
  98. package/dist/tooltip/tooltip.js.map +1 -1
  99. package/package.json +3 -3
@@ -1,6 +1,6 @@
1
1
  import { VariantProps } from "class-variance-authority";
2
- import { DisclosureGroupProps, DisclosurePanelProps, DisclosureProps } from "react-aria-components";
3
2
  import { ReactNode } from "react";
3
+ import { DisclosureGroupProps, DisclosurePanelProps, DisclosureProps } from "react-aria-components";
4
4
  //#region src/accordion/accordion.d.ts
5
5
  /**
6
6
  * Accordion, ported from the legacy shadcn/Radix component onto React Aria's
@@ -50,7 +50,7 @@ export interface AccordionProps extends Omit<DisclosureGroupProps, "className" |
50
50
  /** Extra classes. Merged last, so they win conflicts with ours. */
51
51
  className?: string;
52
52
  }
53
- export declare function Accordion({ className, ...props }: AccordionProps): import("react").JSX.Element;
53
+ export declare const Accordion: import("react").ForwardRefExoticComponent<AccordionProps & import("react").RefAttributes<HTMLDivElement>>;
54
54
  /**
55
55
  * One collapsible section.
56
56
  *
@@ -60,7 +60,7 @@ export interface AccordionItemProps extends Omit<DisclosureProps, "className" |
60
60
  /** Extra classes. Merged last, so they win conflicts with ours. */
61
61
  className?: string;
62
62
  }
63
- export declare function AccordionItem({ className, ...props }: AccordionItemProps): import("react").JSX.Element;
63
+ export declare const AccordionItem: import("react").ForwardRefExoticComponent<AccordionItemProps & import("react").RefAttributes<HTMLDivElement>>;
64
64
  /**
65
65
  * The header button that expands its item.
66
66
  *
@@ -91,7 +91,7 @@ export interface AccordionTriggerProps extends VariantProps<typeof accordionTrig
91
91
  */
92
92
  headingLevel?: number;
93
93
  }
94
- export declare function AccordionTrigger({ children, icon, className, headingLevel }: AccordionTriggerProps): import("react").JSX.Element;
94
+ export declare const AccordionTrigger: import("react").ForwardRefExoticComponent<AccordionTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
95
95
  /**
96
96
  * The content revealed when its item expands.
97
97
  *
@@ -102,6 +102,6 @@ export interface AccordionPanelProps extends Omit<DisclosurePanelProps, "classNa
102
102
  /** Extra classes. Merged last, so they win conflicts with ours. */
103
103
  className?: string;
104
104
  }
105
- export declare function AccordionPanel({ className, ...props }: AccordionPanelProps): import("react").JSX.Element;
105
+ export declare const AccordionPanel: import("react").ForwardRefExoticComponent<AccordionPanelProps & import("react").RefAttributes<HTMLDivElement>>;
106
106
  //#endregion
107
107
  //# sourceMappingURL=accordion.d.ts.map
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { cva } from "class-variance-authority";
4
+ import { forwardRef } from "react";
4
5
  import { Button, Disclosure, DisclosureGroup, DisclosurePanel, Heading } from "react-aria-components";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
6
7
  //#region src/accordion/accordion.tsx
@@ -46,38 +47,42 @@ const accordionTriggerVariants = cva([
46
47
  "[&_svg]:motion-safe:transition-transform [&_svg]:motion-safe:duration-(--duration-fast)"
47
48
  ]);
48
49
  const accordionPanelVariants = cva(["pb-(--density-control-pad-md) font-sans text-sm leading-(--leading-normal) text-foreground"]);
49
- function Accordion({ className, ...props }) {
50
+ const Accordion = forwardRef(function Accordion({ className, ...props }, ref) {
50
51
  return /* @__PURE__ */ jsx(DisclosureGroup, {
52
+ ref,
51
53
  ...props,
52
54
  "data-slot": "accordion",
53
55
  className: cn(accordionVariants(), className)
54
56
  });
55
- }
56
- function AccordionItem({ className, ...props }) {
57
+ });
58
+ const AccordionItem = forwardRef(function AccordionItem({ className, ...props }, ref) {
57
59
  return /* @__PURE__ */ jsx(Disclosure, {
60
+ ref,
58
61
  ...props,
59
62
  "data-slot": "accordion-item",
60
63
  className: cn(accordionItemVariants(), className)
61
64
  });
62
- }
63
- function AccordionTrigger({ children, icon, className, headingLevel = 3 }) {
65
+ });
66
+ const AccordionTrigger = forwardRef(function AccordionTrigger({ children, icon, className, headingLevel = 3 }, ref) {
64
67
  return /* @__PURE__ */ jsx(Heading, {
65
68
  level: headingLevel,
66
69
  children: /* @__PURE__ */ jsxs(Button, {
70
+ ref,
67
71
  slot: "trigger",
68
72
  "data-slot": "accordion-trigger",
69
73
  className: cn(accordionTriggerVariants(), className),
70
74
  children: [children, icon]
71
75
  })
72
76
  });
73
- }
74
- function AccordionPanel({ className, ...props }) {
77
+ });
78
+ const AccordionPanel = forwardRef(function AccordionPanel({ className, ...props }, ref) {
75
79
  return /* @__PURE__ */ jsx(DisclosurePanel, {
80
+ ref,
76
81
  ...props,
77
82
  "data-slot": "accordion-panel",
78
83
  className: cn(accordionPanelVariants(), className)
79
84
  });
80
- }
85
+ });
81
86
  //#endregion
82
87
  export { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, accordionItemVariants, accordionPanelVariants, accordionTriggerVariants, accordionVariants };
83
88
 
@@ -1 +1 @@
1
- {"version":3,"file":"accordion.js","names":["AriaDisclosureGroup","AriaDisclosure","AriaButton","AriaDisclosurePanel"],"sources":["../../src/accordion/accordion.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n Disclosure as AriaDisclosure,\n DisclosureGroup as AriaDisclosureGroup,\n DisclosurePanel as AriaDisclosurePanel,\n Heading,\n type DisclosureGroupProps as AriaDisclosureGroupProps,\n type DisclosurePanelProps as AriaDisclosurePanelProps,\n type DisclosureProps as AriaDisclosureProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Accordion, ported from the legacy shadcn/Radix component onto React Aria's\n * Disclosure primitives.\n *\n * Every string in the cva blocks below is a STATIC literal. Tailwind scans\n * source text, so a class assembled at runtime is invisible to it and would\n * simply never be emitted -- the component would render unstyled with no\n * error anywhere.\n *\n * State comes from React Aria data attributes (`data-expanded`,\n * `data-focus-visible`, `data-disabled`), never from pseudo-classes.\n *\n * ONE BEHAVIOUR IS DELIBERATELY DROPPED. Radix moved focus between headers\n * with Home/End and the arrow keys; React Aria's useDisclosure has no key\n * handling, so each trigger is a button in the tab order and arrows do\n * nothing. The ARIA APG lists arrow navigation for accordions as optional,\n * and hand-rolling roving focus would mean owning focus management that the\n * headless base deliberately does not. The TabReachesEveryTrigger story pins\n * this so it stays a decision rather than becoming a surprise.\n */\nexport const accordionVariants = cva(\"w-full\");\n\nexport const accordionItemVariants = cva([\n // The divider between items. border-b-0 on the last child rather than\n // border-t on siblings: it survives a single-item accordion, which the\n // sibling form does not.\n \"border-b-(length:--border-width) border-b-border last:border-b-0\",\n // The chevron rotation is authored HERE, not on the trigger: React Aria\n // puts data-expanded on the Disclosure, and CSS cannot look upward from\n // the button to its expanded ancestor.\n \"data-[expanded]:[&_[data-slot=accordion-trigger]_svg]:rotate-180\",\n]);\n\nexport const accordionTriggerVariants = cva([\n \"flex w-full items-center justify-between gap-(--density-control-gap)\",\n // text-start, never text-left. Logical properties are what make RTL work\n // without a second stylesheet (ARCHITECTURE section 5 rule 4).\n \"text-start text-sm leading-(--leading-normal) font-sans font-medium text-foreground\",\n \"rounded-md py-(--density-control-pad-md)\",\n \"cursor-pointer select-none\",\n\n // The reset in @seekalfred/styles is opt-in, so a consumer may load none.\n // A bare <button> then keeps the user-agent border, ButtonFace grey and\n // Arial -- the same defence the Button base carries, for the same reason.\n \"border-(length:--border-width) border-transparent bg-transparent\",\n\n // A real outline, not a ring. The legacy component focused with\n // ring-[3px], a box-shadow, which forced-colors discards entirely -- and\n // axe has no focus-indicator rule, so nothing would have caught it.\n // outline-solid is load-bearing: outline-none sets --tw-outline-style:none\n // and the width utility resolves its style from that same variable, so\n // without it the ring gets a width and a colour and paints nothing.\n \"outline-none\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n\n \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n \"data-[hovered]:underline\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)\",\n\n // The chevron. Sized from the icon scale unless the caller sized it, and\n // never a press target of its own.\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n \"[&_svg]:text-muted-foreground\",\n // Rotation is the whole motion story for this component -- see the panel\n // note below for why the panel itself does not animate.\n \"[&_svg]:motion-safe:transition-transform [&_svg]:motion-safe:duration-(--duration-fast)\",\n]);\n\nexport const accordionPanelVariants = cva([\n // font-sans is not decoration. The panel is a plain <div> inheriting from\n // the consumer's body, and the library deliberately does not style the\n // page -- so without this the copy renders in the browser's default serif\n // while the trigger above it renders in the brand face. Caught by eye in\n // the browser, not by any assertion, which is why NoUaLeak now covers the\n // panel too.\n \"pb-(--density-control-pad-md) font-sans text-sm leading-(--leading-normal) text-foreground\",\n // No expand/collapse animation, deliberately. React Aria hides a collapsed\n // panel with the `hidden` attribute, which is display:none, and CSS cannot\n // transition out of display:none. Overriding [hidden] would restore the\n // animation by breaking the accessibility semantics that make hidden\n // correct -- the panel would stay in the accessibility tree while\n // collapsed. @starting-style with transition-behavior:allow-discrete could\n // do this properly once the browser floor allows it.\n]);\n\n/**\n * A grouping of collapsible sections.\n *\n * Expansion is KEYED, which is the sharpest break from the legacy component:\n * `type=\"single\" | \"multiple\"` becomes {@link AccordionProps.allowsMultipleExpanded},\n * and `value` / `defaultValue` strings become `expandedKeys` /\n * `defaultExpandedKeys` holding item ids. The change handler receives a\n * `Set`, not a string.\n *\n * The legacy `collapsible` prop has no equivalent and needs none: React\n * Aria's single-expand mode always allows collapsing the open item.\n *\n * @example\n * <Accordion defaultExpandedKeys={[\"billing\"]}>\n * <AccordionItem id=\"billing\">\n * <AccordionTrigger icon={<ChevronDown aria-hidden />}>Billing</AccordionTrigger>\n * <AccordionPanel>Invoices and payment methods.</AccordionPanel>\n * </AccordionItem>\n * </Accordion>\n */\nexport interface AccordionProps\n extends\n Omit<AriaDisclosureGroupProps, \"className\" | \"style\">,\n VariantProps<typeof accordionVariants> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport function Accordion({ className, ...props }: AccordionProps) {\n return (\n <AriaDisclosureGroup\n {...props}\n data-slot=\"accordion\"\n className={cn(accordionVariants(), className)}\n />\n );\n}\n\n/**\n * One collapsible section.\n *\n * `id` replaces the legacy `value`, and is what `expandedKeys` refers to.\n */\nexport interface AccordionItemProps\n extends\n Omit<AriaDisclosureProps, \"className\" | \"style\">,\n VariantProps<typeof accordionItemVariants> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport function AccordionItem({ className, ...props }: AccordionItemProps) {\n return (\n <AriaDisclosure\n {...props}\n data-slot=\"accordion-item\"\n className={cn(accordionItemVariants(), className)}\n />\n );\n}\n\n/**\n * The header button that expands its item.\n *\n * Rendered inside a {@link Heading} because screen-reader users navigate an\n * accordion by heading; that wrapper is what makes it usable rather than\n * merely operable.\n */\nexport interface AccordionTriggerProps extends VariantProps<typeof accordionTriggerVariants> {\n /** The header label. */\n children?: ReactNode;\n /**\n * The expand/collapse glyph, rendered after the label.\n *\n * Required, and supplied by the caller: this library ships no icon set, so\n * it stays icon-library agnostic. Mark the glyph `aria-hidden` -- the label\n * is the accessible name, and a second name source makes the trigger\n * announce twice. It is sized at `--icon-sm` unless it carries a `size-*`\n * class of its own, and rotates on expand.\n */\n icon: ReactNode;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * Heading level for the wrapper. Accordions appear at different depths, and\n * a fixed level would break the document outline wherever it is wrong.\n *\n * @default 3\n */\n headingLevel?: number;\n}\n\nexport function AccordionTrigger({\n children,\n icon,\n className,\n headingLevel = 3,\n}: AccordionTriggerProps) {\n return (\n <Heading level={headingLevel}>\n <AriaButton\n slot=\"trigger\"\n data-slot=\"accordion-trigger\"\n className={cn(accordionTriggerVariants(), className)}\n >\n {children}\n {icon}\n </AriaButton>\n </Heading>\n );\n}\n\n/**\n * The content revealed when its item expands.\n *\n * Named Panel rather than the legacy Content, matching React Aria and the\n * ARIA pattern.\n */\nexport interface AccordionPanelProps\n extends\n Omit<AriaDisclosurePanelProps, \"className\" | \"style\">,\n VariantProps<typeof accordionPanelVariants> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport function AccordionPanel({ className, ...props }: AccordionPanelProps) {\n return (\n <AriaDisclosurePanel\n {...props}\n data-slot=\"accordion-panel\"\n className={cn(accordionPanelVariants(), className)}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,MAAa,oBAAoB,IAAI,QAAQ;AAE7C,MAAa,wBAAwB,IAAI,CAIvC,oEAIA,kEACF,CAAC;AAED,MAAa,2BAA2B,IAAI;CAC1C;CAGA;CACA;CACA;CAKA;CAQA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CAIA;CACA;CACA;CAGA;AACF,CAAC;AAED,MAAa,yBAAyB,IAAI,CAOxC,4FAQF,CAAC;AA8BD,SAAgB,UAAU,EAAE,WAAW,GAAG,SAAyB;CACjE,OACE,oBAACA,iBAAD;EACE,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,kBAAkB,GAAG,SAAS;CAC7C,CAAA;AAEL;AAeA,SAAgB,cAAc,EAAE,WAAW,GAAG,SAA6B;CACzE,OACE,oBAACC,YAAD;EACE,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,sBAAsB,GAAG,SAAS;CACjD,CAAA;AAEL;AAiCA,SAAgB,iBAAiB,EAC/B,UACA,MACA,WACA,eAAe,KACS;CACxB,OACE,oBAAC,SAAD;EAAS,OAAO;EACd,UAAA,qBAACC,QAAD;GACE,MAAK;GACL,aAAU;GACV,WAAW,GAAG,yBAAyB,GAAG,SAAS;GAHrD,UAAA,CAKG,UACA,IACS;;CACL,CAAA;AAEb;AAgBA,SAAgB,eAAe,EAAE,WAAW,GAAG,SAA8B;CAC3E,OACE,oBAACC,iBAAD;EACE,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,uBAAuB,GAAG,SAAS;CAClD,CAAA;AAEL"}
1
+ {"version":3,"file":"accordion.js","names":["AriaDisclosureGroup","AriaDisclosure","AriaButton","AriaDisclosurePanel"],"sources":["../../src/accordion/accordion.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n Disclosure as AriaDisclosure,\n DisclosureGroup as AriaDisclosureGroup,\n DisclosurePanel as AriaDisclosurePanel,\n Heading,\n type DisclosureGroupProps as AriaDisclosureGroupProps,\n type DisclosurePanelProps as AriaDisclosurePanelProps,\n type DisclosureProps as AriaDisclosureProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Accordion, ported from the legacy shadcn/Radix component onto React Aria's\n * Disclosure primitives.\n *\n * Every string in the cva blocks below is a STATIC literal. Tailwind scans\n * source text, so a class assembled at runtime is invisible to it and would\n * simply never be emitted -- the component would render unstyled with no\n * error anywhere.\n *\n * State comes from React Aria data attributes (`data-expanded`,\n * `data-focus-visible`, `data-disabled`), never from pseudo-classes.\n *\n * ONE BEHAVIOUR IS DELIBERATELY DROPPED. Radix moved focus between headers\n * with Home/End and the arrow keys; React Aria's useDisclosure has no key\n * handling, so each trigger is a button in the tab order and arrows do\n * nothing. The ARIA APG lists arrow navigation for accordions as optional,\n * and hand-rolling roving focus would mean owning focus management that the\n * headless base deliberately does not. The TabReachesEveryTrigger story pins\n * this so it stays a decision rather than becoming a surprise.\n */\nexport const accordionVariants = cva(\"w-full\");\n\nexport const accordionItemVariants = cva([\n // The divider between items. border-b-0 on the last child rather than\n // border-t on siblings: it survives a single-item accordion, which the\n // sibling form does not.\n \"border-b-(length:--border-width) border-b-border last:border-b-0\",\n // The chevron rotation is authored HERE, not on the trigger: React Aria\n // puts data-expanded on the Disclosure, and CSS cannot look upward from\n // the button to its expanded ancestor.\n \"data-[expanded]:[&_[data-slot=accordion-trigger]_svg]:rotate-180\",\n]);\n\nexport const accordionTriggerVariants = cva([\n \"flex w-full items-center justify-between gap-(--density-control-gap)\",\n // text-start, never text-left. Logical properties are what make RTL work\n // without a second stylesheet (ARCHITECTURE section 5 rule 4).\n \"text-start text-sm leading-(--leading-normal) font-sans font-medium text-foreground\",\n \"rounded-md py-(--density-control-pad-md)\",\n \"cursor-pointer select-none\",\n\n // The reset in @seekalfred/styles is opt-in, so a consumer may load none.\n // A bare <button> then keeps the user-agent border, ButtonFace grey and\n // Arial -- the same defence the Button base carries, for the same reason.\n \"border-(length:--border-width) border-transparent bg-transparent\",\n\n // A real outline, not a ring. The legacy component focused with\n // ring-[3px], a box-shadow, which forced-colors discards entirely -- and\n // axe has no focus-indicator rule, so nothing would have caught it.\n // outline-solid is load-bearing: outline-none sets --tw-outline-style:none\n // and the width utility resolves its style from that same variable, so\n // without it the ring gets a width and a colour and paints nothing.\n \"outline-none\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n\n \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n \"data-[hovered]:underline\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)\",\n\n // The chevron. Sized from the icon scale unless the caller sized it, and\n // never a press target of its own.\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n \"[&_svg]:text-muted-foreground\",\n // Rotation is the whole motion story for this component -- see the panel\n // note below for why the panel itself does not animate.\n \"[&_svg]:motion-safe:transition-transform [&_svg]:motion-safe:duration-(--duration-fast)\",\n]);\n\nexport const accordionPanelVariants = cva([\n // font-sans is not decoration. The panel is a plain <div> inheriting from\n // the consumer's body, and the library deliberately does not style the\n // page -- so without this the copy renders in the browser's default serif\n // while the trigger above it renders in the brand face. Caught by eye in\n // the browser, not by any assertion, which is why NoUaLeak now covers the\n // panel too.\n \"pb-(--density-control-pad-md) font-sans text-sm leading-(--leading-normal) text-foreground\",\n // No expand/collapse animation, deliberately. React Aria hides a collapsed\n // panel with the `hidden` attribute, which is display:none, and CSS cannot\n // transition out of display:none. Overriding [hidden] would restore the\n // animation by breaking the accessibility semantics that make hidden\n // correct -- the panel would stay in the accessibility tree while\n // collapsed. @starting-style with transition-behavior:allow-discrete could\n // do this properly once the browser floor allows it.\n]);\n\n/**\n * A grouping of collapsible sections.\n *\n * Expansion is KEYED, which is the sharpest break from the legacy component:\n * `type=\"single\" | \"multiple\"` becomes {@link AccordionProps.allowsMultipleExpanded},\n * and `value` / `defaultValue` strings become `expandedKeys` /\n * `defaultExpandedKeys` holding item ids. The change handler receives a\n * `Set`, not a string.\n *\n * The legacy `collapsible` prop has no equivalent and needs none: React\n * Aria's single-expand mode always allows collapsing the open item.\n *\n * @example\n * <Accordion defaultExpandedKeys={[\"billing\"]}>\n * <AccordionItem id=\"billing\">\n * <AccordionTrigger icon={<ChevronDown aria-hidden />}>Billing</AccordionTrigger>\n * <AccordionPanel>Invoices and payment methods.</AccordionPanel>\n * </AccordionItem>\n * </Accordion>\n */\nexport interface AccordionProps\n extends\n Omit<AriaDisclosureGroupProps, \"className\" | \"style\">,\n VariantProps<typeof accordionVariants> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const Accordion = forwardRef<HTMLDivElement, AccordionProps>(function Accordion(\n { className, ...props },\n ref,\n) {\n return (\n <AriaDisclosureGroup\n ref={ref}\n {...props}\n data-slot=\"accordion\"\n className={cn(accordionVariants(), className)}\n />\n );\n});\n\n/**\n * One collapsible section.\n *\n * `id` replaces the legacy `value`, and is what `expandedKeys` refers to.\n */\nexport interface AccordionItemProps\n extends\n Omit<AriaDisclosureProps, \"className\" | \"style\">,\n VariantProps<typeof accordionItemVariants> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const AccordionItem = forwardRef<HTMLDivElement, AccordionItemProps>(function AccordionItem(\n { className, ...props },\n ref,\n) {\n return (\n <AriaDisclosure\n ref={ref}\n {...props}\n data-slot=\"accordion-item\"\n className={cn(accordionItemVariants(), className)}\n />\n );\n});\n\n/**\n * The header button that expands its item.\n *\n * Rendered inside a {@link Heading} because screen-reader users navigate an\n * accordion by heading; that wrapper is what makes it usable rather than\n * merely operable.\n */\nexport interface AccordionTriggerProps extends VariantProps<typeof accordionTriggerVariants> {\n /** The header label. */\n children?: ReactNode;\n /**\n * The expand/collapse glyph, rendered after the label.\n *\n * Required, and supplied by the caller: this library ships no icon set, so\n * it stays icon-library agnostic. Mark the glyph `aria-hidden` -- the label\n * is the accessible name, and a second name source makes the trigger\n * announce twice. It is sized at `--icon-sm` unless it carries a `size-*`\n * class of its own, and rotates on expand.\n */\n icon: ReactNode;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * Heading level for the wrapper. Accordions appear at different depths, and\n * a fixed level would break the document outline wherever it is wrong.\n *\n * @default 3\n */\n headingLevel?: number;\n}\n\nexport const AccordionTrigger = forwardRef<HTMLButtonElement, AccordionTriggerProps>(\n function AccordionTrigger({ children, icon, className, headingLevel = 3 }, ref) {\n return (\n <Heading level={headingLevel}>\n <AriaButton\n // The ref goes on the button, not the Heading wrapping it: Heading has\n // no interactive behaviour, while the button is what expands/collapses\n // and carries the press/focus data attributes.\n ref={ref}\n slot=\"trigger\"\n data-slot=\"accordion-trigger\"\n className={cn(accordionTriggerVariants(), className)}\n >\n {children}\n {icon}\n </AriaButton>\n </Heading>\n );\n },\n);\n\n/**\n * The content revealed when its item expands.\n *\n * Named Panel rather than the legacy Content, matching React Aria and the\n * ARIA pattern.\n */\nexport interface AccordionPanelProps\n extends\n Omit<AriaDisclosurePanelProps, \"className\" | \"style\">,\n VariantProps<typeof accordionPanelVariants> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const AccordionPanel = forwardRef<HTMLDivElement, AccordionPanelProps>(\n function AccordionPanel({ className, ...props }, ref) {\n return (\n <AriaDisclosurePanel\n ref={ref}\n {...props}\n data-slot=\"accordion-panel\"\n className={cn(accordionPanelVariants(), className)}\n />\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,MAAa,oBAAoB,IAAI,QAAQ;AAE7C,MAAa,wBAAwB,IAAI,CAIvC,oEAIA,kEACF,CAAC;AAED,MAAa,2BAA2B,IAAI;CAC1C;CAGA;CACA;CACA;CAKA;CAQA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CAIA;CACA;CACA;CAGA;AACF,CAAC;AAED,MAAa,yBAAyB,IAAI,CAOxC,4FAQF,CAAC;AA8BD,MAAa,YAAY,WAA2C,SAAS,UAC3E,EAAE,WAAW,GAAG,SAChB,KACA;CACA,OACE,oBAACA,iBAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,kBAAkB,GAAG,SAAS;CAC7C,CAAA;AAEL,CAAC;AAeD,MAAa,gBAAgB,WAA+C,SAAS,cACnF,EAAE,WAAW,GAAG,SAChB,KACA;CACA,OACE,oBAACC,YAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,sBAAsB,GAAG,SAAS;CACjD,CAAA;AAEL,CAAC;AAiCD,MAAa,mBAAmB,WAC9B,SAAS,iBAAiB,EAAE,UAAU,MAAM,WAAW,eAAe,KAAK,KAAK;CAC9E,OACE,oBAAC,SAAD;EAAS,OAAO;EACd,UAAA,qBAACC,QAAD;GAIO;GACL,MAAK;GACL,aAAU;GACV,WAAW,GAAG,yBAAyB,GAAG,SAAS;GAPrD,UAAA,CASG,UACA,IACS;;CACL,CAAA;AAEb,CACF;AAgBA,MAAa,iBAAiB,WAC5B,SAAS,eAAe,EAAE,WAAW,GAAG,SAAS,KAAK;CACpD,OACE,oBAACC,iBAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,uBAAuB,GAAG,SAAS;CAClD,CAAA;AAEL,CACF"}
@@ -24,7 +24,7 @@ import { ComponentPropsWithoutRef, ReactNode } from "react";
24
24
  * problems rather than porting them.
25
25
  */
26
26
  export declare const alertVariants: (props?: ({
27
- variant?: "default" | "destructive" | null | undefined;
27
+ variant?: "destructive" | "default" | null | undefined;
28
28
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
29
29
  /**
30
30
  * An inline status message.
@@ -63,6 +63,6 @@ export interface AlertProps extends Omit<ComponentPropsWithoutRef<"div">, "title
63
63
  */
64
64
  headingLevel?: 2 | 3 | 4 | 5 | 6;
65
65
  }
66
- export declare function Alert({ className, variant, icon, title, headingLevel, children, ...props }: AlertProps): import("react").JSX.Element;
66
+ export declare const Alert: import("react").ForwardRefExoticComponent<AlertProps & import("react").RefAttributes<HTMLDivElement>>;
67
67
  //#endregion
68
68
  //# sourceMappingURL=alert.d.ts.map
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { cva } from "class-variance-authority";
4
+ import { forwardRef } from "react";
4
5
  import { jsx, jsxs } from "react/jsx-runtime";
5
6
  //#region src/alert/alert.tsx
6
7
  /**
@@ -36,9 +37,10 @@ const alertVariants = cva([
36
37
  } },
37
38
  defaultVariants: { variant: "default" }
38
39
  });
39
- function Alert({ className, variant, icon, title, headingLevel = 3, children, ...props }) {
40
+ const Alert = forwardRef(function Alert({ className, variant, icon, title, headingLevel = 3, children, ...props }, ref) {
40
41
  const Title = `h${headingLevel}`;
41
42
  return /* @__PURE__ */ jsxs("div", {
43
+ ref,
42
44
  ...props,
43
45
  role: "alert",
44
46
  "data-slot": "alert",
@@ -60,7 +62,7 @@ function Alert({ className, variant, icon, title, headingLevel = 3, children, ..
60
62
  })]
61
63
  })]
62
64
  });
63
- }
65
+ });
64
66
  //#endregion
65
67
  export { Alert, alertVariants };
66
68
 
@@ -1 +1 @@
1
- {"version":3,"file":"alert.js","names":[],"sources":["../../src/alert/alert.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Alert, ported from the legacy shadcn component.\n *\n * There is no React Aria base: RAC ships no static inline alert (its Toast\n * API is transient and queue-driven), so this is a styled div with\n * role=\"alert\". Because it is not RAC-backed, its props follow\n * ARCHITECTURE section 7 rule 1 -- VariantProps & ComponentPropsWithoutRef --\n * rather than the Button's Omit-from-React-Aria departure, which exists only\n * to stop RAC-ignored props being advertised.\n *\n * Every string below is a STATIC literal. Tailwind scans source text, so a\n * class assembled at runtime is invisible to it and would never be emitted.\n *\n * THE LAYOUT IS THE BIGGEST CHANGE. The legacy component positioned its icon\n * with `[&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4`, pushed the text over\n * with `[&>svg~*]:pl-7`, and nudged it back with `[&>svg+div]:translate-y-[-3px]`.\n * All three are physical properties or magic numbers: `left` and `pl` do not\n * mirror in RTL, so the icon stayed left while the text indented the wrong\n * side, and `translate-y-[-3px]` is a bracket value that `check:hardcoding`\n * rejects outright. Flex with an explicit `icon` prop deletes all three\n * problems rather than porting them.\n */\nexport const alertVariants = cva(\n [\n // box-border is not boilerplate. Tailwind Preflight sets box-sizing\n // globally and the legacy app had it; this library's reset is OPT-IN, so\n // a consumer may load none and the browser default content-box applies.\n // w-full is width:100%, so the padding and border are then ADDED to it\n // and the alert overflows its container by exactly 46px at comfortable\n // density. Measured, not theorised: 346px inside a 300px parent.\n \"box-border flex w-full items-start gap-(--density-control-gap)\",\n \"rounded-lg border-(length:--border-width) p-(--density-control-pad-md)\",\n // font-sans is load-bearing. The library does not style the consumer's\n // page, so anything that does not set its own face inherits the host's\n // default -- which is a serif in an unstyled document. The accordion\n // panel shipped exactly that way and it was caught by eye, not by a test.\n \"font-sans text-sm leading-(--leading-normal)\",\n ],\n {\n variants: {\n variant: {\n default: \"bg-background text-foreground border-border\",\n // destructive-STRONG, not destructive. The fill token is 4.04:1 on\n // the dark page; this role exists so the text and border clear the\n // 4.5 floor in both themes. The legacy `border-destructive/50` was\n // also an opacity modifier, which cannot be contrast-checked at all\n // because the result depends on whatever sits behind it.\n destructive: \"bg-background text-destructive-strong border-destructive-strong\",\n },\n },\n defaultVariants: { variant: \"default\" },\n },\n);\n\n/**\n * An inline status message.\n *\n * Announced by assistive technology the moment it renders, via `role=\"alert\"`.\n * That makes it the wrong component for content that is present on load and\n * merely styled as a notice -- use a plain region for those.\n *\n * @example\n * <Alert variant=\"destructive\" icon={<WarningIcon aria-hidden />} title=\"Save failed\">\n * Your changes were not saved.\n * </Alert>\n */\nexport interface AlertProps\n // The native `title` attribute is omitted so this component can use the\n // name for its heading. That attribute renders a browser tooltip, which is\n // not keyboard reachable and is not announced reliably -- on a role=\"alert\"\n // it is worth nothing, and silently shadowing it with a ReactNode would be\n // a type error waiting for the first consumer who tried both.\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"title\">, VariantProps<typeof alertVariants> {\n /**\n * Leading glyph. Mark it `aria-hidden` -- the message text is the\n * announcement, and an unhidden graphic makes the alert announce twice.\n *\n * A prop rather than a positioned child: the legacy `[&>svg]` selectors\n * only worked when the icon was the first element, and broke in RTL.\n */\n icon?: ReactNode;\n /**\n * Optional heading. Replaces the legacy `AlertTitle` part.\n *\n * Omit it for a single-line alert; most call sites do.\n */\n title?: ReactNode;\n /**\n * Heading level for {@link AlertProps.title}.\n *\n * The legacy `AlertTitle` was always an `<h5>`, which breaks the document\n * outline anywhere the alert is not nested five deep.\n *\n * @default 3\n */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n}\n\nexport function Alert({\n className,\n variant,\n icon,\n title,\n headingLevel = 3,\n children,\n ...props\n}: AlertProps) {\n const Title = `h${headingLevel}` as const;\n\n return (\n <div\n {...props}\n role=\"alert\"\n data-slot=\"alert\"\n className={cn(alertVariants({ variant }), className)}\n >\n {/*\n * The glyph is sized from the icon scale unless the caller sized it,\n * and never becomes a press target. shrink-0 keeps it square when the\n * message wraps.\n */}\n {icon === undefined ? null : (\n <span\n data-slot=\"alert-icon\"\n className=\"shrink-0 [&>svg]:pointer-events-none [&>svg:not([class*='size-'])]:size-(--icon-sm)\"\n >\n {icon}\n </span>\n )}\n <div data-slot=\"alert-body\" className=\"min-w-0 flex-1\">\n {title === undefined ? null : (\n // leading-* AFTER text-*, always: tailwind-merge treats a later\n // text-* as owning line-height and silently strips a leading-*\n // that precedes it.\n <Title\n data-slot=\"alert-title\"\n className=\"mb-1 text-sm leading-(--leading-tight) font-medium\"\n >\n {title}\n </Title>\n )}\n {children === undefined ? null : <div data-slot=\"alert-description\">{children}</div>}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,gBAAgB,IAC3B;CAOE;CACA;CAKA;AACF,GACA;CACE,UAAU,EACR,SAAS;EACP,SAAS;EAMT,aAAa;CACf,EACF;CACA,iBAAiB,EAAE,SAAS,UAAU;AACxC,CACF;AA8CA,SAAgB,MAAM,EACpB,WACA,SACA,MACA,OACA,eAAe,GACf,UACA,GAAG,SACU;CACb,MAAM,QAAQ,IAAI;CAElB,OACE,qBAAC,OAAD;EACE,GAAI;EACJ,MAAK;EACL,aAAU;EACV,WAAW,GAAG,cAAc,EAAE,QAAQ,CAAC,GAAG,SAAS;EAJrD,UAAA,CAWG,SAAS,KAAA,IAAY,OACpB,oBAAC,QAAD;GACE,aAAU;GACV,WAAU;GAET,UAAA;EACG,CAAA,GAER,qBAAC,OAAD;GAAK,aAAU;GAAa,WAAU;GAAtC,UAAA,CACG,UAAU,KAAA,IAAY,OAIrB,oBAAC,OAAD;IACE,aAAU;IACV,WAAU;IAET,UAAA;GACI,CAAA,GAER,aAAa,KAAA,IAAY,OAAO,oBAAC,OAAD;IAAK,aAAU;IAAqB;GAAc,CAAA,CAChF;EACF,CAAA,CAAA;;AAET"}
1
+ {"version":3,"file":"alert.js","names":[],"sources":["../../src/alert/alert.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, type ComponentPropsWithoutRef, type ReactNode } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Alert, ported from the legacy shadcn component.\n *\n * There is no React Aria base: RAC ships no static inline alert (its Toast\n * API is transient and queue-driven), so this is a styled div with\n * role=\"alert\". Because it is not RAC-backed, its props follow\n * ARCHITECTURE section 7 rule 1 -- VariantProps & ComponentPropsWithoutRef --\n * rather than the Button's Omit-from-React-Aria departure, which exists only\n * to stop RAC-ignored props being advertised.\n *\n * Every string below is a STATIC literal. Tailwind scans source text, so a\n * class assembled at runtime is invisible to it and would never be emitted.\n *\n * THE LAYOUT IS THE BIGGEST CHANGE. The legacy component positioned its icon\n * with `[&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4`, pushed the text over\n * with `[&>svg~*]:pl-7`, and nudged it back with `[&>svg+div]:translate-y-[-3px]`.\n * All three are physical properties or magic numbers: `left` and `pl` do not\n * mirror in RTL, so the icon stayed left while the text indented the wrong\n * side, and `translate-y-[-3px]` is a bracket value that `check:hardcoding`\n * rejects outright. Flex with an explicit `icon` prop deletes all three\n * problems rather than porting them.\n */\nexport const alertVariants = cva(\n [\n // box-border is not boilerplate. Tailwind Preflight sets box-sizing\n // globally and the legacy app had it; this library's reset is OPT-IN, so\n // a consumer may load none and the browser default content-box applies.\n // w-full is width:100%, so the padding and border are then ADDED to it\n // and the alert overflows its container by exactly 46px at comfortable\n // density. Measured, not theorised: 346px inside a 300px parent.\n \"box-border flex w-full items-start gap-(--density-control-gap)\",\n \"rounded-lg border-(length:--border-width) p-(--density-control-pad-md)\",\n // font-sans is load-bearing. The library does not style the consumer's\n // page, so anything that does not set its own face inherits the host's\n // default -- which is a serif in an unstyled document. The accordion\n // panel shipped exactly that way and it was caught by eye, not by a test.\n \"font-sans text-sm leading-(--leading-normal)\",\n ],\n {\n variants: {\n variant: {\n default: \"bg-background text-foreground border-border\",\n // destructive-STRONG, not destructive. The fill token is 4.04:1 on\n // the dark page; this role exists so the text and border clear the\n // 4.5 floor in both themes. The legacy `border-destructive/50` was\n // also an opacity modifier, which cannot be contrast-checked at all\n // because the result depends on whatever sits behind it.\n destructive: \"bg-background text-destructive-strong border-destructive-strong\",\n },\n },\n defaultVariants: { variant: \"default\" },\n },\n);\n\n/**\n * An inline status message.\n *\n * Announced by assistive technology the moment it renders, via `role=\"alert\"`.\n * That makes it the wrong component for content that is present on load and\n * merely styled as a notice -- use a plain region for those.\n *\n * @example\n * <Alert variant=\"destructive\" icon={<WarningIcon aria-hidden />} title=\"Save failed\">\n * Your changes were not saved.\n * </Alert>\n */\nexport interface AlertProps\n // The native `title` attribute is omitted so this component can use the\n // name for its heading. That attribute renders a browser tooltip, which is\n // not keyboard reachable and is not announced reliably -- on a role=\"alert\"\n // it is worth nothing, and silently shadowing it with a ReactNode would be\n // a type error waiting for the first consumer who tried both.\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"title\">, VariantProps<typeof alertVariants> {\n /**\n * Leading glyph. Mark it `aria-hidden` -- the message text is the\n * announcement, and an unhidden graphic makes the alert announce twice.\n *\n * A prop rather than a positioned child: the legacy `[&>svg]` selectors\n * only worked when the icon was the first element, and broke in RTL.\n */\n icon?: ReactNode;\n /**\n * Optional heading. Replaces the legacy `AlertTitle` part.\n *\n * Omit it for a single-line alert; most call sites do.\n */\n title?: ReactNode;\n /**\n * Heading level for {@link AlertProps.title}.\n *\n * The legacy `AlertTitle` was always an `<h5>`, which breaks the document\n * outline anywhere the alert is not nested five deep.\n *\n * @default 3\n */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n}\n\nexport const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(\n { className, variant, icon, title, headingLevel = 3, children, ...props },\n ref,\n) {\n const Title = `h${headingLevel}` as const;\n\n return (\n <div\n ref={ref}\n {...props}\n role=\"alert\"\n data-slot=\"alert\"\n className={cn(alertVariants({ variant }), className)}\n >\n {/*\n * The glyph is sized from the icon scale unless the caller sized it,\n * and never becomes a press target. shrink-0 keeps it square when the\n * message wraps.\n */}\n {icon === undefined ? null : (\n <span\n data-slot=\"alert-icon\"\n className=\"shrink-0 [&>svg]:pointer-events-none [&>svg:not([class*='size-'])]:size-(--icon-sm)\"\n >\n {icon}\n </span>\n )}\n <div data-slot=\"alert-body\" className=\"min-w-0 flex-1\">\n {title === undefined ? null : (\n // leading-* AFTER text-*, always: tailwind-merge treats a later\n // text-* as owning line-height and silently strips a leading-*\n // that precedes it.\n <Title\n data-slot=\"alert-title\"\n className=\"mb-1 text-sm leading-(--leading-tight) font-medium\"\n >\n {title}\n </Title>\n )}\n {children === undefined ? null : <div data-slot=\"alert-description\">{children}</div>}\n </div>\n </div>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,gBAAgB,IAC3B;CAOE;CACA;CAKA;AACF,GACA;CACE,UAAU,EACR,SAAS;EACP,SAAS;EAMT,aAAa;CACf,EACF;CACA,iBAAiB,EAAE,SAAS,UAAU;AACxC,CACF;AA8CA,MAAa,QAAQ,WAAuC,SAAS,MACnE,EAAE,WAAW,SAAS,MAAM,OAAO,eAAe,GAAG,UAAU,GAAG,SAClE,KACA;CACA,MAAM,QAAQ,IAAI;CAElB,OACE,qBAAC,OAAD;EACO;EACL,GAAI;EACJ,MAAK;EACL,aAAU;EACV,WAAW,GAAG,cAAc,EAAE,QAAQ,CAAC,GAAG,SAAS;EALrD,UAAA,CAYG,SAAS,KAAA,IAAY,OACpB,oBAAC,QAAD;GACE,aAAU;GACV,WAAU;GAET,UAAA;EACG,CAAA,GAER,qBAAC,OAAD;GAAK,aAAU;GAAa,WAAU;GAAtC,UAAA,CACG,UAAU,KAAA,IAAY,OAIrB,oBAAC,OAAD;IACE,aAAU;IACV,WAAU;IAET,UAAA;GACI,CAAA,GAER,aAAa,KAAA,IAAY,OAAO,oBAAC,OAAD;IAAK,aAAU;IAAqB;GAAc,CAAA,CAChF;EACF,CAAA,CAAA;;AAET,CAAC"}
@@ -48,6 +48,6 @@ export interface AlertDialogProps extends Omit<DialogProps, "role" | "closeButto
48
48
  /** The actions. Give the cancel button `slot="close"`. */
49
49
  children?: ReactNode;
50
50
  }
51
- export declare function AlertDialog(props: AlertDialogProps): import("react").JSX.Element;
51
+ export declare const AlertDialog: import("react").ForwardRefExoticComponent<AlertDialogProps & import("react").RefAttributes<HTMLElement>>;
52
52
  //#endregion
53
53
  //# sourceMappingURL=alert-dialog.d.ts.map
@@ -1,15 +1,17 @@
1
1
  "use client";
2
2
  import { Dialog } from "../dialog/dialog.js";
3
+ import { forwardRef } from "react";
3
4
  import { jsx } from "react/jsx-runtime";
4
5
  //#region src/alert-dialog/alert-dialog.tsx
5
- function AlertDialog(props) {
6
+ const AlertDialog = forwardRef(function AlertDialog(props, ref) {
6
7
  return /* @__PURE__ */ jsx(Dialog, {
7
8
  ...props,
9
+ ref,
8
10
  role: "alertdialog",
9
11
  closeButton: false,
10
12
  isDismissable: false
11
13
  });
12
- }
14
+ });
13
15
  //#endregion
14
16
  export { AlertDialog };
15
17
 
@@ -1 +1 @@
1
- {"version":3,"file":"alert-dialog.js","names":[],"sources":["../../src/alert-dialog/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ReactNode } from \"react\";\n\nimport { Dialog, type DialogProps } from \"../dialog/dialog.js\";\n\n/**\n * AlertDialog: a confirmation that must be answered.\n *\n * A PRESET over `Dialog`, not a second implementation. The legacy\n * `alert-dialog.tsx` is a 157-line clone of `dialog.tsx` with three\n * differences, and keeping two copies is how they drift -- the legacy pair\n * already disagree about `leading-none` on their titles for no stated\n * reason.\n *\n * Census: 2 files, all eight parts used twice each, `AlertDialogPortal`,\n * `AlertDialogOverlay` and `AlertDialogTrigger` never rendered externally.\n *\n * What the preset forces, and why each one is a defect if forgotten:\n *\n * - `role=\"alertdialog\"`. React Aria associates the description with\n * `aria-describedby` ONLY for this role (useDialog.mjs:29), so a\n * confirmation built from a plain Dialog loses the sentence explaining\n * what is about to happen.\n * - `closeButton={false}`. An X in the corner is a third answer to a\n * two-answer question, and it is ambiguous: it reads as \"cancel\" but\n * looks like \"dismiss\".\n * - `isDismissable={false}`. Clicking the page must not resolve a\n * destructive confirmation. Escape still cancels, which is the APG\n * behaviour and what Radix does.\n * - `description` is REQUIRED. A confirmation with no explanation is the\n * thing this component exists to prevent, and the role only associates it\n * when it is there.\n *\n * NO `AlertDialogAction` OR `AlertDialogCancel`. The legacy shipped both,\n * with `AlertDialogAction` defaulting to the gradient primary button -- and\n * BOTH call sites override it back to destructive by hand\n * (`data-table.tsx:146`, `calendar-view.tsx:638`). A default that every\n * caller fights is not a default. Use a `Button` and say which one it is:\n * `slot=\"close\"` cancels, and the confirm button picks its own variant.\n */\nexport interface AlertDialogProps\n extends Omit<DialogProps, \"role\" | \"closeButton\" | \"closeLabel\" | \"isDismissable\"> {\n /**\n * What is about to happen, and whether it can be undone.\n *\n * **Required**, unlike on `Dialog`. `role=\"alertdialog\"` is what makes\n * React Aria wire this to `aria-describedby`, so it is both the sentence\n * the user needs and the one the screen reader announces with the name.\n */\n description: ReactNode;\n /** The actions. Give the cancel button `slot=\"close\"`. */\n children?: ReactNode;\n}\n\nexport function AlertDialog(props: AlertDialogProps) {\n return <Dialog {...props} role=\"alertdialog\" closeButton={false} isDismissable={false} />;\n}\n"],"mappings":";;;;AAuDA,SAAgB,YAAY,OAAyB;CACnD,OAAO,oBAAC,QAAD;EAAQ,GAAI;EAAO,MAAK;EAAc,aAAa;EAAO,eAAe;CAAQ,CAAA;AAC1F"}
1
+ {"version":3,"file":"alert-dialog.js","names":[],"sources":["../../src/alert-dialog/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, type ReactNode } from \"react\";\n\nimport { Dialog, type DialogProps } from \"../dialog/dialog.js\";\n\n/**\n * AlertDialog: a confirmation that must be answered.\n *\n * A PRESET over `Dialog`, not a second implementation. The legacy\n * `alert-dialog.tsx` is a 157-line clone of `dialog.tsx` with three\n * differences, and keeping two copies is how they drift -- the legacy pair\n * already disagree about `leading-none` on their titles for no stated\n * reason.\n *\n * Census: 2 files, all eight parts used twice each, `AlertDialogPortal`,\n * `AlertDialogOverlay` and `AlertDialogTrigger` never rendered externally.\n *\n * What the preset forces, and why each one is a defect if forgotten:\n *\n * - `role=\"alertdialog\"`. React Aria associates the description with\n * `aria-describedby` ONLY for this role (useDialog.mjs:29), so a\n * confirmation built from a plain Dialog loses the sentence explaining\n * what is about to happen.\n * - `closeButton={false}`. An X in the corner is a third answer to a\n * two-answer question, and it is ambiguous: it reads as \"cancel\" but\n * looks like \"dismiss\".\n * - `isDismissable={false}`. Clicking the page must not resolve a\n * destructive confirmation. Escape still cancels, which is the APG\n * behaviour and what Radix does.\n * - `description` is REQUIRED. A confirmation with no explanation is the\n * thing this component exists to prevent, and the role only associates it\n * when it is there.\n *\n * NO `AlertDialogAction` OR `AlertDialogCancel`. The legacy shipped both,\n * with `AlertDialogAction` defaulting to the gradient primary button -- and\n * BOTH call sites override it back to destructive by hand\n * (`data-table.tsx:146`, `calendar-view.tsx:638`). A default that every\n * caller fights is not a default. Use a `Button` and say which one it is:\n * `slot=\"close\"` cancels, and the confirm button picks its own variant.\n */\nexport interface AlertDialogProps\n extends Omit<DialogProps, \"role\" | \"closeButton\" | \"closeLabel\" | \"isDismissable\"> {\n /**\n * What is about to happen, and whether it can be undone.\n *\n * **Required**, unlike on `Dialog`. `role=\"alertdialog\"` is what makes\n * React Aria wire this to `aria-describedby`, so it is both the sentence\n * the user needs and the one the screen reader announces with the name.\n */\n description: ReactNode;\n /** The actions. Give the cancel button `slot=\"close\"`. */\n children?: ReactNode;\n}\n\nexport const AlertDialog = forwardRef<HTMLElement, AlertDialogProps>(function AlertDialog(\n props,\n ref,\n) {\n return (\n <Dialog {...props} ref={ref} role=\"alertdialog\" closeButton={false} isDismissable={false} />\n );\n});\n"],"mappings":";;;;;AAuDA,MAAa,cAAc,WAA0C,SAAS,YAC5E,OACA,KACA;CACA,OACE,oBAAC,QAAD;EAAQ,GAAI;EAAY;EAAK,MAAK;EAAc,aAAa;EAAO,eAAe;CAAQ,CAAA;AAE/F,CAAC"}
@@ -45,6 +45,6 @@ export interface AvatarProps extends Omit<ComponentPropsWithoutRef<"span">, "chi
45
45
  */
46
46
  fallback?: ReactNode;
47
47
  }
48
- export declare function Avatar({ className, size, src, alt, fallback, ...props }: AvatarProps): import("react").JSX.Element;
48
+ export declare const Avatar: import("react").ForwardRefExoticComponent<AvatarProps & import("react").RefAttributes<HTMLImageElement>>;
49
49
  //#endregion
50
50
  //# sourceMappingURL=avatar.d.ts.map
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
+ import { mergeRefs } from "../lib/merge-refs.js";
3
4
  import { cva } from "class-variance-authority";
5
+ import { forwardRef, useCallback, useMemo, useState } from "react";
4
6
  import { jsx, jsxs } from "react/jsx-runtime";
5
- import { useCallback, useState } from "react";
6
7
  //#region src/avatar/avatar.tsx
7
8
  /**
8
9
  * Avatar, ported from the legacy shadcn/Radix component.
@@ -35,7 +36,7 @@ const avatarVariants = cva([
35
36
  } },
36
37
  defaultVariants: { size: "md" }
37
38
  });
38
- function Avatar({ className, size, src, alt, fallback, ...props }) {
39
+ const Avatar = forwardRef(function Avatar({ className, size, src, alt, fallback, ...props }, ref) {
39
40
  const [status, setStatus] = useState(src === void 0 ? "idle" : "loading");
40
41
  /**
41
42
  * Reset when src changes, during RENDER rather than in an effect.
@@ -63,6 +64,7 @@ function Avatar({ className, size, src, alt, fallback, ...props }) {
63
64
  const measure = useCallback((node) => {
64
65
  if (node?.complete) setStatus(node.naturalWidth > 0 ? "loaded" : "error");
65
66
  }, []);
67
+ const imageRef = useMemo(() => mergeRefs(ref, measure), [ref, measure]);
66
68
  const showImage = src !== void 0 && status !== "error";
67
69
  return /* @__PURE__ */ jsxs("span", {
68
70
  ...props,
@@ -70,7 +72,7 @@ function Avatar({ className, size, src, alt, fallback, ...props }) {
70
72
  "data-status": status,
71
73
  className: cn(avatarVariants({ size }), className),
72
74
  children: [showImage ? /* @__PURE__ */ jsx("img", {
73
- ref: measure,
75
+ ref: imageRef,
74
76
  "data-slot": "avatar-image",
75
77
  className: "size-full object-cover",
76
78
  src,
@@ -84,7 +86,7 @@ function Avatar({ className, size, src, alt, fallback, ...props }) {
84
86
  children: fallback
85
87
  })]
86
88
  });
87
- }
89
+ });
88
90
  //#endregion
89
91
  export { Avatar, avatarVariants };
90
92
 
@@ -1 +1 @@
1
- {"version":3,"file":"avatar.js","names":[],"sources":["../../src/avatar/avatar.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { useCallback, useState, type ComponentPropsWithoutRef, type ReactNode } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Avatar, ported from the legacy shadcn/Radix component.\n *\n * React Aria ships no avatar, so the image load/error state machine is\n * re-implemented here -- see the status handling below, which is the part\n * Radix was actually providing.\n *\n * The three legacy parts (Avatar / AvatarImage / AvatarFallback) collapse\n * into one props-driven component. The census is unambiguous: 36 / 35 / 35\n * uses, always as the same triple, so the compound API bought nothing but\n * three imports and the chance to forget one.\n *\n * Every string below is a STATIC literal. Tailwind scans source text, so a\n * class assembled at runtime is invisible to it and would never be emitted.\n */\nexport const avatarVariants = cva(\n [\n \"relative inline-flex shrink-0 items-center justify-center overflow-hidden\",\n \"rounded-full\",\n // box-border: this library's reset is OPT-IN, so a consumer may load\n // none and the browser default content-box applies. The Alert shipped\n // overflowing its container by 46px for exactly this reason.\n \"box-border\",\n // font-sans: initials are text, and an unstyled document renders text in\n // a serif. The accordion panel shipped that way.\n \"font-sans font-medium\",\n \"bg-muted text-foreground\",\n ],\n {\n variants: {\n // Sizes come from the census, not from taste: call sites reach for\n // h-5 / h-6 / h-8 / h-10 / h-16 through className because the legacy\n // component has no size prop. size-7 (2 uses) has no variant; those\n // sites pick sm or md.\n //\n // Deliberately NOT on the density scale. An avatar is content, not a\n // control -- it does not shrink when a table is compacted, and putting\n // it on --density-control-* would couple a portrait to a button's\n // rhythm. If that turns out wrong it becomes an --avatar-* scale.\n size: {\n xs: \"size-5 text-2xs leading-(--leading-none)\",\n sm: \"size-6 text-2xs leading-(--leading-none)\",\n md: \"size-8 text-sm leading-(--leading-none)\",\n lg: \"size-10 text-sm leading-(--leading-none)\",\n xl: \"size-16 text-base leading-(--leading-none)\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\ntype Status = \"idle\" | \"loading\" | \"loaded\" | \"error\";\n\n/**\n * A user avatar.\n *\n * @example\n * <Avatar src={user.photoUrl} alt={user.name} fallback={initials(user.name)} />\n * <Avatar alt=\"\" fallback=\"JD\" />\n */\nexport interface AvatarProps\n extends Omit<ComponentPropsWithoutRef<\"span\">, \"children\">, VariantProps<typeof avatarVariants> {\n /** Image URL. Omit it to render the fallback alone. */\n src?: string;\n /**\n * Alternative text. **Required**, and `\"\"` is the right value when the\n * person's name already appears next to the avatar.\n *\n * Required on purpose: the census found 15 of 38 legacy `AvatarImage` uses\n * with no `alt` at all, which renders an `<img>` that fails axe's\n * `image-alt` rule. Making it required turns that into a decision the\n * caller makes rather than a default they fall into.\n */\n alt: string;\n /**\n * Shown until the image loads, and permanently if it fails. Usually\n * initials.\n */\n fallback?: ReactNode;\n}\n\nexport function Avatar({ className, size, src, alt, fallback, ...props }: AvatarProps) {\n const [status, setStatus] = useState<Status>(src === undefined ? \"idle\" : \"loading\");\n\n /**\n * Reset when src changes, during RENDER rather than in an effect.\n *\n * React's documented \"adjusting state when a prop changes\" pattern. An\n * effect that called setState here would be flagged by\n * react-hooks/set-state-in-effect, and rightly: it renders once with the\n * stale status before correcting itself.\n */\n const [renderedSrc, setRenderedSrc] = useState(src);\n if (src !== renderedSrc) {\n setRenderedSrc(src);\n setStatus(src === undefined ? \"idle\" : \"loading\");\n }\n\n /**\n * Resolve the status from the DOM, not only from events.\n *\n * A cached or data-URL image is already decoded by the time React attaches\n * its handlers, so `onLoad` fires BEFORE any effect could run -- and an\n * effect that set \"loading\" would then clobber it and leave the avatar on\n * its fallback forever. A ref callback runs when the node attaches, which\n * is early enough to read `complete` and late enough to have a node. The\n * handlers below still cover the asynchronous case.\n */\n const measure = useCallback((node: HTMLImageElement | null) => {\n if (node?.complete) {\n setStatus(node.naturalWidth > 0 ? \"loaded\" : \"error\");\n }\n }, []);\n\n const showImage = src !== undefined && status !== \"error\";\n\n return (\n <span\n {...props}\n data-slot=\"avatar\"\n data-status={status}\n className={cn(avatarVariants({ size }), className)}\n >\n {showImage ? (\n <img\n ref={measure}\n data-slot=\"avatar-image\"\n className=\"size-full object-cover\"\n src={src}\n alt={alt}\n onLoad={() => setStatus(\"loaded\")}\n onError={() => setStatus(\"error\")}\n />\n ) : null}\n {status === \"loaded\" ? null : (\n // aria-hidden: when the image is present the img carries the\n // accessible name, and when it is not the initials are decorative\n // shorthand for a name the surrounding UI already states.\n <span data-slot=\"avatar-fallback\" aria-hidden=\"true\" className=\"select-none\">\n {fallback}\n </span>\n )}\n </span>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA,MAAa,iBAAiB,IAC5B;CACE;CACA;CAIA;CAGA;CACA;AACF,GACA;CACE,UAAU,EAUR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;CACN,EACF;CACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CACF;AAgCA,SAAgB,OAAO,EAAE,WAAW,MAAM,KAAK,KAAK,UAAU,GAAG,SAAsB;CACrF,MAAM,CAAC,QAAQ,aAAa,SAAiB,QAAQ,KAAA,IAAY,SAAS,SAAS;;;;;;;;;CAUnF,MAAM,CAAC,aAAa,kBAAkB,SAAS,GAAG;CAClD,IAAI,QAAQ,aAAa;EACvB,eAAe,GAAG;EAClB,UAAU,QAAQ,KAAA,IAAY,SAAS,SAAS;CAClD;;;;;;;;;;;CAYA,MAAM,UAAU,aAAa,SAAkC;EAC7D,IAAI,MAAM,UACR,UAAU,KAAK,eAAe,IAAI,WAAW,OAAO;CAExD,GAAG,CAAC,CAAC;CAEL,MAAM,YAAY,QAAQ,KAAA,KAAa,WAAW;CAElD,OACE,qBAAC,QAAD;EACE,GAAI;EACJ,aAAU;EACV,eAAa;EACb,WAAW,GAAG,eAAe,EAAE,KAAK,CAAC,GAAG,SAAS;EAJnD,UAAA,CAMG,YACC,oBAAC,OAAD;GACE,KAAK;GACL,aAAU;GACV,WAAU;GACL;GACA;GACL,cAAc,UAAU,QAAQ;GAChC,eAAe,UAAU,OAAO;EACjC,CAAA,IACC,MACH,WAAW,WAAW,OAIrB,oBAAC,QAAD;GAAM,aAAU;GAAkB,eAAY;GAAO,WAAU;GAC5D,UAAA;EACG,CAAA,CAEJ;;AAEV"}
1
+ {"version":3,"file":"avatar.js","names":[],"sources":["../../src/avatar/avatar.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n forwardRef,\n useCallback,\n useMemo,\n useState,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\n\n/**\n * Avatar, ported from the legacy shadcn/Radix component.\n *\n * React Aria ships no avatar, so the image load/error state machine is\n * re-implemented here -- see the status handling below, which is the part\n * Radix was actually providing.\n *\n * The three legacy parts (Avatar / AvatarImage / AvatarFallback) collapse\n * into one props-driven component. The census is unambiguous: 36 / 35 / 35\n * uses, always as the same triple, so the compound API bought nothing but\n * three imports and the chance to forget one.\n *\n * Every string below is a STATIC literal. Tailwind scans source text, so a\n * class assembled at runtime is invisible to it and would never be emitted.\n */\nexport const avatarVariants = cva(\n [\n \"relative inline-flex shrink-0 items-center justify-center overflow-hidden\",\n \"rounded-full\",\n // box-border: this library's reset is OPT-IN, so a consumer may load\n // none and the browser default content-box applies. The Alert shipped\n // overflowing its container by 46px for exactly this reason.\n \"box-border\",\n // font-sans: initials are text, and an unstyled document renders text in\n // a serif. The accordion panel shipped that way.\n \"font-sans font-medium\",\n \"bg-muted text-foreground\",\n ],\n {\n variants: {\n // Sizes come from the census, not from taste: call sites reach for\n // h-5 / h-6 / h-8 / h-10 / h-16 through className because the legacy\n // component has no size prop. size-7 (2 uses) has no variant; those\n // sites pick sm or md.\n //\n // Deliberately NOT on the density scale. An avatar is content, not a\n // control -- it does not shrink when a table is compacted, and putting\n // it on --density-control-* would couple a portrait to a button's\n // rhythm. If that turns out wrong it becomes an --avatar-* scale.\n size: {\n xs: \"size-5 text-2xs leading-(--leading-none)\",\n sm: \"size-6 text-2xs leading-(--leading-none)\",\n md: \"size-8 text-sm leading-(--leading-none)\",\n lg: \"size-10 text-sm leading-(--leading-none)\",\n xl: \"size-16 text-base leading-(--leading-none)\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\ntype Status = \"idle\" | \"loading\" | \"loaded\" | \"error\";\n\n/**\n * A user avatar.\n *\n * @example\n * <Avatar src={user.photoUrl} alt={user.name} fallback={initials(user.name)} />\n * <Avatar alt=\"\" fallback=\"JD\" />\n */\nexport interface AvatarProps\n extends Omit<ComponentPropsWithoutRef<\"span\">, \"children\">, VariantProps<typeof avatarVariants> {\n /** Image URL. Omit it to render the fallback alone. */\n src?: string;\n /**\n * Alternative text. **Required**, and `\"\"` is the right value when the\n * person's name already appears next to the avatar.\n *\n * Required on purpose: the census found 15 of 38 legacy `AvatarImage` uses\n * with no `alt` at all, which renders an `<img>` that fails axe's\n * `image-alt` rule. Making it required turns that into a decision the\n * caller makes rather than a default they fall into.\n */\n alt: string;\n /**\n * Shown until the image loads, and permanently if it fails. Usually\n * initials.\n */\n fallback?: ReactNode;\n}\n\nexport const Avatar = forwardRef<HTMLImageElement, AvatarProps>(function Avatar(\n { className, size, src, alt, fallback, ...props },\n ref,\n) {\n const [status, setStatus] = useState<Status>(src === undefined ? \"idle\" : \"loading\");\n\n /**\n * Reset when src changes, during RENDER rather than in an effect.\n *\n * React's documented \"adjusting state when a prop changes\" pattern. An\n * effect that called setState here would be flagged by\n * react-hooks/set-state-in-effect, and rightly: it renders once with the\n * stale status before correcting itself.\n */\n const [renderedSrc, setRenderedSrc] = useState(src);\n if (src !== renderedSrc) {\n setRenderedSrc(src);\n setStatus(src === undefined ? \"idle\" : \"loading\");\n }\n\n /**\n * Resolve the status from the DOM, not only from events.\n *\n * A cached or data-URL image is already decoded by the time React attaches\n * its handlers, so `onLoad` fires BEFORE any effect could run -- and an\n * effect that set \"loading\" would then clobber it and leave the avatar on\n * its fallback forever. A ref callback runs when the node attaches, which\n * is early enough to read `complete` and late enough to have a node. The\n * handlers below still cover the asynchronous case.\n */\n const measure = useCallback((node: HTMLImageElement | null) => {\n if (node?.complete) {\n setStatus(node.naturalWidth > 0 ? \"loaded\" : \"error\");\n }\n }, []);\n\n // Memoized, not called inline on the img below: mergeRefs returns a new\n // callback every call, and an inline call is a new callback every render --\n // React would detach and reattach both refs on every re-render instead of\n // only on mount, re-running measure's decode check each time.\n const imageRef = useMemo(() => mergeRefs(ref, measure), [ref, measure]);\n\n const showImage = src !== undefined && status !== \"error\";\n\n return (\n <span\n {...props}\n data-slot=\"avatar\"\n data-status={status}\n className={cn(avatarVariants({ size }), className)}\n >\n {showImage ? (\n <img\n // The forwarded ref is null while the fallback shows -- the img is\n // not rendered then -- matching measure's own scope, not a new\n // limitation.\n ref={imageRef}\n data-slot=\"avatar-image\"\n className=\"size-full object-cover\"\n src={src}\n alt={alt}\n onLoad={() => setStatus(\"loaded\")}\n onError={() => setStatus(\"error\")}\n />\n ) : null}\n {status === \"loaded\" ? null : (\n // aria-hidden: when the image is present the img carries the\n // accessible name, and when it is not the initials are decorative\n // shorthand for a name the surrounding UI already states.\n <span data-slot=\"avatar-fallback\" aria-hidden=\"true\" className=\"select-none\">\n {fallback}\n </span>\n )}\n </span>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA8BA,MAAa,iBAAiB,IAC5B;CACE;CACA;CAIA;CAGA;CACA;AACF,GACA;CACE,UAAU,EAUR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;CACN,EACF;CACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CACF;AAgCA,MAAa,SAAS,WAA0C,SAAS,OACvE,EAAE,WAAW,MAAM,KAAK,KAAK,UAAU,GAAG,SAC1C,KACA;CACA,MAAM,CAAC,QAAQ,aAAa,SAAiB,QAAQ,KAAA,IAAY,SAAS,SAAS;;;;;;;;;CAUnF,MAAM,CAAC,aAAa,kBAAkB,SAAS,GAAG;CAClD,IAAI,QAAQ,aAAa;EACvB,eAAe,GAAG;EAClB,UAAU,QAAQ,KAAA,IAAY,SAAS,SAAS;CAClD;;;;;;;;;;;CAYA,MAAM,UAAU,aAAa,SAAkC;EAC7D,IAAI,MAAM,UACR,UAAU,KAAK,eAAe,IAAI,WAAW,OAAO;CAExD,GAAG,CAAC,CAAC;CAML,MAAM,WAAW,cAAc,UAAU,KAAK,OAAO,GAAG,CAAC,KAAK,OAAO,CAAC;CAEtE,MAAM,YAAY,QAAQ,KAAA,KAAa,WAAW;CAElD,OACE,qBAAC,QAAD;EACE,GAAI;EACJ,aAAU;EACV,eAAa;EACb,WAAW,GAAG,eAAe,EAAE,KAAK,CAAC,GAAG,SAAS;EAJnD,UAAA,CAMG,YACC,oBAAC,OAAD;GAIE,KAAK;GACL,aAAU;GACV,WAAU;GACL;GACA;GACL,cAAc,UAAU,QAAQ;GAChC,eAAe,UAAU,OAAO;EACjC,CAAA,IACC,MACH,WAAW,WAAW,OAIrB,oBAAC,QAAD;GAAM,aAAU;GAAkB,eAAY;GAAO,WAAU;GAC5D,UAAA;EACG,CAAA,CAEJ;;AAEV,CAAC"}
@@ -37,11 +37,11 @@ import { ComponentPropsWithoutRef } from "react";
37
37
  */
38
38
  export declare const badgeVariants: (props?: ({
39
39
  tone?: "neutral" | "success" | "warning" | "danger" | "info" | "primary" | null | undefined;
40
- appearance?: "outline" | "solid" | "soft" | null | undefined;
40
+ appearance?: "solid" | "soft" | "outline" | null | undefined;
41
41
  size?: "sm" | "md" | "lg" | null | undefined;
42
42
  shape?: "rounded" | "pill" | null | undefined;
43
43
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
44
44
  export interface BadgeProps extends ComponentPropsWithoutRef<"span">, VariantProps<typeof badgeVariants> {}
45
- export declare function Badge({ className, tone, appearance, size, shape, ...props }: BadgeProps): import("react").JSX.Element;
45
+ export declare const Badge: import("react").ForwardRefExoticComponent<BadgeProps & import("react").RefAttributes<HTMLSpanElement>>;
46
46
  //#endregion
47
47
  //# sourceMappingURL=badge.d.ts.map
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { cva } from "class-variance-authority";
4
+ import { forwardRef } from "react";
4
5
  import { jsx } from "react/jsx-runtime";
5
6
  //#region src/badge/badge.tsx
6
7
  /**
@@ -170,8 +171,9 @@ const badgeVariants = cva([
170
171
  shape: "rounded"
171
172
  }
172
173
  });
173
- function Badge({ className, tone, appearance, size, shape, ...props }) {
174
+ const Badge = forwardRef(function Badge({ className, tone, appearance, size, shape, ...props }, ref) {
174
175
  return /* @__PURE__ */ jsx("span", {
176
+ ref,
175
177
  ...props,
176
178
  "data-slot": "badge",
177
179
  "data-tone": tone ?? "neutral",
@@ -182,7 +184,7 @@ function Badge({ className, tone, appearance, size, shape, ...props }) {
182
184
  shape
183
185
  }), className)
184
186
  });
185
- }
187
+ });
186
188
  //#endregion
187
189
  export { Badge, badgeVariants };
188
190
 
@@ -1 +1 @@
1
- {"version":3,"file":"badge.js","names":[],"sources":["../../src/badge/badge.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentPropsWithoutRef } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Badge -- the status pill, in every shape the product asks for.\n *\n * Presentational, so there is no React Aria base. RAC's `Tag` looks similar\n * and is not equivalent: it is focusable, selectable and removable, and it is\n * illegal outside a `TagGroup` collection. A status pill is none of those.\n *\n * THIS ABSORBED THE PRODUCT'S `Pill`. The app shipped two components for one\n * job -- `badge.tsx` with a `variant` axis and `pill.tsx` with a `tone` axis --\n * across 22 and 22 consumer files. They disagreed about everything: shape\n * (`rounded-md` against `rounded-full`), size (one fixed, one `sm`/`md`/`lg`)\n * and naming, so `pill` accepted `blue` AND `info`, `green` AND `success`, for\n * the same colour. Converging them is why `variant` is gone.\n *\n * The axes are the two questions a call site actually answers -- what does\n * this MEAN (`tone`) and how loud should it be (`appearance`) -- rather than\n * one axis whose values are the cross product. Six tones by three appearances\n * is eighteen `variant` values otherwise, and shipping eighteen is how a\n * design system ends up with `softPrimary` next to `primaryOutline`.\n *\n * Every string below is a STATIC literal, including the compound ones.\n * Tailwind scans source text, so a class assembled at runtime is invisible to\n * it and would never be emitted.\n *\n * Three things in the legacy are deliberately not ported:\n *\n * - `asChild` and its `[a&]:hover:` rules. Zero call sites; the hover styles\n * existed only to colour the anchor asChild produced.\n * - `focus-visible:ring-*`. A span is not focusable, so the ring could never\n * appear. A consumer who adds `tabIndex` owns the indicator, and a ring is\n * the wrong one anyway -- box-shadows vanish under forced-colors.\n * - `aria-invalid:*`. A badge is never invalid; that was boilerplate copied\n * across every shadcn control.\n */\nexport const badgeVariants = cva(\n [\n \"inline-flex w-fit shrink-0 items-center justify-center gap-1\",\n \"border-(length:--border-width) border-transparent\",\n // NO leading-* here. It lives on each `size` instead, AFTER that size's\n // text-* class: tailwind-merge treats a later text-* as owning\n // line-height, so a leading-* in this base string is silently stripped the\n // moment a size variant adds one. The NoUaLeak story caught exactly that.\n \"font-sans font-medium\",\n \"whitespace-nowrap\",\n // box-border and font-sans are not boilerplate. The reset in\n // @seekalfred/styles is OPT-IN, so a consumer may load none: an unstyled\n // span inherits the host's serif, and content-box would add the padding\n // to any width a consumer sets.\n \"box-border\",\n \"[&>svg]:pointer-events-none [&>svg]:size-3\",\n ],\n {\n variants: {\n /** What the badge MEANS. Colour follows from it; it is never picked directly. */\n tone: {\n neutral: \"\",\n primary: \"\",\n success: \"\",\n warning: \"\",\n danger: \"\",\n info: \"\",\n },\n /** How loud it is. */\n appearance: {\n solid: \"\",\n soft: \"\",\n outline: \"bg-transparent\",\n },\n size: {\n sm: \"px-2 py-0.5 text-xs leading-(--leading-snug)\",\n md: \"px-2.5 py-0.5 text-xs leading-(--leading-snug)\",\n lg: \"px-3 py-1 text-sm leading-(--leading-snug)\",\n },\n /*\n * The product's two components disagreed on this and nothing else:\n * `badge.tsx` was rounded-md, `pill.tsx` was rounded-full. Both survive\n * as one axis rather than one winning, because a count-bearing badge and\n * a verdict pill really do read differently.\n */\n shape: { rounded: \"rounded-md\", pill: \"rounded-full\" },\n },\n compoundVariants: [\n // SOLID -- the fill carries the meaning, so the text must clear 4.5:1 on it.\n /*\n * muted-foreground, NOT `secondary`. --color-secondary resolves to\n * periwinkle.400, so a \"neutral\" badge rendered lavender -- caught in a\n * browser, not by any assertion, because the story compared it against\n * the same wrong token it was painted with. A tone called neutral has to\n * be neutral; the periwinkle fill the product uses for its `secondary`\n * badge is the brand's SECOND colour, not an absence of one.\n */\n { tone: \"neutral\", appearance: \"solid\", class: \"bg-muted-foreground text-background\" },\n { tone: \"primary\", appearance: \"solid\", class: \"bg-primary text-primary-foreground\" },\n { tone: \"success\", appearance: \"solid\", class: \"bg-success-strong text-success-strong-foreground\" },\n { tone: \"warning\", appearance: \"solid\", class: \"bg-warning-strong text-warning-strong-foreground\" },\n { tone: \"danger\", appearance: \"solid\", class: \"bg-destructive text-destructive-foreground\" },\n { tone: \"info\", appearance: \"solid\", class: \"bg-info-strong text-info-strong-foreground\" },\n\n /*\n * SOFT -- a tint, and the default, because this is what a status pill is.\n * The ink is one step darker than the obvious pairing for success,\n * warning and info: the product measured 4.13, 3.48 and 3.34 on its own\n * tints and had to darken all three. Our `-foreground` tokens already\n * encode that correction, which is why they are used rather than\n * `-strong`.\n */\n { tone: \"neutral\", appearance: \"soft\", class: \"bg-muted text-muted-foreground\" },\n // No primary-subtle-foreground token: plain `foreground` on this tint\n // measures 18.70 light / 9.78 dark, already graded for the Table's\n // highlighted row.\n { tone: \"primary\", appearance: \"soft\", class: \"bg-primary-subtle text-foreground\" },\n { tone: \"success\", appearance: \"soft\", class: \"bg-success-subtle text-success-foreground\" },\n { tone: \"warning\", appearance: \"soft\", class: \"bg-warning-subtle text-warning-foreground\" },\n {\n tone: \"danger\",\n appearance: \"soft\",\n class: \"bg-destructive-subtle text-destructive-subtle-foreground\",\n },\n { tone: \"info\", appearance: \"soft\", class: \"bg-info-subtle text-info-foreground\" },\n\n /*\n * OUTLINE -- no fill, so the border is the only thing separating the\n * badge from the page and is held to the 3:1 non-text floor of WCAG\n * 1.4.11. Text AND border are `-strong`, not `-foreground`: with no tint\n * behind it the label sits on `background`, and `-strong on background`\n * is the pair already graded at 4.5. `-foreground` is graded only\n * against its own `-subtle`, so using it here would have been a ratio\n * nobody had measured.\n */\n { tone: \"neutral\", appearance: \"outline\", class: \"border-border text-foreground\" },\n { tone: \"primary\", appearance: \"outline\", class: \"border-primary-strong text-foreground\" },\n {\n tone: \"success\",\n appearance: \"outline\",\n class: \"border-success-strong text-success-strong\",\n },\n {\n tone: \"warning\",\n appearance: \"outline\",\n class: \"border-warning-strong text-warning-strong\",\n },\n {\n tone: \"danger\",\n appearance: \"outline\",\n class: \"border-destructive-strong text-destructive-strong\",\n },\n { tone: \"info\", appearance: \"outline\", class: \"border-info-strong text-info-strong\" },\n ],\n defaultVariants: { tone: \"neutral\", appearance: \"soft\", size: \"md\", shape: \"rounded\" },\n },\n);\n\nexport interface BadgeProps\n extends ComponentPropsWithoutRef<\"span\">,\n VariantProps<typeof badgeVariants> {}\n\nexport function Badge({ className, tone, appearance, size, shape, ...props }: BadgeProps) {\n return (\n <span\n {...props}\n data-slot=\"badge\"\n data-tone={tone ?? \"neutral\"}\n className={cn(badgeVariants({ tone, appearance, size, shape }), className)}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,MAAa,gBAAgB,IAC3B;CACE;CACA;CAKA;CACA;CAKA;CACA;AACF,GACA;CACE,UAAU;;EAER,MAAM;GACJ,SAAS;GACT,SAAS;GACT,SAAS;GACT,SAAS;GACT,QAAQ;GACR,MAAM;EACR;;EAEA,YAAY;GACV,OAAO;GACP,MAAM;GACN,SAAS;EACX;EACA,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;EAOA,OAAO;GAAE,SAAS;GAAc,MAAM;EAAe;CACvD;CACA,kBAAkB;EAUhB;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAsC;EACrF;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAqC;EACpF;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAmD;EAClG;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAmD;EAClG;GAAE,MAAM;GAAU,YAAY;GAAS,OAAO;EAA6C;EAC3F;GAAE,MAAM;GAAQ,YAAY;GAAS,OAAO;EAA6C;EAUzF;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAAiC;EAI/E;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAAoC;EAClF;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAA4C;EAC1F;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAA4C;EAC1F;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GAAE,MAAM;GAAQ,YAAY;GAAQ,OAAO;EAAsC;EAWjF;GAAE,MAAM;GAAW,YAAY;GAAW,OAAO;EAAgC;EACjF;GAAE,MAAM;GAAW,YAAY;GAAW,OAAO;EAAwC;EACzF;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GAAE,MAAM;GAAQ,YAAY;GAAW,OAAO;EAAsC;CACtF;CACA,iBAAiB;EAAE,MAAM;EAAW,YAAY;EAAQ,MAAM;EAAM,OAAO;CAAU;AACvF,CACF;AAMA,SAAgB,MAAM,EAAE,WAAW,MAAM,YAAY,MAAM,OAAO,GAAG,SAAqB;CACxF,OACE,oBAAC,QAAD;EACE,GAAI;EACJ,aAAU;EACV,aAAW,QAAQ;EACnB,WAAW,GAAG,cAAc;GAAE;GAAM;GAAY;GAAM;EAAM,CAAC,GAAG,SAAS;CAC1E,CAAA;AAEL"}
1
+ {"version":3,"file":"badge.js","names":[],"sources":["../../src/badge/badge.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, type ComponentPropsWithoutRef } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Badge -- the status pill, in every shape the product asks for.\n *\n * Presentational, so there is no React Aria base. RAC's `Tag` looks similar\n * and is not equivalent: it is focusable, selectable and removable, and it is\n * illegal outside a `TagGroup` collection. A status pill is none of those.\n *\n * THIS ABSORBED THE PRODUCT'S `Pill`. The app shipped two components for one\n * job -- `badge.tsx` with a `variant` axis and `pill.tsx` with a `tone` axis --\n * across 22 and 22 consumer files. They disagreed about everything: shape\n * (`rounded-md` against `rounded-full`), size (one fixed, one `sm`/`md`/`lg`)\n * and naming, so `pill` accepted `blue` AND `info`, `green` AND `success`, for\n * the same colour. Converging them is why `variant` is gone.\n *\n * The axes are the two questions a call site actually answers -- what does\n * this MEAN (`tone`) and how loud should it be (`appearance`) -- rather than\n * one axis whose values are the cross product. Six tones by three appearances\n * is eighteen `variant` values otherwise, and shipping eighteen is how a\n * design system ends up with `softPrimary` next to `primaryOutline`.\n *\n * Every string below is a STATIC literal, including the compound ones.\n * Tailwind scans source text, so a class assembled at runtime is invisible to\n * it and would never be emitted.\n *\n * Three things in the legacy are deliberately not ported:\n *\n * - `asChild` and its `[a&]:hover:` rules. Zero call sites; the hover styles\n * existed only to colour the anchor asChild produced.\n * - `focus-visible:ring-*`. A span is not focusable, so the ring could never\n * appear. A consumer who adds `tabIndex` owns the indicator, and a ring is\n * the wrong one anyway -- box-shadows vanish under forced-colors.\n * - `aria-invalid:*`. A badge is never invalid; that was boilerplate copied\n * across every shadcn control.\n */\nexport const badgeVariants = cva(\n [\n \"inline-flex w-fit shrink-0 items-center justify-center gap-1\",\n \"border-(length:--border-width) border-transparent\",\n // NO leading-* here. It lives on each `size` instead, AFTER that size's\n // text-* class: tailwind-merge treats a later text-* as owning\n // line-height, so a leading-* in this base string is silently stripped the\n // moment a size variant adds one. The NoUaLeak story caught exactly that.\n \"font-sans font-medium\",\n \"whitespace-nowrap\",\n // box-border and font-sans are not boilerplate. The reset in\n // @seekalfred/styles is OPT-IN, so a consumer may load none: an unstyled\n // span inherits the host's serif, and content-box would add the padding\n // to any width a consumer sets.\n \"box-border\",\n \"[&>svg]:pointer-events-none [&>svg]:size-3\",\n ],\n {\n variants: {\n /** What the badge MEANS. Colour follows from it; it is never picked directly. */\n tone: {\n neutral: \"\",\n primary: \"\",\n success: \"\",\n warning: \"\",\n danger: \"\",\n info: \"\",\n },\n /** How loud it is. */\n appearance: {\n solid: \"\",\n soft: \"\",\n outline: \"bg-transparent\",\n },\n size: {\n sm: \"px-2 py-0.5 text-xs leading-(--leading-snug)\",\n md: \"px-2.5 py-0.5 text-xs leading-(--leading-snug)\",\n lg: \"px-3 py-1 text-sm leading-(--leading-snug)\",\n },\n /*\n * The product's two components disagreed on this and nothing else:\n * `badge.tsx` was rounded-md, `pill.tsx` was rounded-full. Both survive\n * as one axis rather than one winning, because a count-bearing badge and\n * a verdict pill really do read differently.\n */\n shape: { rounded: \"rounded-md\", pill: \"rounded-full\" },\n },\n compoundVariants: [\n // SOLID -- the fill carries the meaning, so the text must clear 4.5:1 on it.\n /*\n * muted-foreground, NOT `secondary`. --color-secondary resolves to\n * periwinkle.400, so a \"neutral\" badge rendered lavender -- caught in a\n * browser, not by any assertion, because the story compared it against\n * the same wrong token it was painted with. A tone called neutral has to\n * be neutral; the periwinkle fill the product uses for its `secondary`\n * badge is the brand's SECOND colour, not an absence of one.\n */\n { tone: \"neutral\", appearance: \"solid\", class: \"bg-muted-foreground text-background\" },\n { tone: \"primary\", appearance: \"solid\", class: \"bg-primary text-primary-foreground\" },\n { tone: \"success\", appearance: \"solid\", class: \"bg-success-strong text-success-strong-foreground\" },\n { tone: \"warning\", appearance: \"solid\", class: \"bg-warning-strong text-warning-strong-foreground\" },\n { tone: \"danger\", appearance: \"solid\", class: \"bg-destructive text-destructive-foreground\" },\n { tone: \"info\", appearance: \"solid\", class: \"bg-info-strong text-info-strong-foreground\" },\n\n /*\n * SOFT -- a tint, and the default, because this is what a status pill is.\n * The ink is one step darker than the obvious pairing for success,\n * warning and info: the product measured 4.13, 3.48 and 3.34 on its own\n * tints and had to darken all three. Our `-foreground` tokens already\n * encode that correction, which is why they are used rather than\n * `-strong`.\n */\n { tone: \"neutral\", appearance: \"soft\", class: \"bg-muted text-muted-foreground\" },\n // No primary-subtle-foreground token: plain `foreground` on this tint\n // measures 18.70 light / 9.78 dark, already graded for the Table's\n // highlighted row.\n { tone: \"primary\", appearance: \"soft\", class: \"bg-primary-subtle text-foreground\" },\n { tone: \"success\", appearance: \"soft\", class: \"bg-success-subtle text-success-foreground\" },\n { tone: \"warning\", appearance: \"soft\", class: \"bg-warning-subtle text-warning-foreground\" },\n {\n tone: \"danger\",\n appearance: \"soft\",\n class: \"bg-destructive-subtle text-destructive-subtle-foreground\",\n },\n { tone: \"info\", appearance: \"soft\", class: \"bg-info-subtle text-info-foreground\" },\n\n /*\n * OUTLINE -- no fill, so the border is the only thing separating the\n * badge from the page and is held to the 3:1 non-text floor of WCAG\n * 1.4.11. Text AND border are `-strong`, not `-foreground`: with no tint\n * behind it the label sits on `background`, and `-strong on background`\n * is the pair already graded at 4.5. `-foreground` is graded only\n * against its own `-subtle`, so using it here would have been a ratio\n * nobody had measured.\n */\n { tone: \"neutral\", appearance: \"outline\", class: \"border-border text-foreground\" },\n { tone: \"primary\", appearance: \"outline\", class: \"border-primary-strong text-foreground\" },\n {\n tone: \"success\",\n appearance: \"outline\",\n class: \"border-success-strong text-success-strong\",\n },\n {\n tone: \"warning\",\n appearance: \"outline\",\n class: \"border-warning-strong text-warning-strong\",\n },\n {\n tone: \"danger\",\n appearance: \"outline\",\n class: \"border-destructive-strong text-destructive-strong\",\n },\n { tone: \"info\", appearance: \"outline\", class: \"border-info-strong text-info-strong\" },\n ],\n defaultVariants: { tone: \"neutral\", appearance: \"soft\", size: \"md\", shape: \"rounded\" },\n },\n);\n\nexport interface BadgeProps\n extends ComponentPropsWithoutRef<\"span\">,\n VariantProps<typeof badgeVariants> {}\n\nexport const Badge = forwardRef<HTMLSpanElement, BadgeProps>(function Badge(\n { className, tone, appearance, size, shape, ...props },\n ref,\n) {\n return (\n <span\n ref={ref}\n {...props}\n data-slot=\"badge\"\n data-tone={tone ?? \"neutral\"}\n className={cn(badgeVariants({ tone, appearance, size, shape }), className)}\n />\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,MAAa,gBAAgB,IAC3B;CACE;CACA;CAKA;CACA;CAKA;CACA;AACF,GACA;CACE,UAAU;;EAER,MAAM;GACJ,SAAS;GACT,SAAS;GACT,SAAS;GACT,SAAS;GACT,QAAQ;GACR,MAAM;EACR;;EAEA,YAAY;GACV,OAAO;GACP,MAAM;GACN,SAAS;EACX;EACA,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;EAOA,OAAO;GAAE,SAAS;GAAc,MAAM;EAAe;CACvD;CACA,kBAAkB;EAUhB;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAsC;EACrF;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAqC;EACpF;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAmD;EAClG;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAmD;EAClG;GAAE,MAAM;GAAU,YAAY;GAAS,OAAO;EAA6C;EAC3F;GAAE,MAAM;GAAQ,YAAY;GAAS,OAAO;EAA6C;EAUzF;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAAiC;EAI/E;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAAoC;EAClF;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAA4C;EAC1F;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAA4C;EAC1F;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GAAE,MAAM;GAAQ,YAAY;GAAQ,OAAO;EAAsC;EAWjF;GAAE,MAAM;GAAW,YAAY;GAAW,OAAO;EAAgC;EACjF;GAAE,MAAM;GAAW,YAAY;GAAW,OAAO;EAAwC;EACzF;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GAAE,MAAM;GAAQ,YAAY;GAAW,OAAO;EAAsC;CACtF;CACA,iBAAiB;EAAE,MAAM;EAAW,YAAY;EAAQ,MAAM;EAAM,OAAO;CAAU;AACvF,CACF;AAMA,MAAa,QAAQ,WAAwC,SAAS,MACpE,EAAE,WAAW,MAAM,YAAY,MAAM,OAAO,GAAG,SAC/C,KACA;CACA,OACE,oBAAC,QAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,aAAW,QAAQ;EACnB,WAAW,GAAG,cAAc;GAAE;GAAM;GAAY;GAAM;EAAM,CAAC,GAAG,SAAS;CAC1E,CAAA;AAEL,CAAC"}
@@ -1,5 +1,5 @@
1
- import { BreadcrumbProps, BreadcrumbsProps } from "react-aria-components";
2
1
  import { ReactNode } from "react";
2
+ import { BreadcrumbProps, BreadcrumbsProps } from "react-aria-components";
3
3
  //#region src/breadcrumb/breadcrumb.d.ts
4
4
  /**
5
5
  * Breadcrumbs, ported from the legacy shadcn/Radix component onto React
@@ -67,7 +67,7 @@ interface BreadcrumbsProps$1 extends Omit<BreadcrumbsProps<object>, "className"
67
67
  */
68
68
  "aria-label"?: string;
69
69
  }
70
- declare function Breadcrumbs$1({ className, children, "aria-label": ariaLabel, ...props }: BreadcrumbsProps$1): import("react").JSX.Element;
70
+ declare const Breadcrumbs$1: import("react").ForwardRefExoticComponent<BreadcrumbsProps$1 & import("react").RefAttributes<HTMLOListElement>>;
71
71
  /**
72
72
  * One crumb.
73
73
  *
@@ -92,7 +92,7 @@ export interface BreadcrumbItemProps extends Omit<BreadcrumbProps, "className" |
92
92
  /** Anchor rel, forwarded when `href` is set. */
93
93
  rel?: string;
94
94
  }
95
- export declare function BreadcrumbItem({ className, children, href, target, rel, "aria-label": ariaLabel, ...props }: BreadcrumbItemProps): import("react").JSX.Element;
95
+ export declare const BreadcrumbItem: import("react").ForwardRefExoticComponent<BreadcrumbItemProps & import("react").RefAttributes<HTMLAnchorElement>>;
96
96
  //#endregion
97
97
  export { Breadcrumbs$1 as Breadcrumbs, BreadcrumbsProps$1 as BreadcrumbsProps };
98
98
  //# sourceMappingURL=breadcrumb.d.ts.map
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { cva } from "class-variance-authority";
4
+ import { forwardRef } from "react";
4
5
  import { Breadcrumb, Breadcrumbs, Link } from "react-aria-components";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
6
7
  //#region src/breadcrumb/breadcrumb.tsx
@@ -53,11 +54,12 @@ const breadcrumbLinkVariants = cva([
53
54
  "[&_svg]:pointer-events-none [&_svg]:shrink-0",
54
55
  "[&_svg:not([class*='size-'])]:size-(--icon-sm)"
55
56
  ]);
56
- function Breadcrumbs$1({ className, children, "aria-label": ariaLabel = "Breadcrumb", ...props }) {
57
+ const Breadcrumbs$1 = forwardRef(function Breadcrumbs$2({ className, children, "aria-label": ariaLabel = "Breadcrumb", ...props }, ref) {
57
58
  return /* @__PURE__ */ jsx("nav", {
58
59
  "aria-label": ariaLabel,
59
60
  "data-slot": "breadcrumb",
60
61
  children: /* @__PURE__ */ jsx(Breadcrumbs, {
62
+ ref,
61
63
  ...props,
62
64
  "aria-label": ariaLabel,
63
65
  "data-slot": "breadcrumb-list",
@@ -65,13 +67,14 @@ function Breadcrumbs$1({ className, children, "aria-label": ariaLabel = "Breadcr
65
67
  children
66
68
  })
67
69
  });
68
- }
69
- function BreadcrumbItem({ className, children, href, target, rel, "aria-label": ariaLabel, ...props }) {
70
+ });
71
+ const BreadcrumbItem = forwardRef(function BreadcrumbItem({ className, children, href, target, rel, "aria-label": ariaLabel, ...props }, ref) {
70
72
  return /* @__PURE__ */ jsxs(Breadcrumb, {
71
73
  ...props,
72
74
  "data-slot": "breadcrumb-item",
73
75
  className: cn(breadcrumbItemVariants(), className),
74
76
  children: [/* @__PURE__ */ jsx(Link, {
77
+ ref,
75
78
  href,
76
79
  target,
77
80
  rel,
@@ -86,7 +89,7 @@ function BreadcrumbItem({ className, children, href, target, rel, "aria-label":
86
89
  children: "/"
87
90
  })]
88
91
  });
89
- }
92
+ });
90
93
  //#endregion
91
94
  export { BreadcrumbItem, Breadcrumbs$1 as Breadcrumbs, breadcrumbItemVariants, breadcrumbLinkVariants, breadcrumbsVariants };
92
95