@seekalfred/primitives 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +315 -4
  2. package/dist/alert/alert.d.ts +2 -2
  3. package/dist/alert/alert.js +1 -1
  4. package/dist/alert/alert.js.map +1 -1
  5. package/dist/alert-dialog/alert-dialog.d.ts +14 -0
  6. package/dist/alert-dialog/alert-dialog.js +52 -27
  7. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  8. package/dist/announcement-bar/announcement-bar.d.ts +15 -4
  9. package/dist/announcement-bar/announcement-bar.js +31 -6
  10. package/dist/announcement-bar/announcement-bar.js.map +1 -1
  11. package/dist/avatar/avatar.d.ts +7 -0
  12. package/dist/avatar/avatar.js +33 -12
  13. package/dist/avatar/avatar.js.map +1 -1
  14. package/dist/badge/badge.d.ts +16 -1
  15. package/dist/badge/badge.js +12 -5
  16. package/dist/badge/badge.js.map +1 -1
  17. package/dist/bar-chart/bar-chart-series.d.ts +75 -0
  18. package/dist/bar-chart/bar-chart-series.js +401 -0
  19. package/dist/bar-chart/bar-chart-series.js.map +1 -0
  20. package/dist/bar-chart/bar-chart.d.ts +25 -1
  21. package/dist/bar-chart/bar-chart.js +26 -1
  22. package/dist/bar-chart/bar-chart.js.map +1 -1
  23. package/dist/bar-chart/index.d.ts +2 -1
  24. package/dist/button/button.d.ts +24 -2
  25. package/dist/button/button.js +62 -13
  26. package/dist/button/button.js.map +1 -1
  27. package/dist/card/card.js +1 -1
  28. package/dist/card/card.js.map +1 -1
  29. package/dist/checkbox/checkbox.d.ts +25 -1
  30. package/dist/checkbox/checkbox.js +103 -28
  31. package/dist/checkbox/checkbox.js.map +1 -1
  32. package/dist/checkbox/index.d.ts +2 -2
  33. package/dist/checkbox/index.js +2 -2
  34. package/dist/chip-group/chip-group.d.ts +11 -0
  35. package/dist/chip-group/chip-group.js +50 -34
  36. package/dist/chip-group/chip-group.js.map +1 -1
  37. package/dist/chip-group/index.d.ts +2 -2
  38. package/dist/code-block/code-block.d.ts +45 -0
  39. package/dist/code-block/code-block.js +74 -0
  40. package/dist/code-block/code-block.js.map +1 -0
  41. package/dist/code-block/index.d.ts +2 -0
  42. package/dist/code-block/index.js +2 -0
  43. package/dist/command-palette/command-palette.d.ts +128 -0
  44. package/dist/command-palette/command-palette.js +371 -0
  45. package/dist/command-palette/command-palette.js.map +1 -0
  46. package/dist/command-palette/index.d.ts +2 -0
  47. package/dist/command-palette/index.js +2 -0
  48. package/dist/copy-button/copy-button.d.ts +19 -3
  49. package/dist/copy-button/copy-button.js +7 -4
  50. package/dist/copy-button/copy-button.js.map +1 -1
  51. package/dist/dialog/dialog.js +1 -1
  52. package/dist/dialog/dialog.js.map +1 -1
  53. package/dist/empty-state/empty-state.d.ts +17 -0
  54. package/dist/empty-state/empty-state.js +9 -7
  55. package/dist/empty-state/empty-state.js.map +1 -1
  56. package/dist/file-drop-zone/file-drop-zone.d.ts +29 -3
  57. package/dist/file-drop-zone/file-drop-zone.js +26 -8
  58. package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
  59. package/dist/gauge/gauge.d.ts +18 -1
  60. package/dist/gauge/gauge.js +35 -21
  61. package/dist/gauge/gauge.js.map +1 -1
  62. package/dist/gauge/index.d.ts +2 -2
  63. package/dist/index.d.ts +14 -9
  64. package/dist/index.js +14 -10
  65. package/dist/info-tooltip/index.d.ts +2 -0
  66. package/dist/info-tooltip/index.js +2 -0
  67. package/dist/info-tooltip/info-tooltip.d.ts +35 -0
  68. package/dist/info-tooltip/info-tooltip.js +49 -0
  69. package/dist/info-tooltip/info-tooltip.js.map +1 -0
  70. package/dist/input/input.d.ts +7 -0
  71. package/dist/input/input.js +4 -2
  72. package/dist/input/input.js.map +1 -1
  73. package/dist/lib/field-shell.js +2 -6
  74. package/dist/lib/field-shell.js.map +1 -1
  75. package/dist/lib/required-marker.js +20 -0
  76. package/dist/lib/required-marker.js.map +1 -0
  77. package/dist/lib/selection-control.js +32 -1
  78. package/dist/lib/selection-control.js.map +1 -1
  79. package/dist/line-chart/index.d.ts +2 -2
  80. package/dist/line-chart/line-chart.d.ts +106 -3
  81. package/dist/line-chart/line-chart.js +185 -46
  82. package/dist/line-chart/line-chart.js.map +1 -1
  83. package/dist/mention-input/mention-input.js +1 -1
  84. package/dist/money-input/index.d.ts +2 -2
  85. package/dist/money-input/money-input.d.ts +14 -0
  86. package/dist/money-input/money-input.js +17 -7
  87. package/dist/money-input/money-input.js.map +1 -1
  88. package/dist/morph-select/morph-select.d.ts +8 -1
  89. package/dist/morph-select/morph-select.js +8 -5
  90. package/dist/morph-select/morph-select.js.map +1 -1
  91. package/dist/multi-select/multi-select.d.ts +31 -0
  92. package/dist/multi-select/multi-select.js +123 -6
  93. package/dist/multi-select/multi-select.js.map +1 -1
  94. package/dist/number-field/number-field.js +1 -1
  95. package/dist/password-field/password-field.js +1 -1
  96. package/dist/password-strength/password-strength.js +1 -1
  97. package/dist/phone-input/phone-input.js +1 -1
  98. package/dist/popover/popover.js +1 -1
  99. package/dist/popover/popover.js.map +1 -1
  100. package/dist/progress/index.d.ts +2 -2
  101. package/dist/progress/index.js +2 -2
  102. package/dist/progress/progress.d.ts +53 -2
  103. package/dist/progress/progress.js +146 -36
  104. package/dist/progress/progress.js.map +1 -1
  105. package/dist/radio-group/radio-group.d.ts +12 -3
  106. package/dist/radio-group/radio-group.js +62 -52
  107. package/dist/radio-group/radio-group.js.map +1 -1
  108. package/dist/rich-text-editor/rich-text-editor.js +1 -1
  109. package/dist/scroll-area/scroll-area.d.ts +1 -1
  110. package/dist/scrub-input/scrub-input.d.ts +11 -1
  111. package/dist/scrub-input/scrub-input.js +22 -10
  112. package/dist/scrub-input/scrub-input.js.map +1 -1
  113. package/dist/search-field/search-field.js +1 -1
  114. package/dist/select/select.d.ts +10 -1
  115. package/dist/select/select.js +4 -3
  116. package/dist/select/select.js.map +1 -1
  117. package/dist/separator/separator.d.ts +1 -1
  118. package/dist/shortcut-recorder/shortcut-recorder.js +1 -1
  119. package/dist/sparkline/sparkline.d.ts +27 -2
  120. package/dist/sparkline/sparkline.js +115 -18
  121. package/dist/sparkline/sparkline.js.map +1 -1
  122. package/dist/stat-card/index.d.ts +2 -2
  123. package/dist/stat-card/index.js +2 -2
  124. package/dist/stat-card/stat-card.d.ts +32 -1
  125. package/dist/stat-card/stat-card.js +52 -9
  126. package/dist/stat-card/stat-card.js.map +1 -1
  127. package/dist/styles.css +1 -1
  128. package/dist/table/index.d.ts +2 -2
  129. package/dist/table/index.js +2 -2
  130. package/dist/table/table.d.ts +211 -3
  131. package/dist/table/table.js +473 -93
  132. package/dist/table/table.js.map +1 -1
  133. package/dist/tabs/tabs.d.ts +4 -2
  134. package/dist/tabs/tabs.js +7 -5
  135. package/dist/tabs/tabs.js.map +1 -1
  136. package/dist/tag-input/tag-input.js +1 -1
  137. package/dist/text-shimmer/text-shimmer.d.ts +18 -2
  138. package/dist/text-shimmer/text-shimmer.js +5 -2
  139. package/dist/text-shimmer/text-shimmer.js.map +1 -1
  140. package/dist/textarea/textarea.d.ts +7 -0
  141. package/dist/textarea/textarea.js +4 -2
  142. package/dist/textarea/textarea.js.map +1 -1
  143. package/dist/toast/toast.d.ts +2 -2
  144. package/dist/toggle-button/index.d.ts +2 -0
  145. package/dist/toggle-button/index.js +2 -0
  146. package/dist/toggle-button/toggle-button.d.ts +93 -0
  147. package/dist/toggle-button/toggle-button.js +199 -0
  148. package/dist/toggle-button/toggle-button.js.map +1 -0
  149. package/package.json +19 -3
@@ -2,9 +2,9 @@
2
2
  import { cn, cnAll } from "../lib/cn.js";
3
3
  import { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect } from "../lib/motion.js";
4
4
  import { useHeightFollow } from "../lib/height-follow.js";
5
+ import { FieldShell, useFieldWiring } from "../lib/field-shell.js";
5
6
  import { fieldSurfaceVariants } from "../lib/field-surface.js";
6
7
  import { useLeavingList } from "../lib/leaving-list.js";
7
- import { FieldShell, useFieldWiring } from "../lib/field-shell.js";
8
8
  import { forwardRef, useCallback, useImperativeHandle, useRef, useState } from "react";
9
9
  import { Button, Group, Input, Tag, TagGroup, TagList } from "react-aria-components";
10
10
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -1,3 +1,4 @@
1
+ import { ReactNode } from "react";
1
2
  //#region src/text-shimmer/text-shimmer.d.ts
2
3
  /**
3
4
  * TextShimmer: a calm light sweep across a short status line, such as
@@ -10,10 +11,25 @@
10
11
  * over a short transition. A changed label rises in place. Under reduced motion
11
12
  * nothing moves and the text stays in its muted base colour. The component sets
12
13
  * `aria-busy`; announce completion from your own live region.
14
+ *
15
+ * The label can carry inline markup, such as a `<strong>` name or a count: the
16
+ * sweep is clipped to all the text inside it. Keep that markup to plain inline
17
+ * text (no icons, backgrounds, transforms or positioning, which the text clip
18
+ * does not reach). Give non-string children a `swapKey` so a changed label still
19
+ * rises in place.
13
20
  */
14
21
  export interface TextShimmerProps {
15
- /** The status text. Keep it to one short line. */
16
- children: string;
22
+ /**
23
+ * The status text. Keep it to one short line. A string, or inline text with
24
+ * markup such as `<strong>` or a count; the sweep paints over all of it.
25
+ */
26
+ children: ReactNode;
27
+ /**
28
+ * Identifies the label, so a changed label rises in place. A string or number
29
+ * child is its own key. Other children have no key unless you pass one, and
30
+ * then update in place without the rise. An empty string counts as unset.
31
+ */
32
+ swapKey?: string;
17
33
  /** Sweep while work is ongoing. @default true */
18
34
  active?: boolean;
19
35
  /** Seconds for one pass of the band across the text (the pause between passes comes on top). @default 1.8 */
@@ -4,9 +4,12 @@ import { SwapText } from "../lib/swap-text.js";
4
4
  import { createElement, forwardRef } from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  //#region src/text-shimmer/text-shimmer.tsx
7
+ /** The key for children that bring none of their own: it never changes, so they never swap. */
8
+ const STILL_KEY = "text-shimmer";
7
9
  /** The sweep takes this share of one cycle; the rest is the rest between passes. */
8
10
  const SWEEP_SHARE = .76;
9
- const TextShimmer = forwardRef(function TextShimmer({ children, active = true, duration = 1.8, as = "span", className, id }, ref) {
11
+ const TextShimmer = forwardRef(function TextShimmer({ children, swapKey, active = true, duration = 1.8, as = "span", className, id }, ref) {
12
+ const key = swapKey || (typeof children === "string" || typeof children === "number" ? String(children) : STILL_KEY);
10
13
  return createElement(as, {
11
14
  ref,
12
15
  id,
@@ -16,7 +19,7 @@ const TextShimmer = forwardRef(function TextShimmer({ children, active = true, d
16
19
  style: { "--text-shimmer-cycle": `${duration / SWEEP_SHARE}s` },
17
20
  className: cn("shimmer-text", className)
18
21
  }, /* @__PURE__ */ jsx(SwapText, {
19
- swapKey: children,
22
+ swapKey: key,
20
23
  children: /* @__PURE__ */ jsx("span", {
21
24
  className: "shimmer-text-label",
22
25
  children
@@ -1 +1 @@
1
- {"version":3,"file":"text-shimmer.js","names":[],"sources":["../../src/text-shimmer/text-shimmer.tsx"],"sourcesContent":["\"use client\";\n\nimport { createElement, forwardRef, type CSSProperties } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * TextShimmer: a calm light sweep across a short status line, such as\n * \"Thinking\" or \"Generating summary\", to show that work is ongoing.\n *\n * The band is the current text colour over a muted base, painted by CSS alone\n * (a gradient clipped to the text, its position driven by a registered custom\n * property), so nothing re-renders per frame. A pause of about a quarter of the\n * cycle follows each pass. When `active` turns false the text settles to solid\n * over a short transition. A changed label rises in place. Under reduced motion\n * nothing moves and the text stays in its muted base colour. The component sets\n * `aria-busy`; announce completion from your own live region.\n */\nexport interface TextShimmerProps {\n /** The status text. Keep it to one short line. */\n children: string;\n /** Sweep while work is ongoing. @default true */\n active?: boolean;\n /** Seconds for one pass of the band across the text (the pause between passes comes on top). @default 1.8 */\n duration?: number;\n /** The element to render. @default \"span\" */\n as?: \"span\" | \"p\" | \"div\" | \"h2\" | \"h3\" | \"h4\";\n /** Extra classes for the element. Merged last. */\n className?: string;\n id?: string;\n}\n\n/** The sweep takes this share of one cycle; the rest is the rest between passes. */\nconst SWEEP_SHARE = 0.76;\n\nexport const TextShimmer = forwardRef<HTMLElement, TextShimmerProps>(function TextShimmer(\n { children, active = true, duration = 1.8, as = \"span\", className, id },\n ref,\n) {\n return createElement(\n as,\n {\n ref,\n id,\n \"data-slot\": \"text-shimmer\",\n \"data-state\": active ? \"active\" : \"idle\",\n \"aria-busy\": active || undefined,\n style: { \"--text-shimmer-cycle\": `${duration / SWEEP_SHARE}s` } as CSSProperties,\n className: cn(\"shimmer-text\", className),\n },\n <SwapText swapKey={children}>\n <span className=\"shimmer-text-label\">{children}</span>\n </SwapText>,\n );\n});\n"],"mappings":";;;;;;;AAkCA,MAAM,cAAc;AAEpB,MAAa,cAAc,WAA0C,SAAS,YAC5E,EAAE,UAAU,SAAS,MAAM,WAAW,KAAK,KAAK,QAAQ,WAAW,MACnE,KACA;CACA,OAAO,cACL,IACA;EACE;EACA;EACA,aAAa;EACb,cAAc,SAAS,WAAW;EAClC,aAAa,UAAU,KAAA;EACvB,OAAO,EAAE,wBAAwB,GAAG,WAAW,YAAY,GAAG;EAC9D,WAAW,GAAG,gBAAgB,SAAS;CACzC,GACA,oBAAC,UAAD;EAAU,SAAS;EACjB,UAAA,oBAAC,QAAD;GAAM,WAAU;GAAsB;EAAe,CAAA;CAC7C,CAAA,CACZ;AACF,CAAC"}
1
+ {"version":3,"file":"text-shimmer.js","names":[],"sources":["../../src/text-shimmer/text-shimmer.tsx"],"sourcesContent":["\"use client\";\n\nimport { createElement, forwardRef, type CSSProperties, type ReactNode } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * TextShimmer: a calm light sweep across a short status line, such as\n * \"Thinking\" or \"Generating summary\", to show that work is ongoing.\n *\n * The band is the current text colour over a muted base, painted by CSS alone\n * (a gradient clipped to the text, its position driven by a registered custom\n * property), so nothing re-renders per frame. A pause of about a quarter of the\n * cycle follows each pass. When `active` turns false the text settles to solid\n * over a short transition. A changed label rises in place. Under reduced motion\n * nothing moves and the text stays in its muted base colour. The component sets\n * `aria-busy`; announce completion from your own live region.\n *\n * The label can carry inline markup, such as a `<strong>` name or a count: the\n * sweep is clipped to all the text inside it. Keep that markup to plain inline\n * text (no icons, backgrounds, transforms or positioning, which the text clip\n * does not reach). Give non-string children a `swapKey` so a changed label still\n * rises in place.\n */\nexport interface TextShimmerProps {\n /**\n * The status text. Keep it to one short line. A string, or inline text with\n * markup such as `<strong>` or a count; the sweep paints over all of it.\n */\n children: ReactNode;\n /**\n * Identifies the label, so a changed label rises in place. A string or number\n * child is its own key. Other children have no key unless you pass one, and\n * then update in place without the rise. An empty string counts as unset.\n */\n swapKey?: string;\n /** Sweep while work is ongoing. @default true */\n active?: boolean;\n /** Seconds for one pass of the band across the text (the pause between passes comes on top). @default 1.8 */\n duration?: number;\n /** The element to render. @default \"span\" */\n as?: \"span\" | \"p\" | \"div\" | \"h2\" | \"h3\" | \"h4\";\n /** Extra classes for the element. Merged last. */\n className?: string;\n id?: string;\n}\n\n/** The key for children that bring none of their own: it never changes, so they never swap. */\nconst STILL_KEY = \"text-shimmer\";\n\n/** The sweep takes this share of one cycle; the rest is the rest between passes. */\nconst SWEEP_SHARE = 0.76;\n\nexport const TextShimmer = forwardRef<HTMLElement, TextShimmerProps>(function TextShimmer(\n { children, swapKey, active = true, duration = 1.8, as = \"span\", className, id },\n ref,\n) {\n const key =\n swapKey ||\n (typeof children === \"string\" || typeof children === \"number\" ? String(children) : STILL_KEY);\n return createElement(\n as,\n {\n ref,\n id,\n \"data-slot\": \"text-shimmer\",\n \"data-state\": active ? \"active\" : \"idle\",\n \"aria-busy\": active || undefined,\n style: { \"--text-shimmer-cycle\": `${duration / SWEEP_SHARE}s` } as CSSProperties,\n className: cn(\"shimmer-text\", className),\n },\n <SwapText swapKey={key}>\n <span className=\"shimmer-text-label\">{children}</span>\n </SwapText>,\n );\n});\n"],"mappings":";;;;;;;AAiDA,MAAM,YAAY;;AAGlB,MAAM,cAAc;AAEpB,MAAa,cAAc,WAA0C,SAAS,YAC5E,EAAE,UAAU,SAAS,SAAS,MAAM,WAAW,KAAK,KAAK,QAAQ,WAAW,MAC5E,KACA;CACA,MAAM,MACJ,YACC,OAAO,aAAa,YAAY,OAAO,aAAa,WAAW,OAAO,QAAQ,IAAI;CACrF,OAAO,cACL,IACA;EACE;EACA;EACA,aAAa;EACb,cAAc,SAAS,WAAW;EAClC,aAAa,UAAU,KAAA;EACvB,OAAO,EAAE,wBAAwB,GAAG,WAAW,YAAY,GAAG;EAC9D,WAAW,GAAG,gBAAgB,SAAS;CACzC,GACA,oBAAC,UAAD;EAAU,SAAS;EACjB,UAAA,oBAAC,QAAD;GAAM,WAAU;GAAsB;EAAe,CAAA;CAC7C,CAAA,CACZ;AACF,CAAC"}
@@ -44,6 +44,13 @@ export interface TextareaProps extends Omit<TextAreaProps, "className" | "style"
44
44
  * `undefined` value closes the row.
45
45
  */
46
46
  error?: string;
47
+ /**
48
+ * The field must be filled: the textarea is `required` (announced by screen
49
+ * readers, and checked by native form validation) and the label shows the
50
+ * required marker. The same prop as PasswordField's.
51
+ * @default false
52
+ */
53
+ isRequired?: boolean;
47
54
  }
48
55
  export declare const Textarea: import("react").ForwardRefExoticComponent<TextareaProps & import("react").RefAttributes<HTMLTextAreaElement>>;
49
56
  //#endregion
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
- import { fieldSurfaceVariants } from "../lib/field-surface.js";
4
3
  import { FieldShell, hasFieldProps, useFieldWiring } from "../lib/field-shell.js";
4
+ import { fieldSurfaceVariants } from "../lib/field-surface.js";
5
5
  import { cva } from "class-variance-authority";
6
6
  import { forwardRef } from "react";
7
7
  import { TextArea } from "react-aria-components";
@@ -27,7 +27,7 @@ const textareaVariants = cva([
27
27
  "leading-(--leading-normal)"
28
28
  ]);
29
29
  const Textarea = forwardRef(function Textarea(allProps, ref) {
30
- const { className, label, description, error, ...props } = allProps;
30
+ const { className, label, description, error, isRequired, ...props } = allProps;
31
31
  const field = useFieldWiring({
32
32
  enabled: hasFieldProps(allProps),
33
33
  id: props.id,
@@ -39,6 +39,7 @@ const Textarea = forwardRef(function Textarea(allProps, ref) {
39
39
  const textarea = /* @__PURE__ */ jsx(TextArea, {
40
40
  ref,
41
41
  ...props,
42
+ ...isRequired ? { required: true } : {},
42
43
  ...field.controlProps,
43
44
  "data-slot": "textarea",
44
45
  className: cn(textareaVariants(), className)
@@ -51,6 +52,7 @@ const Textarea = forwardRef(function Textarea(allProps, ref) {
51
52
  hintId: field.hintId,
52
53
  error,
53
54
  errorId: field.errorId,
55
+ required: isRequired,
54
56
  wordExit: "quick",
55
57
  children: textarea
56
58
  });
@@ -1 +1 @@
1
- {"version":3,"file":"textarea.js","names":["AriaTextArea"],"sources":["../../src/textarea/textarea.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n TextArea as AriaTextArea,\n type TextAreaProps as AriaTextAreaProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { FieldShell, hasFieldProps, useFieldWiring } from \"../lib/field-shell.js\";\nimport { fieldSurfaceVariants } from \"../lib/field-surface.js\";\n\n/**\n * Textarea, ported from the legacy component onto React Aria's TextArea.\n *\n * Census: 3 files, 3 uses -- `value`, `onChange`, `placeholder`, `id`,\n * `ref`. All native props, all still native here.\n *\n * The surface comes from `fieldSurfaceVariants`, shared with Input. The two\n * legacy files styled it separately and had already drifted -- different\n * background, different focus ring, invalid styling on one and not the\n * other -- and none of those differences was a decision anyone made.\n */\nexport const textareaVariants = cva([\n fieldSurfaceVariants(),\n\n // min-h-20 is 80px, the legacy min-h-[80px] -- but as a spacing-scale step\n // rather than a bracket value, which check:hardcoding rejects.\n \"min-h-20\",\n \"px-(--density-control-pad-md) py-(--density-control-pad-sm)\",\n \"text-base\",\n\n // A textarea is resizable by the user agent, and horizontal resize breaks\n // any layout it sits in. Vertical only.\n \"resize-y\",\n // Height is content, not a control slot, so the line-height has to be set\n // explicitly -- unset it computes to `normal`, which varies with the\n // font's own metrics.\n \"leading-(--leading-normal)\",\n]);\n\n/**\n * A multi-line text field.\n *\n * @example\n * <Textarea placeholder=\"Tell us more\" value={v} onChange={e => set(e.target.value)} />\n */\nexport interface TextareaProps extends Omit<AriaTextAreaProps, \"className\" | \"style\"> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * A visible label above the field, tied to it with `htmlFor`. Passing any of\n * `label`, `description` or `error` (even as `undefined`) wraps the field;\n * with none of them the DOM is exactly the bare textarea. Do not also pass\n * `aria-label`: it wins over the label (WCAG 2.5.3). Ignored inside a React\n * Aria `TextField`, which supplies its own label and messages.\n */\n label?: ReactNode;\n /**\n * Helper text under the field, named by `aria-describedby`. A count in it,\n * such as \"12/200 characters\", rolls digit by digit as it changes. Opens on a\n * spring when it appears after mount; nothing animates on first render, and\n * reduced motion swaps it instantly. A string, because the copy is animated\n * word by word.\n */\n description?: string;\n /**\n * Error text under the field. Sets `aria-invalid`, so the field takes its\n * invalid border, and is announced as an alert when it appears. An empty or\n * `undefined` value closes the row.\n */\n error?: string;\n}\n\nexport const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(\n function Textarea(allProps, ref) {\n const { className, label, description, error, ...props } = allProps;\n const field = useFieldWiring({\n enabled: hasFieldProps(allProps),\n id: props.id,\n describedBy: props[\"aria-describedby\"],\n invalid: props[\"aria-invalid\"],\n description,\n error,\n });\n const textarea = (\n <AriaTextArea\n ref={ref}\n {...props}\n {...field.controlProps}\n data-slot=\"textarea\"\n className={cn(textareaVariants(), className)}\n />\n );\n if (!field.active) return textarea;\n return (\n <FieldShell\n label={label}\n controlId={field.controlId}\n description={description}\n hintId={field.hintId}\n error={error}\n errorId={field.errorId}\n wordExit=\"quick\"\n >\n {textarea}\n </FieldShell>\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,mBAAmB,IAAI;CAClC,qBAAqB;CAIrB;CACA;CACA;CAIA;CAIA;AACF,CAAC;AAmCD,MAAa,WAAW,WACtB,SAAS,SAAS,UAAU,KAAK;CAC/B,MAAM,EAAE,WAAW,OAAO,aAAa,OAAO,GAAG,UAAU;CAC3D,MAAM,QAAQ,eAAe;EAC3B,SAAS,cAAc,QAAQ;EAC/B,IAAI,MAAM;EACV,aAAa,MAAM;EACnB,SAAS,MAAM;EACf;EACA;CACF,CAAC;CACD,MAAM,WACJ,oBAACA,UAAD;EACO;EACL,GAAI;EACJ,GAAI,MAAM;EACV,aAAU;EACV,WAAW,GAAG,iBAAiB,GAAG,SAAS;CAC5C,CAAA;CAEH,IAAI,CAAC,MAAM,QAAQ,OAAO;CAC1B,OACE,oBAAC,YAAD;EACS;EACP,WAAW,MAAM;EACJ;EACb,QAAQ,MAAM;EACP;EACP,SAAS,MAAM;EACf,UAAS;EAER,UAAA;CACS,CAAA;AAEhB,CACF"}
1
+ {"version":3,"file":"textarea.js","names":["AriaTextArea"],"sources":["../../src/textarea/textarea.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n TextArea as AriaTextArea,\n type TextAreaProps as AriaTextAreaProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { FieldShell, hasFieldProps, useFieldWiring } from \"../lib/field-shell.js\";\nimport { fieldSurfaceVariants } from \"../lib/field-surface.js\";\n\n/**\n * Textarea, ported from the legacy component onto React Aria's TextArea.\n *\n * Census: 3 files, 3 uses -- `value`, `onChange`, `placeholder`, `id`,\n * `ref`. All native props, all still native here.\n *\n * The surface comes from `fieldSurfaceVariants`, shared with Input. The two\n * legacy files styled it separately and had already drifted -- different\n * background, different focus ring, invalid styling on one and not the\n * other -- and none of those differences was a decision anyone made.\n */\nexport const textareaVariants = cva([\n fieldSurfaceVariants(),\n\n // min-h-20 is 80px, the legacy min-h-[80px] -- but as a spacing-scale step\n // rather than a bracket value, which check:hardcoding rejects.\n \"min-h-20\",\n \"px-(--density-control-pad-md) py-(--density-control-pad-sm)\",\n \"text-base\",\n\n // A textarea is resizable by the user agent, and horizontal resize breaks\n // any layout it sits in. Vertical only.\n \"resize-y\",\n // Height is content, not a control slot, so the line-height has to be set\n // explicitly -- unset it computes to `normal`, which varies with the\n // font's own metrics.\n \"leading-(--leading-normal)\",\n]);\n\n/**\n * A multi-line text field.\n *\n * @example\n * <Textarea placeholder=\"Tell us more\" value={v} onChange={e => set(e.target.value)} />\n */\nexport interface TextareaProps extends Omit<AriaTextAreaProps, \"className\" | \"style\"> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * A visible label above the field, tied to it with `htmlFor`. Passing any of\n * `label`, `description` or `error` (even as `undefined`) wraps the field;\n * with none of them the DOM is exactly the bare textarea. Do not also pass\n * `aria-label`: it wins over the label (WCAG 2.5.3). Ignored inside a React\n * Aria `TextField`, which supplies its own label and messages.\n */\n label?: ReactNode;\n /**\n * Helper text under the field, named by `aria-describedby`. A count in it,\n * such as \"12/200 characters\", rolls digit by digit as it changes. Opens on a\n * spring when it appears after mount; nothing animates on first render, and\n * reduced motion swaps it instantly. A string, because the copy is animated\n * word by word.\n */\n description?: string;\n /**\n * Error text under the field. Sets `aria-invalid`, so the field takes its\n * invalid border, and is announced as an alert when it appears. An empty or\n * `undefined` value closes the row.\n */\n error?: string;\n /**\n * The field must be filled: the textarea is `required` (announced by screen\n * readers, and checked by native form validation) and the label shows the\n * required marker. The same prop as PasswordField's.\n * @default false\n */\n isRequired?: boolean;\n}\n\nexport const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(\n function Textarea(allProps, ref) {\n const { className, label, description, error, isRequired, ...props } = allProps;\n const field = useFieldWiring({\n enabled: hasFieldProps(allProps),\n id: props.id,\n describedBy: props[\"aria-describedby\"],\n invalid: props[\"aria-invalid\"],\n description,\n error,\n });\n const textarea = (\n <AriaTextArea\n ref={ref}\n {...props}\n {...(isRequired ? { required: true } : {})}\n {...field.controlProps}\n data-slot=\"textarea\"\n className={cn(textareaVariants(), className)}\n />\n );\n if (!field.active) return textarea;\n return (\n <FieldShell\n label={label}\n controlId={field.controlId}\n description={description}\n hintId={field.hintId}\n error={error}\n errorId={field.errorId}\n required={isRequired}\n wordExit=\"quick\"\n >\n {textarea}\n </FieldShell>\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,mBAAmB,IAAI;CAClC,qBAAqB;CAIrB;CACA;CACA;CAIA;CAIA;AACF,CAAC;AA0CD,MAAa,WAAW,WACtB,SAAS,SAAS,UAAU,KAAK;CAC/B,MAAM,EAAE,WAAW,OAAO,aAAa,OAAO,YAAY,GAAG,UAAU;CACvE,MAAM,QAAQ,eAAe;EAC3B,SAAS,cAAc,QAAQ;EAC/B,IAAI,MAAM;EACV,aAAa,MAAM;EACnB,SAAS,MAAM;EACf;EACA;CACF,CAAC;CACD,MAAM,WACJ,oBAACA,UAAD;EACO;EACL,GAAI;EACJ,GAAK,aAAa,EAAE,UAAU,KAAK,IAAI,CAAC;EACxC,GAAI,MAAM;EACV,aAAU;EACV,WAAW,GAAG,iBAAiB,GAAG,SAAS;CAC5C,CAAA;CAEH,IAAI,CAAC,MAAM,QAAQ,OAAO;CAC1B,OACE,oBAAC,YAAD;EACS;EACP,WAAW,MAAM;EACJ;EACb,QAAQ,MAAM;EACP;EACP,SAAS,MAAM;EACf,UAAU;EACV,UAAS;EAER,UAAA;CACS,CAAA;AAEhB,CACF"}
@@ -34,11 +34,11 @@ import { UNSTABLE_ToastQueue } from "react-aria-components";
34
34
  /** Where the region sits. Fixed, above everything, out of the way of content. */
35
35
  export declare const toastRegionVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
36
36
  export declare const toastVariants: (props?: ({
37
- tone?: "success" | "warning" | "danger" | "neutral" | "info" | null | undefined;
37
+ tone?: "neutral" | "success" | "warning" | "danger" | "info" | null | undefined;
38
38
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
39
39
  /** The status icon's circle: the tone's light fill with its AA text colour. */
40
40
  export declare const toastIconVariants: (props?: ({
41
- tone?: "success" | "warning" | "danger" | "neutral" | "info" | null | undefined;
41
+ tone?: "neutral" | "success" | "warning" | "danger" | "info" | null | undefined;
42
42
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
43
43
  export declare const toastCloseVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
44
44
  /** What a toast carries. `title` is required: an unnamed toast announces nothing. */
@@ -0,0 +1,2 @@
1
+ import { ToggleButton, ToggleButtonGroup, ToggleButtonGroupProps, ToggleButtonProps, toggleButtonGroupVariants, toggleButtonVariants } from "./toggle-button.js";
2
+ export { ToggleButton, ToggleButtonGroup, type ToggleButtonGroupProps, type ToggleButtonProps, toggleButtonGroupVariants, toggleButtonVariants };
@@ -0,0 +1,2 @@
1
+ import { ToggleButton, ToggleButtonGroup, toggleButtonGroupVariants, toggleButtonVariants } from "./toggle-button.js";
2
+ export { ToggleButton, ToggleButtonGroup, toggleButtonGroupVariants, toggleButtonVariants };
@@ -0,0 +1,93 @@
1
+ import { VariantProps } from "class-variance-authority";
2
+ import { ReactNode } from "react";
3
+ import { ToggleButtonGroupProps, ToggleButtonProps } from "react-aria-components";
4
+ //#region src/toggle-button/toggle-button.d.ts
5
+ /**
6
+ * ToggleButton: a button that stays pressed. One question, two answers
7
+ * ("Has new reply", "Primary key"), where a Switch would sit in a form row and
8
+ * a Checkbox would need a label beside a box.
9
+ *
10
+ * Built on React Aria's `ToggleButton`, so it is a real `<button>` with
11
+ * `aria-pressed`; in a {@link ToggleButtonGroup} that allows one choice the
12
+ * same buttons are a toolbar of `aria-pressed` buttons. It is NOT Button with `aria-pressed` and a
13
+ * swapped variant, which is what the apps hand-build today: the selected look
14
+ * here does not rest on colour.
15
+ *
16
+ * SELECTED IS SHOWN THREE WAYS. A tint, a `primary-strong` edge and a check
17
+ * mark drawn in `currentColor`, so the state survives forced colours and
18
+ * colour-blindness (WCAG 1.4.1). The mark sits INSIDE the control's own box,
19
+ * in the corner, so it never shifts the layout when it appears and is never
20
+ * clipped by a table row or scroll area outside the border. An icon-only
21
+ * toggle carries it too.
22
+ *
23
+ * FOCUS follows the field surface: on `:focus-visible` the control's own 1px
24
+ * border turns dark and nothing else is drawn, no ring. Forced colours
25
+ * override a border colour, so only there does an outline come back.
26
+ *
27
+ * Every string is a STATIC literal. Tailwind scans source text, so a class
28
+ * assembled at runtime is invisible to it and would never be emitted.
29
+ */
30
+ export declare const toggleButtonVariants: (props?: ({
31
+ size?: "sm" | "md" | null | undefined;
32
+ iconOnly?: boolean | null | undefined;
33
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
34
+ /** The group's row. Buttons wrap rather than overflow a narrow container. */
35
+ export declare const toggleButtonGroupVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
36
+ interface ToggleButtonProps$1 extends Omit<ToggleButtonProps, "className" | "style" | "children">, Omit<VariantProps<typeof toggleButtonVariants>, "iconOnly"> {
37
+ /** The label, or the glyph of an `iconOnly` toggle. */
38
+ children?: ReactNode;
39
+ /**
40
+ * Only a glyph, in a square button. Pass the glyph as `children` and name the
41
+ * button with `aria-label`; without one it has no accessible name.
42
+ *
43
+ * @default false
44
+ */
45
+ iconOnly?: boolean;
46
+ /** Extra classes. Merged last, so they win conflicts with ours. */
47
+ className?: string;
48
+ }
49
+ /**
50
+ * A button that is on or off. Controlled with `isSelected` and
51
+ * `onChange(isSelected)`, or uncontrolled with `defaultSelected`.
52
+ *
53
+ * Give an icon-only toggle an `aria-label`, and wrap it in a `TooltipTrigger`
54
+ * when the glyph alone is not enough. The label should name the control, not
55
+ * its state: assistive technology says "pressed" itself.
56
+ *
57
+ * @example
58
+ * <ToggleButton isSelected={unread} onChange={setUnread}>Has new reply</ToggleButton>
59
+ * <ToggleButton iconOnly aria-label="Primary key"><KeyIcon /></ToggleButton>
60
+ */
61
+ declare const ToggleButton$1: import("react").ForwardRefExoticComponent<ToggleButtonProps$1 & import("react").RefAttributes<HTMLButtonElement>>;
62
+ interface ToggleButtonGroupProps$1 extends Omit<ToggleButtonGroupProps, "className" | "style"> {
63
+ /** Extra classes. Merged last, so they win conflicts with ours. */
64
+ className?: string;
65
+ }
66
+ /**
67
+ * A row of ToggleButtons that share a selection. Always a toolbar of
68
+ * `aria-pressed` buttons: ONE tab stop, arrow keys move focus, Space or Enter
69
+ * toggles the focused button (selection never follows focus).
70
+ *
71
+ * `selectionMode="single"` (the default) lets one button be pressed at a time:
72
+ * pressing another moves the selection to it, and pressing the pressed one
73
+ * clears it unless `disallowEmptySelection`. Tab enters the group on the pressed
74
+ * button (on the first when none is). `"multiple"` lets any number be pressed.
75
+ * Each child needs an `id`, which is its key in `selectedKeys`.
76
+ *
77
+ * Why not `role="radiogroup"`: React Aria's single mode is a radio group in
78
+ * name only. Its arrows move focus and do not check, and every button is a tab
79
+ * stop, which is not the APG radio pattern. A true radio group needs the
80
+ * selection to follow focus, which a button that you press does not do. The
81
+ * toolbar of pressed buttons is the APG pattern that matches how these behave.
82
+ * If you need a radio group, use `RadioGroup` (`variant="card"` for the framed look).
83
+ *
84
+ * @example
85
+ * <ToggleButtonGroup aria-label="Range" selectionMode="single" disallowEmptySelection>
86
+ * <ToggleButton id="7d">7 days</ToggleButton>
87
+ * <ToggleButton id="30d">30 days</ToggleButton>
88
+ * </ToggleButtonGroup>
89
+ */
90
+ declare const ToggleButtonGroup$1: import("react").ForwardRefExoticComponent<ToggleButtonGroupProps$1 & import("react").RefAttributes<HTMLDivElement>>;
91
+ //#endregion
92
+ export { ToggleButton$1 as ToggleButton, ToggleButtonGroup$1 as ToggleButtonGroup, ToggleButtonGroupProps$1 as ToggleButtonGroupProps, ToggleButtonProps$1 as ToggleButtonProps };
93
+ //# sourceMappingURL=toggle-button.d.ts.map
@@ -0,0 +1,199 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { mergeRefs } from "../lib/merge-refs.js";
4
+ import { useIsomorphicLayoutEffect } from "../lib/motion.js";
5
+ import { cva } from "class-variance-authority";
6
+ import { createContext, forwardRef, useContext, useMemo, useRef, useState } from "react";
7
+ import { ToggleButton, ToggleButtonGroup } from "react-aria-components";
8
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
+ //#region src/toggle-button/toggle-button.tsx
10
+ /**
11
+ * ToggleButton: a button that stays pressed. One question, two answers
12
+ * ("Has new reply", "Primary key"), where a Switch would sit in a form row and
13
+ * a Checkbox would need a label beside a box.
14
+ *
15
+ * Built on React Aria's `ToggleButton`, so it is a real `<button>` with
16
+ * `aria-pressed`; in a {@link ToggleButtonGroup} that allows one choice the
17
+ * same buttons are a toolbar of `aria-pressed` buttons. It is NOT Button with `aria-pressed` and a
18
+ * swapped variant, which is what the apps hand-build today: the selected look
19
+ * here does not rest on colour.
20
+ *
21
+ * SELECTED IS SHOWN THREE WAYS. A tint, a `primary-strong` edge and a check
22
+ * mark drawn in `currentColor`, so the state survives forced colours and
23
+ * colour-blindness (WCAG 1.4.1). The mark sits INSIDE the control's own box,
24
+ * in the corner, so it never shifts the layout when it appears and is never
25
+ * clipped by a table row or scroll area outside the border. An icon-only
26
+ * toggle carries it too.
27
+ *
28
+ * FOCUS follows the field surface: on `:focus-visible` the control's own 1px
29
+ * border turns dark and nothing else is drawn, no ring. Forced colours
30
+ * override a border colour, so only there does an outline come back.
31
+ *
32
+ * Every string is a STATIC literal. Tailwind scans source text, so a class
33
+ * assembled at runtime is invisible to it and would never be emitted.
34
+ */
35
+ const toggleButtonVariants = cva([
36
+ "relative inline-flex items-center justify-center gap-(--density-control-gap) shrink-0 whitespace-nowrap",
37
+ "rounded-control font-sans font-medium text-foreground",
38
+ "cursor-pointer select-none box-border",
39
+ "border-(length:--border-width) border-border bg-surface shadow-surface",
40
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0",
41
+ "[&_svg:not([class*='size-'])]:size-(--icon-sm)",
42
+ "[-webkit-tap-highlight-color:transparent]",
43
+ "motion-safe:transition-[border-color,background-color] motion-safe:duration-(--duration-quick) motion-safe:ease-settle",
44
+ "data-[hovered]:bg-muted data-[hovered]:border-border-strong",
45
+ "data-[selected]:bg-primary-subtle data-[selected]:border-primary-strong",
46
+ "data-[selected]:data-[hovered]:bg-primary-subtle",
47
+ "outline-none",
48
+ "data-[focus-visible]:border-foreground",
49
+ "data-[focus-visible]:data-[selected]:border-foreground",
50
+ "forced-colors:data-[focus-visible]:outline-solid",
51
+ "forced-colors:data-[focus-visible]:outline-(length:--focus-ring-width)",
52
+ "forced-colors:data-[focus-visible]:outline-offset-(--focus-ring-offset)",
53
+ "data-[disabled]:cursor-not-allowed data-[disabled]:bg-muted data-[disabled]:text-text-disabled data-[disabled]:border-border",
54
+ "data-[disabled]:data-[selected]:bg-muted data-[disabled]:data-[selected]:border-border"
55
+ ], {
56
+ variants: {
57
+ size: {
58
+ sm: "h-(--density-control-h-sm) text-sm",
59
+ md: "h-(--density-control-h-md) text-sm"
60
+ },
61
+ /** A square button that holds only a glyph. It must carry an `aria-label`. */
62
+ iconOnly: {
63
+ true: "px-0 text-base",
64
+ false: "ps-4 pe-5"
65
+ }
66
+ },
67
+ compoundVariants: [{
68
+ size: "sm",
69
+ iconOnly: true,
70
+ class: "w-(--density-control-h-sm)"
71
+ }, {
72
+ size: "md",
73
+ iconOnly: true,
74
+ class: "w-(--density-control-h-md)"
75
+ }],
76
+ defaultVariants: {
77
+ size: "sm",
78
+ iconOnly: false
79
+ }
80
+ });
81
+ /** The group's row. Buttons wrap rather than overflow a narrow container. */
82
+ const toggleButtonGroupVariants = cva(["box-border inline-flex flex-wrap gap-2 font-sans", "data-[orientation=vertical]:flex-col"]);
83
+ const TabStopContext = createContext(null);
84
+ const CHECK_PLACEMENT = {
85
+ label: "size-3 end-1.5 top-1/2 -translate-y-1/2",
86
+ icon: "size-2.5 end-1.5 top-1.5"
87
+ };
88
+ /**
89
+ * A button that is on or off. Controlled with `isSelected` and
90
+ * `onChange(isSelected)`, or uncontrolled with `defaultSelected`.
91
+ *
92
+ * Give an icon-only toggle an `aria-label`, and wrap it in a `TooltipTrigger`
93
+ * when the glyph alone is not enough. The label should name the control, not
94
+ * its state: assistive technology says "pressed" itself.
95
+ *
96
+ * @example
97
+ * <ToggleButton isSelected={unread} onChange={setUnread}>Has new reply</ToggleButton>
98
+ * <ToggleButton iconOnly aria-label="Primary key"><KeyIcon /></ToggleButton>
99
+ */
100
+ const ToggleButton$1 = forwardRef(function ToggleButton$2({ className, size, iconOnly = false, children, excludeFromTabOrder, ...props }, ref) {
101
+ const stop = useContext(TabStopContext);
102
+ const skipTab = stop !== null && String(props.id) !== stop;
103
+ return /* @__PURE__ */ jsx(ToggleButton, {
104
+ ref,
105
+ ...props,
106
+ excludeFromTabOrder: excludeFromTabOrder ?? skipTab,
107
+ "data-slot": "toggle-button",
108
+ "data-key": props.id,
109
+ className: cn(toggleButtonVariants({
110
+ size,
111
+ iconOnly
112
+ }), className),
113
+ children: ({ isSelected }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [children, isSelected ? /* @__PURE__ */ jsx("svg", {
114
+ "aria-hidden": "true",
115
+ "data-slot": "toggle-button-check",
116
+ viewBox: "0 0 12 12",
117
+ fill: "none",
118
+ stroke: "currentColor",
119
+ strokeWidth: "1.75",
120
+ strokeLinecap: "round",
121
+ strokeLinejoin: "round",
122
+ className: cn("pointer-events-none absolute", iconOnly ? CHECK_PLACEMENT.icon : CHECK_PLACEMENT.label),
123
+ children: /* @__PURE__ */ jsx("path", { d: "M2.5 6.25 5 8.75 9.5 3.5" })
124
+ }) : null] })
125
+ });
126
+ });
127
+ /**
128
+ * A row of ToggleButtons that share a selection. Always a toolbar of
129
+ * `aria-pressed` buttons: ONE tab stop, arrow keys move focus, Space or Enter
130
+ * toggles the focused button (selection never follows focus).
131
+ *
132
+ * `selectionMode="single"` (the default) lets one button be pressed at a time:
133
+ * pressing another moves the selection to it, and pressing the pressed one
134
+ * clears it unless `disallowEmptySelection`. Tab enters the group on the pressed
135
+ * button (on the first when none is). `"multiple"` lets any number be pressed.
136
+ * Each child needs an `id`, which is its key in `selectedKeys`.
137
+ *
138
+ * Why not `role="radiogroup"`: React Aria's single mode is a radio group in
139
+ * name only. Its arrows move focus and do not check, and every button is a tab
140
+ * stop, which is not the APG radio pattern. A true radio group needs the
141
+ * selection to follow focus, which a button that you press does not do. The
142
+ * toolbar of pressed buttons is the APG pattern that matches how these behave.
143
+ * If you need a radio group, use `RadioGroup` (`variant="card"` for the framed look).
144
+ *
145
+ * @example
146
+ * <ToggleButtonGroup aria-label="Range" selectionMode="single" disallowEmptySelection>
147
+ * <ToggleButton id="7d">7 days</ToggleButton>
148
+ * <ToggleButton id="30d">30 days</ToggleButton>
149
+ * </ToggleButtonGroup>
150
+ */
151
+ const ToggleButtonGroup$1 = forwardRef(function ToggleButtonGroup$2({ className, selectionMode = "single", selectedKeys, defaultSelectedKeys, onSelectionChange, ...props }, ref) {
152
+ const single = selectionMode === "single";
153
+ const [inner, setInner] = useState(() => new Set(defaultSelectedKeys ?? []));
154
+ const chosen = selectedKeys === void 0 ? inner : new Set(selectedKeys);
155
+ const own = useRef(null);
156
+ const [stop, setStop] = useState(null);
157
+ useIsomorphicLayoutEffect(() => {
158
+ if (!single || chosen.size === 0) return setStop(null);
159
+ const enabled = [...own.current?.querySelectorAll("[data-slot=\"toggle-button\"]") ?? []].filter((el) => !el.disabled);
160
+ const pressed = enabled.find((el) => el.getAttribute("aria-pressed") === "true");
161
+ setStop((pressed ?? enabled[0])?.getAttribute("data-key") ?? null);
162
+ });
163
+ const shared = {
164
+ ref: useMemo(() => mergeRefs(own, ref), [ref]),
165
+ ...props,
166
+ "data-slot": "toggle-button-group",
167
+ className: cn(toggleButtonGroupVariants(), className)
168
+ };
169
+ if (!single) return /* @__PURE__ */ jsx(ToggleButtonGroup, {
170
+ ...shared,
171
+ selectionMode: "multiple",
172
+ selectedKeys,
173
+ defaultSelectedKeys,
174
+ onSelectionChange
175
+ });
176
+ const change = (next) => {
177
+ if (next === "all") return;
178
+ let keys = next;
179
+ if (next.size > 1) {
180
+ const added = [...next].find((key) => !chosen.has(key));
181
+ keys = /* @__PURE__ */ new Set([added ?? [...next][0]]);
182
+ }
183
+ if (selectedKeys === void 0) setInner(keys);
184
+ onSelectionChange?.(keys);
185
+ };
186
+ return /* @__PURE__ */ jsx(TabStopContext.Provider, {
187
+ value: stop,
188
+ children: /* @__PURE__ */ jsx(ToggleButtonGroup, {
189
+ ...shared,
190
+ selectionMode: "multiple",
191
+ selectedKeys: chosen,
192
+ onSelectionChange: change
193
+ })
194
+ });
195
+ });
196
+ //#endregion
197
+ export { ToggleButton$1 as ToggleButton, ToggleButtonGroup$1 as ToggleButtonGroup, toggleButtonGroupVariants, toggleButtonVariants };
198
+
199
+ //# sourceMappingURL=toggle-button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toggle-button.js","names":["ToggleButton","AriaToggleButton","ToggleButtonGroup","AriaToggleButtonGroup"],"sources":["../../src/toggle-button/toggle-button.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n createContext,\n forwardRef,\n useContext,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport {\n ToggleButton as AriaToggleButton,\n ToggleButtonGroup as AriaToggleButtonGroup,\n type ToggleButtonGroupProps as AriaToggleButtonGroupProps,\n type Key,\n type Selection,\n type ToggleButtonProps as AriaToggleButtonProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { useIsomorphicLayoutEffect } from \"../lib/motion.js\";\n\n/**\n * ToggleButton: a button that stays pressed. One question, two answers\n * (\"Has new reply\", \"Primary key\"), where a Switch would sit in a form row and\n * a Checkbox would need a label beside a box.\n *\n * Built on React Aria's `ToggleButton`, so it is a real `<button>` with\n * `aria-pressed`; in a {@link ToggleButtonGroup} that allows one choice the\n * same buttons are a toolbar of `aria-pressed` buttons. It is NOT Button with `aria-pressed` and a\n * swapped variant, which is what the apps hand-build today: the selected look\n * here does not rest on colour.\n *\n * SELECTED IS SHOWN THREE WAYS. A tint, a `primary-strong` edge and a check\n * mark drawn in `currentColor`, so the state survives forced colours and\n * colour-blindness (WCAG 1.4.1). The mark sits INSIDE the control's own box,\n * in the corner, so it never shifts the layout when it appears and is never\n * clipped by a table row or scroll area outside the border. An icon-only\n * toggle carries it too.\n *\n * FOCUS follows the field surface: on `:focus-visible` the control's own 1px\n * border turns dark and nothing else is drawn, no ring. Forced colours\n * override a border colour, so only there does an outline come back.\n *\n * Every string is a STATIC literal. Tailwind scans source text, so a class\n * assembled at runtime is invisible to it and would never be emitted.\n */\nexport const toggleButtonVariants = cva(\n [\n \"relative inline-flex items-center justify-center gap-(--density-control-gap) shrink-0 whitespace-nowrap\",\n \"rounded-control font-sans font-medium text-foreground\",\n \"cursor-pointer select-none box-border\",\n \"border-(length:--border-width) border-border bg-surface shadow-surface\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n \"[-webkit-tap-highlight-color:transparent]\",\n \"motion-safe:transition-[border-color,background-color] motion-safe:duration-(--duration-quick) motion-safe:ease-settle\",\n\n // Hover darkens the edge a step, as on a text field.\n \"data-[hovered]:bg-muted data-[hovered]:border-border-strong\",\n\n // Selected: tint and edge. The check mark below is the cue that is not colour.\n \"data-[selected]:bg-primary-subtle data-[selected]:border-primary-strong\",\n \"data-[selected]:data-[hovered]:bg-primary-subtle\",\n\n // Focus is the control's own border going dark. Declared last and doubled\n // with the selected state so a pressed, focused toggle shows it too.\n // outline-none, then the outline only under forced colours, where a border\n // colour is overridden and would leave no indicator (same recipe as\n // fieldSurfaceVariants).\n \"outline-none\",\n \"data-[focus-visible]:border-foreground\",\n \"data-[focus-visible]:data-[selected]:border-foreground\",\n \"forced-colors:data-[focus-visible]:outline-solid\",\n \"forced-colors:data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"forced-colors:data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n\n // Disabled comes after, and doubled with the selected state for the same reason, so a\n // disabled, selected toggle drops the tint and the edge and keeps only the check mark.\n \"data-[disabled]:cursor-not-allowed data-[disabled]:bg-muted data-[disabled]:text-text-disabled data-[disabled]:border-border\",\n \"data-[disabled]:data-[selected]:bg-muted data-[disabled]:data-[selected]:border-border\",\n ],\n {\n variants: {\n size: {\n sm: \"h-(--density-control-h-sm) text-sm\",\n md: \"h-(--density-control-h-md) text-sm\",\n },\n /** A square button that holds only a glyph. It must carry an `aria-label`. */\n iconOnly: {\n true: \"px-0 text-base\",\n // 4px more at the end than the start: room for the selected mark, always, so nothing moves.\n false: \"ps-4 pe-5\",\n },\n },\n compoundVariants: [\n { size: \"sm\", iconOnly: true, class: \"w-(--density-control-h-sm)\" },\n { size: \"md\", iconOnly: true, class: \"w-(--density-control-h-md)\" },\n ],\n defaultVariants: { size: \"sm\", iconOnly: false },\n },\n);\n\n/** The group's row. Buttons wrap rather than overflow a narrow container. */\nexport const toggleButtonGroupVariants = cva([\n \"box-border inline-flex flex-wrap gap-2 font-sans\",\n \"data-[orientation=vertical]:flex-col\",\n]);\n\n// The key (as a string) of a single-choice group's one tab stop, or null when the group has\n// no opinion (outside a group, in multiple mode, nothing pressed, or nothing enabled).\nconst TabStopContext = createContext<string | null>(null);\n\nexport interface ToggleButtonProps\n extends\n Omit<AriaToggleButtonProps, \"className\" | \"style\" | \"children\">,\n Omit<VariantProps<typeof toggleButtonVariants>, \"iconOnly\"> {\n /** The label, or the glyph of an `iconOnly` toggle. */\n children?: ReactNode;\n /**\n * Only a glyph, in a square button. Pass the glyph as `children` and name the\n * button with `aria-label`; without one it has no accessible name.\n *\n * @default false\n */\n iconOnly?: boolean;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\n// Where the mark sits inside the box. A label toggle keeps 4px more padding at the end\n// (always, so nothing moves when the mark appears) and the mark is centred in it; an\n// icon-only toggle has no spare room, so the mark takes the top corner, drawn smaller.\nconst CHECK_PLACEMENT = {\n label: \"size-3 end-1.5 top-1/2 -translate-y-1/2\",\n icon: \"size-2.5 end-1.5 top-1.5\",\n} as const;\n\n/**\n * A button that is on or off. Controlled with `isSelected` and\n * `onChange(isSelected)`, or uncontrolled with `defaultSelected`.\n *\n * Give an icon-only toggle an `aria-label`, and wrap it in a `TooltipTrigger`\n * when the glyph alone is not enough. The label should name the control, not\n * its state: assistive technology says \"pressed\" itself.\n *\n * @example\n * <ToggleButton isSelected={unread} onChange={setUnread}>Has new reply</ToggleButton>\n * <ToggleButton iconOnly aria-label=\"Primary key\"><KeyIcon /></ToggleButton>\n */\nexport const ToggleButton = forwardRef<HTMLButtonElement, ToggleButtonProps>(function ToggleButton(\n { className, size, iconOnly = false, children, excludeFromTabOrder, ...props },\n ref,\n) {\n // Single choice: Tab enters the group on the chosen button, the rest are reached by arrows.\n const stop = useContext(TabStopContext);\n const skipTab = stop !== null && String(props.id) !== stop;\n return (\n <AriaToggleButton\n ref={ref}\n {...props}\n excludeFromTabOrder={excludeFromTabOrder ?? skipTab}\n data-slot=\"toggle-button\"\n data-key={props.id}\n className={cn(toggleButtonVariants({ size, iconOnly }), className)}\n >\n {({ isSelected }) => (\n <>\n {children}\n {isSelected ? (\n // Decorative: the button's own pressed state carries it for assistive technology.\n <svg\n aria-hidden=\"true\"\n data-slot=\"toggle-button-check\"\n viewBox=\"0 0 12 12\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={cn(\n \"pointer-events-none absolute\",\n iconOnly ? CHECK_PLACEMENT.icon : CHECK_PLACEMENT.label,\n )}\n >\n <path d=\"M2.5 6.25 5 8.75 9.5 3.5\" />\n </svg>\n ) : null}\n </>\n )}\n </AriaToggleButton>\n );\n});\n\nexport interface ToggleButtonGroupProps extends Omit<\n AriaToggleButtonGroupProps,\n \"className\" | \"style\"\n> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\n/**\n * A row of ToggleButtons that share a selection. Always a toolbar of\n * `aria-pressed` buttons: ONE tab stop, arrow keys move focus, Space or Enter\n * toggles the focused button (selection never follows focus).\n *\n * `selectionMode=\"single\"` (the default) lets one button be pressed at a time:\n * pressing another moves the selection to it, and pressing the pressed one\n * clears it unless `disallowEmptySelection`. Tab enters the group on the pressed\n * button (on the first when none is). `\"multiple\"` lets any number be pressed.\n * Each child needs an `id`, which is its key in `selectedKeys`.\n *\n * Why not `role=\"radiogroup\"`: React Aria's single mode is a radio group in\n * name only. Its arrows move focus and do not check, and every button is a tab\n * stop, which is not the APG radio pattern. A true radio group needs the\n * selection to follow focus, which a button that you press does not do. The\n * toolbar of pressed buttons is the APG pattern that matches how these behave.\n * If you need a radio group, use `RadioGroup` (`variant=\"card\"` for the framed look).\n *\n * @example\n * <ToggleButtonGroup aria-label=\"Range\" selectionMode=\"single\" disallowEmptySelection>\n * <ToggleButton id=\"7d\">7 days</ToggleButton>\n * <ToggleButton id=\"30d\">30 days</ToggleButton>\n * </ToggleButtonGroup>\n */\nexport const ToggleButtonGroup = forwardRef<HTMLDivElement, ToggleButtonGroupProps>(\n function ToggleButtonGroup(\n {\n className,\n selectionMode = \"single\",\n selectedKeys,\n defaultSelectedKeys,\n onSelectionChange,\n ...props\n },\n ref,\n ) {\n const single = selectionMode === \"single\";\n const [inner, setInner] = useState<Set<Key>>(() => new Set(defaultSelectedKeys ?? []));\n const chosen = selectedKeys === undefined ? inner : new Set(selectedKeys);\n\n // The one tab stop: the pressed button, or the first enabled one when the pressed button is\n // disabled. Read from the DOM after each render because disabled may come from any wrapper.\n const own = useRef<HTMLDivElement | null>(null);\n const [stop, setStop] = useState<string | null>(null);\n useIsomorphicLayoutEffect(() => {\n if (!single || chosen.size === 0) return setStop(null);\n const enabled = [\n ...(own.current?.querySelectorAll<HTMLElement>('[data-slot=\"toggle-button\"]') ?? []),\n ].filter((el) => !(el as HTMLButtonElement).disabled);\n const pressed = enabled.find((el) => el.getAttribute(\"aria-pressed\") === \"true\");\n setStop((pressed ?? enabled[0])?.getAttribute(\"data-key\") ?? null);\n });\n\n // Memoized on the caller's ref so the merged callback is stable across renders: a new function\n // each render would have React call the caller's callback ref with null and then the node again.\n const mergedRef = useMemo(() => mergeRefs(own, ref), [ref]);\n\n const shared = {\n ref: mergedRef,\n ...props,\n \"data-slot\": \"toggle-button-group\",\n className: cn(toggleButtonGroupVariants(), className),\n };\n\n if (!single) {\n return (\n <AriaToggleButtonGroup\n {...shared}\n selectionMode=\"multiple\"\n selectedKeys={selectedKeys}\n defaultSelectedKeys={defaultSelectedKeys}\n onSelectionChange={onSelectionChange}\n />\n );\n }\n\n // React Aria's own single mode is a radio group; we want the toolbar, so run it as\n // \"multiple\" and keep it to one key: the newly pressed key replaces the old one.\n const change = (next: Selection) => {\n if (next === \"all\") return;\n let keys: Set<Key> = next;\n if (next.size > 1) {\n const added = [...next].find((key) => !chosen.has(key));\n keys = new Set([added ?? [...next][0]!]);\n }\n if (selectedKeys === undefined) setInner(keys);\n onSelectionChange?.(keys);\n };\n\n return (\n <TabStopContext.Provider value={stop}>\n <AriaToggleButtonGroup\n {...shared}\n selectionMode=\"multiple\"\n selectedKeys={chosen}\n onSelectionChange={change}\n />\n </TabStopContext.Provider>\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,MAAa,uBAAuB,IAClC;CACE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CAGA;CACA;CAOA;CACA;CACA;CACA;CACA;CACA;CAIA;CACA;AACF,GACA;CACE,UAAU;EACR,MAAM;GACJ,IAAI;GACJ,IAAI;EACN;;EAEA,UAAU;GACR,MAAM;GAEN,OAAO;EACT;CACF;CACA,kBAAkB,CAChB;EAAE,MAAM;EAAM,UAAU;EAAM,OAAO;CAA6B,GAClE;EAAE,MAAM;EAAM,UAAU;EAAM,OAAO;CAA6B,CACpE;CACA,iBAAiB;EAAE,MAAM;EAAM,UAAU;CAAM;AACjD,CACF;;AAGA,MAAa,4BAA4B,IAAI,CAC3C,oDACA,sCACF,CAAC;AAID,MAAM,iBAAiB,cAA6B,IAAI;AAsBxD,MAAM,kBAAkB;CACtB,OAAO;CACP,MAAM;AACR;;;;;;;;;;;;;AAcA,MAAaA,iBAAe,WAAiD,SAASA,eACpF,EAAE,WAAW,MAAM,WAAW,OAAO,UAAU,qBAAqB,GAAG,SACvE,KACA;CAEA,MAAM,OAAO,WAAW,cAAc;CACtC,MAAM,UAAU,SAAS,QAAQ,OAAO,MAAM,EAAE,MAAM;CACtD,OACE,oBAACC,cAAD;EACO;EACL,GAAI;EACJ,qBAAqB,uBAAuB;EAC5C,aAAU;EACV,YAAU,MAAM;EAChB,WAAW,GAAG,qBAAqB;GAAE;GAAM;EAAS,CAAC,GAAG,SAAS;EAE/D,WAAA,EAAE,iBACF,qBAAA,YAAA,EAAA,UAAA,CACG,UACA,aAEC,oBAAC,OAAD;GACE,eAAY;GACZ,aAAU;GACV,SAAQ;GACR,MAAK;GACL,QAAO;GACP,aAAY;GACZ,eAAc;GACd,gBAAe;GACf,WAAW,GACT,gCACA,WAAW,gBAAgB,OAAO,gBAAgB,KACpD;GAEA,UAAA,oBAAC,QAAD,EAAM,GAAE,2BAA4B,CAAA;EACjC,CAAA,IACH,IACJ,EAAA,CAAA;CAEY,CAAA;AAEtB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;AAkCD,MAAaC,sBAAoB,WAC/B,SAASA,oBACP,EACE,WACA,gBAAgB,UAChB,cACA,qBACA,mBACA,GAAG,SAEL,KACA;CACA,MAAM,SAAS,kBAAkB;CACjC,MAAM,CAAC,OAAO,YAAY,eAAyB,IAAI,IAAI,uBAAuB,CAAC,CAAC,CAAC;CACrF,MAAM,SAAS,iBAAiB,KAAA,IAAY,QAAQ,IAAI,IAAI,YAAY;CAIxE,MAAM,MAAM,OAA8B,IAAI;CAC9C,MAAM,CAAC,MAAM,WAAW,SAAwB,IAAI;CACpD,gCAAgC;EAC9B,IAAI,CAAC,UAAU,OAAO,SAAS,GAAG,OAAO,QAAQ,IAAI;EACrD,MAAM,UAAU,CACd,GAAI,IAAI,SAAS,iBAA8B,+BAA6B,KAAK,CAAC,CACpF,CAAC,CAAC,QAAQ,OAAO,CAAE,GAAyB,QAAQ;EACpD,MAAM,UAAU,QAAQ,MAAM,OAAO,GAAG,aAAa,cAAc,MAAM,MAAM;EAC/E,SAAS,WAAW,QAAQ,GAAA,EAAK,aAAa,UAAU,KAAK,IAAI;CACnE,CAAC;CAMD,MAAM,SAAS;EACb,KAHgB,cAAc,UAAU,KAAK,GAAG,GAAG,CAAC,GAAG,CAG1C;EACb,GAAG;EACH,aAAa;EACb,WAAW,GAAG,0BAA0B,GAAG,SAAS;CACtD;CAEA,IAAI,CAAC,QACH,OACE,oBAACC,mBAAD;EACE,GAAI;EACJ,eAAc;EACA;EACO;EACF;CACpB,CAAA;CAML,MAAM,UAAU,SAAoB;EAClC,IAAI,SAAS,OAAO;EACpB,IAAI,OAAiB;EACrB,IAAI,KAAK,OAAO,GAAG;GACjB,MAAM,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,QAAQ,CAAC,OAAO,IAAI,GAAG,CAAC;GACtD,uBAAO,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC,EAAG,CAAC;EACzC;EACA,IAAI,iBAAiB,KAAA,GAAW,SAAS,IAAI;EAC7C,oBAAoB,IAAI;CAC1B;CAEA,OACE,oBAAC,eAAe,UAAhB;EAAyB,OAAO;EAC9B,UAAA,oBAACA,mBAAD;GACE,GAAI;GACJ,eAAc;GACd,cAAc;GACd,mBAAmB;EACpB,CAAA;CACsB,CAAA;AAE7B,CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seekalfred/primitives",
3
- "version": "0.7.3",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Accessible, styled primitives built on React Aria Components.",
@@ -104,6 +104,14 @@
104
104
  "types": "./dist/chip-group/index.d.ts",
105
105
  "default": "./dist/chip-group/index.js"
106
106
  },
107
+ "./code-block": {
108
+ "types": "./dist/code-block/index.d.ts",
109
+ "default": "./dist/code-block/index.js"
110
+ },
111
+ "./command-palette": {
112
+ "types": "./dist/command-palette/index.d.ts",
113
+ "default": "./dist/command-palette/index.js"
114
+ },
107
115
  "./confirm-morph": {
108
116
  "types": "./dist/confirm-morph/index.d.ts",
109
117
  "default": "./dist/confirm-morph/index.js"
@@ -160,6 +168,10 @@
160
168
  "types": "./dist/gauge/index.d.ts",
161
169
  "default": "./dist/gauge/index.js"
162
170
  },
171
+ "./info-tooltip": {
172
+ "types": "./dist/info-tooltip/index.d.ts",
173
+ "default": "./dist/info-tooltip/index.js"
174
+ },
163
175
  "./input": {
164
176
  "types": "./dist/input/index.d.ts",
165
177
  "default": "./dist/input/index.js"
@@ -360,6 +372,10 @@
360
372
  "types": "./dist/toast/index.d.ts",
361
373
  "default": "./dist/toast/index.js"
362
374
  },
375
+ "./toggle-button": {
376
+ "types": "./dist/toggle-button/index.d.ts",
377
+ "default": "./dist/toggle-button/index.js"
378
+ },
363
379
  "./tooltip": {
364
380
  "types": "./dist/tooltip/index.d.ts",
365
381
  "default": "./dist/tooltip/index.js"
@@ -395,7 +411,7 @@
395
411
  "@lexical/rich-text": "0.52.0",
396
412
  "@lexical/selection": "0.52.0",
397
413
  "@lexical/utils": "0.52.0",
398
- "@seekalfred/tokens": "^0.7.3",
414
+ "@seekalfred/tokens": "^0.8.0",
399
415
  "class-variance-authority": "0.7.1",
400
416
  "clsx": "2.1.1",
401
417
  "lexical": "0.52.0",
@@ -404,7 +420,7 @@
404
420
  "devDependencies": {
405
421
  "@arethetypeswrong/cli": "0.18.5",
406
422
  "@internationalized/date": "3.12.4",
407
- "@seekalfred/styles": "^0.7.3",
423
+ "@seekalfred/styles": "^0.8.0",
408
424
  "@seekalfred/tsconfig": "0.0.0",
409
425
  "@size-limit/preset-small-lib": "13.0.3",
410
426
  "@storybook/react-vite": "10.6.0",