@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
@@ -12,6 +12,11 @@ import { Key, SelectProps } from "react-aria-components";
12
12
  * in beside the chevron when anything is picked. Options are `SelectItem`s, whose
13
13
  * tick pops in as an option is picked; `MultiSelectGroup` gathers them under a
14
14
  * heading.
15
+ *
16
+ * Opt in to `searchable` for a search field at the top of the list that filters
17
+ * the options as you type (React Aria's Autocomplete, so the arrow keys and Enter
18
+ * work from the field). Pass `onCreate` to also offer a "Create “…”" row when the
19
+ * typed text matches no option.
15
20
  */
16
21
  export interface MultiSelectProps extends Omit<SelectProps<object, "multiple">, "className" | "style" | "children" | "items" | "selectionMode"> {
17
22
  /** `SelectItem` children. */
@@ -43,6 +48,32 @@ export interface MultiSelectProps extends Omit<SelectProps<object, "multiple">,
43
48
  size?: "sm" | "md" | "lg";
44
49
  /** Extra classes for the wrapper. Merged last. */
45
50
  className?: string;
51
+ /**
52
+ * Puts a search field at the top of the list. Typing filters the options (and
53
+ * groups) by their text, ignoring case and accents; the arrow keys and Enter
54
+ * work from the field. Cleared each time the list closes. Off by default.
55
+ * Turned on by `onCreate` too.
56
+ *
57
+ * @default false
58
+ */
59
+ searchable?: boolean;
60
+ /** Accessible name of the search field. @default "Search options" */
61
+ searchLabel?: string;
62
+ /** Placeholder of the search field. @default "Search" */
63
+ searchPlaceholder?: string;
64
+ /** Shown in the list while the search matches nothing. @default "No options found" */
65
+ emptyLabel?: string;
66
+ /**
67
+ * Makes the list creatable, and searchable. While the typed text (trimmed)
68
+ * matches no option's text or id nor a picked value, ignoring case, the list ends
69
+ * with a "Create “text”" row. Picking it calls `onCreate` with the text, adds
70
+ * the text itself as a picked key (through `onChange`, as any pick) and clears
71
+ * the search. Render a `SelectItem` whose `id` is that text so it lists like
72
+ * the others; until then its chip shows the text.
73
+ */
74
+ onCreate?: (value: string) => void;
75
+ /** The create row's text for the typed value. @default (value) => `Create “${value}”` */
76
+ createLabel?: (value: string) => string;
46
77
  }
47
78
  export declare const MultiSelect: import("react").ForwardRefExoticComponent<MultiSelectProps & import("react").RefAttributes<HTMLButtonElement>>;
48
79
  export declare const multiSelectGroupVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
@@ -2,14 +2,97 @@
2
2
  import { cn, cnAll } from "../lib/cn.js";
3
3
  import { SwapText } from "../lib/swap-text.js";
4
4
  import { useFlip } from "../lib/flip.js";
5
- import { Tooltip as Tooltip$1, TooltipTrigger as TooltipTrigger$1 } from "../tooltip/tooltip.js";
6
5
  import { labelVariants } from "../label/label.js";
7
- import { selectPopoverVariants, selectTriggerVariants, selectVariants } from "../select/select.js";
6
+ import { Tooltip as Tooltip$1, TooltipTrigger as TooltipTrigger$1 } from "../tooltip/tooltip.js";
7
+ import { selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants } from "../select/select.js";
8
8
  import { cva } from "class-variance-authority";
9
9
  import { forwardRef, useContext, useRef, useState } from "react";
10
- import { Button, ButtonContext, FieldError, Header, Label, ListBox, ListBoxSection, Popover, Select, SelectStateContext, SelectValue } from "react-aria-components";
10
+ import { Autocomplete, Button, ButtonContext, FieldError, Header, Input, Label, ListBox, ListBoxItem, ListBoxSection, Popover, SearchField, Select, SelectStateContext, SelectValue, useFilter } from "react-aria-components";
11
11
  import { jsx, jsxs } from "react/jsx-runtime";
12
12
  //#region src/multi-select/multi-select.tsx
13
+ /** The key of the create row. Never reaches `onChange`. */
14
+ const CREATE_KEY = "__multi-select-create__";
15
+ const defaultCreateLabel = (value) => `Create \u201c${value}\u201d`;
16
+ /** Whether `text` already names an option (by its text or its key) or a pick, ignoring case. */
17
+ function isTaken(state, text) {
18
+ const want = text.toLocaleLowerCase();
19
+ if ((state?.value ?? []).some((key) => String(key).toLocaleLowerCase() === want)) return true;
20
+ if (!state) return false;
21
+ for (const key of state.collection.getKeys()) {
22
+ const node = state.collection.getItem(key);
23
+ if (node?.type === "item" && node.key !== CREATE_KEY && (node.textValue.toLocaleLowerCase() === want || String(node.key).toLocaleLowerCase() === want)) return true;
24
+ }
25
+ return false;
26
+ }
27
+ /**
28
+ * The list with a search field over it (React Aria's Autocomplete): the field
29
+ * keeps focus and drives the options with the arrow keys, Enter picks.
30
+ */
31
+ function SearchList({ query, onQueryChange, creatable, searchLabel, searchPlaceholder, emptyLabel, children }) {
32
+ const state = useContext(SelectStateContext);
33
+ const { contains } = useFilter({ sensitivity: "base" });
34
+ const text = query.trim();
35
+ const offerCreate = creatable && text !== "" && !isTaken(state, text);
36
+ return /* @__PURE__ */ jsxs(Autocomplete, {
37
+ inputValue: query,
38
+ onInputChange: onQueryChange,
39
+ filter: (textValue, input, node) => node.key === CREATE_KEY ? offerCreate : contains(textValue, input),
40
+ children: [/* @__PURE__ */ jsxs(SearchField, {
41
+ "aria-label": searchLabel,
42
+ autoFocus: true,
43
+ "data-slot": "multi-select-search",
44
+ className: multiSelectSearchClass,
45
+ children: [/* @__PURE__ */ jsxs("svg", {
46
+ "aria-hidden": "true",
47
+ viewBox: "0 0 16 16",
48
+ fill: "none",
49
+ stroke: "currentColor",
50
+ strokeWidth: "1.75",
51
+ strokeLinecap: "round",
52
+ className: "size-4 flex-none text-muted-foreground",
53
+ children: [/* @__PURE__ */ jsx("circle", {
54
+ cx: "7",
55
+ cy: "7",
56
+ r: "4.5"
57
+ }), /* @__PURE__ */ jsx("path", { d: "m10.5 10.5 3 3" })]
58
+ }), /* @__PURE__ */ jsx(Input, {
59
+ placeholder: searchPlaceholder,
60
+ "data-slot": "multi-select-search-input",
61
+ className: "min-w-0 flex-1 appearance-none border-0 bg-transparent p-0 font-sans text-sm text-foreground outline-none placeholder:text-muted-foreground [&::-webkit-search-cancel-button]:hidden"
62
+ })]
63
+ }), /* @__PURE__ */ jsx(ListBox, {
64
+ "data-slot": "multi-select-list",
65
+ renderEmptyState: () => /* @__PURE__ */ jsx("p", {
66
+ "data-slot": "multi-select-empty",
67
+ className: "m-0 px-2 py-1.5 text-sm text-muted-foreground",
68
+ children: emptyLabel
69
+ }),
70
+ className: "min-h-0 flex-1 overflow-y-auto outline-none",
71
+ children
72
+ })]
73
+ });
74
+ }
75
+ /** The search row: a quiet field over the list, underlined while focused. */
76
+ const multiSelectSearchClass = "relative mb-1 flex h-(--density-control-h-sm) flex-none items-center gap-2 border-b-(length:--border-width) border-border px-2 after:pointer-events-none after:absolute after:inset-x-0 after:bottom-0 after:h-(--focus-ring-width) after:bg-transparent has-[input:focus]:after:bg-(--color-control-edge)";
77
+ /** The row that creates the typed value. Kept in the collection; the filter shows it. */
78
+ function CreateItem({ label }) {
79
+ return /* @__PURE__ */ jsxs(ListBoxItem, {
80
+ id: CREATE_KEY,
81
+ textValue: label,
82
+ "data-slot": "multi-select-create",
83
+ className: cnAll(selectItemVariants(), "text-text-secondary data-[focused]:text-foreground"),
84
+ children: [/* @__PURE__ */ jsx("svg", {
85
+ "aria-hidden": "true",
86
+ viewBox: "0 0 16 16",
87
+ fill: "none",
88
+ stroke: "currentColor",
89
+ strokeWidth: "1.75",
90
+ strokeLinecap: "round",
91
+ className: "size-3.5 flex-none",
92
+ children: /* @__PURE__ */ jsx("path", { d: "M8 3.5v9M3.5 8h9" })
93
+ }), label]
94
+ });
95
+ }
13
96
  /** The picked options as chips, then "+N", reading Select's own state. */
14
97
  function Chips({ placeholder, maxVisible, moreLabel, moreRef }) {
15
98
  const state = useContext(SelectStateContext);
@@ -117,8 +200,29 @@ function Clear({ label, disabled }) {
117
200
  })
118
201
  });
119
202
  }
120
- const MultiSelect = forwardRef(function MultiSelect({ children, label, labelHidden, placeholder = "Select options", errorMessage, maxVisible = 2, moreLabel, overflowTooltip = false, clearLabel = "Clear selections", size = "md", className, ...props }, ref) {
203
+ const MultiSelect = forwardRef(function MultiSelect({ children, label, labelHidden, placeholder = "Select options", errorMessage, maxVisible = 2, moreLabel, overflowTooltip = false, clearLabel = "Clear selections", size = "md", className, searchable = false, searchLabel = "Search options", searchPlaceholder = "Search", emptyLabel = "No options found", onCreate, createLabel = defaultCreateLabel, ...props }, ref) {
121
204
  const moreRef = useRef(null);
205
+ const creatable = onCreate != null;
206
+ const withSearch = searchable || creatable;
207
+ const [query, setQuery] = useState("");
208
+ const [ownValue, setOwnValue] = useState(() => props.defaultValue ?? []);
209
+ const value = props.value ?? ownValue;
210
+ const creatableProps = creatable ? {
211
+ value,
212
+ defaultValue: void 0,
213
+ onChange: (keys) => {
214
+ let next = keys;
215
+ if (keys.includes(CREATE_KEY)) {
216
+ const text = query.trim();
217
+ next = keys.filter((key) => key !== CREATE_KEY);
218
+ if (text !== "" && !next.includes(text)) next.push(text);
219
+ setQuery("");
220
+ onCreate(text);
221
+ }
222
+ setOwnValue(next);
223
+ props.onChange?.(next);
224
+ }
225
+ } : {};
122
226
  const trigger = /* @__PURE__ */ jsxs(Button, {
123
227
  ref,
124
228
  "data-slot": "multi-select-trigger",
@@ -150,6 +254,11 @@ const MultiSelect = forwardRef(function MultiSelect({ children, label, labelHidd
150
254
  });
151
255
  return /* @__PURE__ */ jsxs(Select, {
152
256
  ...props,
257
+ ...creatableProps,
258
+ onOpenChange: (isOpen) => {
259
+ if (!isOpen) setQuery("");
260
+ props.onOpenChange?.(isOpen);
261
+ },
153
262
  selectionMode: "multiple",
154
263
  "data-slot": "multi-select",
155
264
  className: cn(selectVariants(), className),
@@ -178,8 +287,16 @@ const MultiSelect = forwardRef(function MultiSelect({ children, label, labelHidd
178
287
  }),
179
288
  /* @__PURE__ */ jsx(Popover, {
180
289
  "data-slot": "multi-select-popover",
181
- className: selectPopoverVariants(),
182
- children: /* @__PURE__ */ jsx(ListBox, {
290
+ className: withSearch ? cnAll(selectPopoverVariants({ reveal: "fade" }), "flex flex-col overflow-y-hidden") : selectPopoverVariants(),
291
+ children: withSearch ? /* @__PURE__ */ jsxs(SearchList, {
292
+ query,
293
+ onQueryChange: setQuery,
294
+ creatable,
295
+ searchLabel,
296
+ searchPlaceholder,
297
+ emptyLabel,
298
+ children: [children, creatable ? /* @__PURE__ */ jsx(CreateItem, { label: createLabel(query.trim()) }) : null]
299
+ }) : /* @__PURE__ */ jsx(ListBox, {
183
300
  "data-slot": "multi-select-list",
184
301
  className: "outline-none",
185
302
  children
@@ -1 +1 @@
1
- {"version":3,"file":"multi-select.js","names":["TooltipTrigger","Tooltip","AriaButton","AriaSelectValue","AriaSelect","AriaLabel","AriaFieldError","AriaPopover","AriaListBox","AriaListBoxSection","AriaHeader"],"sources":["../../src/multi-select/multi-select.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, useContext, useRef, useState, type ReactNode, type RefObject } from \"react\";\nimport {\n Button as AriaButton,\n ButtonContext,\n FieldError as AriaFieldError,\n Header as AriaHeader,\n Label as AriaLabel,\n ListBox as AriaListBox,\n ListBoxSection as AriaListBoxSection,\n Popover as AriaPopover,\n Select as AriaSelect,\n SelectStateContext,\n SelectValue as AriaSelectValue,\n type Key,\n type SelectProps as AriaSelectProps,\n} from \"react-aria-components\";\n\nimport { labelVariants } from \"../label/label.js\";\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { useFlip } from \"../lib/flip.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { selectPopoverVariants, selectTriggerVariants, selectVariants } from \"../select/select.js\";\nimport { Tooltip, TooltipTrigger } from \"../tooltip/tooltip.js\";\n\n/**\n * MultiSelect: pick several options from a list. A Select in multiple mode\n * (React Aria), so the keyboard, typeahead, focus and screen-reader behaviour\n * are Select's own; the list stays open while picking.\n *\n * The trigger shows the first picks as chips and the rest as \"+N\". A new chip\n * fades up from a blur and grows in on the snappy spring while the chips beside\n * it glide over; the count rolls up or down as it changes. A clear button scales\n * in beside the chevron when anything is picked. Options are `SelectItem`s, whose\n * tick pops in as an option is picked; `MultiSelectGroup` gathers them under a\n * heading.\n */\nexport interface MultiSelectProps extends Omit<\n AriaSelectProps<object, \"multiple\">,\n \"className\" | \"style\" | \"children\" | \"items\" | \"selectionMode\"\n> {\n /** `SelectItem` children. */\n children?: ReactNode;\n /** What is being chosen, and the control's accessible name. */\n label: ReactNode;\n labelHidden?: boolean;\n /** Shown while nothing is picked. @default \"Select options\" */\n placeholder?: string;\n /** Why the selection is wrong. Rendered under the control. */\n errorMessage?: ReactNode;\n /** Chips shown in the trigger before the rest become \"+N\". @default 2 */\n maxVisible?: number;\n /**\n * Words after the overflow count, so `\"more\"` reads \"+2 more\". Off by default:\n * the count alone, \"+2\".\n */\n moreLabel?: string;\n /**\n * While some picks are folded into \"+N\", hovering or focusing the trigger shows\n * a tooltip listing them. Off by default.\n *\n * @default false\n */\n overflowTooltip?: boolean;\n /** Accessible name of the clear button. @default \"Clear selections\" */\n clearLabel?: string;\n /** @default \"md\" */\n size?: \"sm\" | \"md\" | \"lg\";\n /** Extra classes for the wrapper. Merged last. */\n className?: string;\n}\n\n/** The picked options as chips, then \"+N\", reading Select's own state. */\nfunction Chips({\n placeholder,\n maxVisible,\n moreLabel,\n moreRef,\n}: {\n placeholder: string;\n maxVisible: number;\n moreLabel: string | undefined;\n moreRef: RefObject<HTMLSpanElement | null>;\n}) {\n const state = useContext(SelectStateContext);\n const keys = (state?.value as Key[] | undefined) ?? [];\n const labelOf = (key: Key) => state?.collection.getItem(key)?.textValue ?? String(key);\n const shown = keys.slice(0, maxVisible);\n const rest = keys.length - shown.length;\n const row = useRef<HTMLSpanElement>(null);\n // The chips beside a new or removed one glide to their new place.\n useFlip(row, keys.join(\"|\"), \"var(--duration-smooth)\", \"var(--ease-smooth)\");\n // Chips there at first paint never animate; later ones grow in.\n const [initial] = useState(() => new Set(keys));\n const [count, setCount] = useState({ rest, dir: 1 });\n if (count.rest !== rest) setCount({ rest, dir: rest > count.rest ? 1 : -1 });\n return (\n <span\n ref={row}\n aria-hidden=\"true\"\n className=\"relative flex min-h-6.5 min-w-0 flex-1 items-center gap-1.5 overflow-hidden\"\n >\n {keys.length === 0 ? (\n <span\n data-slot=\"multi-select-placeholder\"\n className=\"truncate text-muted-foreground motion-safe:animate-fade-in\"\n >\n {placeholder}\n </span>\n ) : null}\n {shown.map((key) => (\n <span\n key={String(key)}\n data-flip=\"\"\n data-slot=\"multi-select-chip\"\n className={cnAll(\n \"multi-select-chip\",\n !initial.has(key) && \"motion-safe:animate-chip-reveal\",\n )}\n >\n {labelOf(key)}\n </span>\n ))}\n {rest > 0 ? (\n <span\n ref={moreRef}\n data-flip=\"\"\n data-slot=\"multi-select-more\"\n className=\"multi-select-chip text-text-secondary tabular-nums\"\n >\n +\n <SwapText swapKey={String(rest)} dir={count.dir} motion=\"rise\">\n {rest}\n </SwapText>\n {/* The chip is a flex box, which drops a leading space in a bare text\n node; whitespace-pre keeps \"+2 more\" from reading \"+2more\". */}\n {moreLabel ? (\n <span data-slot=\"multi-select-more-label\" className=\"whitespace-pre\">\n {` ${moreLabel}`}\n </span>\n ) : null}\n </span>\n ) : null}\n </span>\n );\n}\n\n/** The labels of the picks folded into \"+N\", in the order they were picked. */\nfunction useHiddenLabels(maxVisible: number) {\n const state = useContext(SelectStateContext);\n const keys = (state?.value as Key[] | undefined) ?? [];\n return keys\n .slice(maxVisible)\n .map((key) => state?.collection.getItem(key)?.textValue ?? String(key));\n}\n\n/**\n * Wraps the trigger in a tooltip naming the picks hidden behind \"+N\", placed\n * against the +N chip rather than the whole field. Always rendered, so turning\n * `overflowTooltip` on or off never swaps the trigger element (and drops its\n * focus); disabled while off or while nothing is hidden, so it never opens empty.\n */\nfunction OverflowTooltip({\n enabled,\n maxVisible,\n anchorRef,\n children,\n}: {\n enabled: boolean;\n maxVisible: number;\n anchorRef: RefObject<HTMLSpanElement | null>;\n children: ReactNode;\n}) {\n const hidden = useHiddenLabels(maxVisible);\n return (\n <TooltipTrigger isDisabled={!enabled || hidden.length === 0}>\n {children}\n <Tooltip triggerRef={anchorRef} data-slot=\"multi-select-overflow-tooltip\">\n {hidden.join(\", \")}\n </Tooltip>\n </TooltipTrigger>\n );\n}\n\n/** Clears every pick. It sits outside the trigger, so it is its own tab stop. */\nfunction Clear({ label, disabled }: { label: string; disabled: boolean }) {\n const state = useContext(SelectStateContext);\n const keys = (state?.value as Key[] | undefined) ?? [];\n if (!state || keys.length === 0 || disabled) return null;\n // Select hands its trigger props to every Button inside it; clear that, or this\n // button would become a second trigger named by the label.\n return (\n <ButtonContext.Provider value={null}>\n <AriaButton\n aria-label={label}\n data-slot=\"multi-select-clear\"\n onPress={() => {\n state.setValue([]);\n state.close();\n }}\n className=\"multi-select-clear motion-safe:animate-field-clear-in motion-reduce:animate-fade-in\"\n >\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n className=\"size-3.5\"\n >\n <path d=\"m4 4 8 8M12 4l-8 8\" />\n </svg>\n </AriaButton>\n </ButtonContext.Provider>\n );\n}\n\nexport const MultiSelect = forwardRef<HTMLButtonElement, MultiSelectProps>(function MultiSelect(\n {\n children,\n label,\n labelHidden,\n placeholder = \"Select options\",\n errorMessage,\n maxVisible = 2,\n moreLabel,\n overflowTooltip = false,\n clearLabel = \"Clear selections\",\n size = \"md\",\n className,\n ...props\n },\n ref,\n) {\n const moreRef = useRef<HTMLSpanElement>(null);\n const trigger = (\n <AriaButton\n ref={ref}\n data-slot=\"multi-select-trigger\"\n className={cnAll(selectTriggerVariants({ size }), \"pe-16 multi-select-trigger\")}\n >\n <AriaSelectValue data-slot=\"multi-select-value\" className=\"sr-only\">\n {({ isPlaceholder, selectedText }) => (isPlaceholder ? placeholder : selectedText)}\n </AriaSelectValue>\n <Chips\n placeholder={placeholder}\n maxVisible={maxVisible}\n moreLabel={moreLabel}\n moreRef={moreRef}\n />\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"absolute end-(--density-control-pad-md) text-muted-foreground motion-safe:transition-[rotate] motion-safe:duration-(--duration-gentle) motion-safe:ease-gentle group-aria-expanded/trigger:rotate-180\"\n >\n <path d=\"m4 6 4 4 4-4\" />\n </svg>\n </AriaButton>\n );\n return (\n <AriaSelect<object, \"multiple\">\n {...props}\n selectionMode=\"multiple\"\n data-slot=\"multi-select\"\n className={cn(selectVariants(), className)}\n >\n <AriaLabel\n data-slot=\"multi-select-label\"\n className={cnAll(labelVariants(), labelHidden && \"sr-only\")}\n >\n {label}\n </AriaLabel>\n <div className=\"relative\">\n <OverflowTooltip enabled={overflowTooltip} maxVisible={maxVisible} anchorRef={moreRef}>\n {trigger}\n </OverflowTooltip>\n <Clear label={clearLabel} disabled={props.isDisabled ?? false} />\n </div>\n {errorMessage == null ? null : (\n <AriaFieldError\n data-slot=\"multi-select-error\"\n className=\"text-sm leading-(--leading-normal) text-destructive-strong\"\n >\n {errorMessage}\n </AriaFieldError>\n )}\n <AriaPopover data-slot=\"multi-select-popover\" className={selectPopoverVariants()}>\n <AriaListBox data-slot=\"multi-select-list\" className=\"outline-none\">\n {children}\n </AriaListBox>\n </AriaPopover>\n </AriaSelect>\n );\n});\n\nexport const multiSelectGroupVariants = cva([\"flex flex-col not-first:mt-1\"]);\n\nexport const multiSelectGroupHeaderVariants = cva([\n \"px-2 pt-1.5 pb-1 text-xs leading-(--leading-normal) font-semibold text-muted-foreground\",\n]);\n\n/**\n * A run of options under a heading, such as \"Countries\" and \"Regions\". React\n * Aria's ListBoxSection, so the heading names the group for assistive tech and\n * the arrow keys move straight through from one group to the next.\n *\n * @example\n * <MultiSelect label=\"Markets\">\n * <MultiSelectGroup title=\"Countries\">\n * <SelectItem id=\"IN\">India</SelectItem>\n * </MultiSelectGroup>\n * <MultiSelectGroup title=\"Regions\">\n * <SelectItem id=\"europe\">Europe</SelectItem>\n * </MultiSelectGroup>\n * </MultiSelect>\n */\nexport interface MultiSelectGroupProps {\n /** The heading drawn above the group, and the group's accessible name. */\n title: ReactNode;\n /** `SelectItem` children. */\n children?: ReactNode;\n /** Identifies the group in the collection. Defaults to a generated id. */\n id?: Key;\n /** Extra classes for the group. Merged last. */\n className?: string;\n}\n\nexport const MultiSelectGroup = forwardRef<HTMLElement, MultiSelectGroupProps>(\n function MultiSelectGroup({ title, children, id, className }, ref) {\n return (\n <AriaListBoxSection\n ref={ref}\n id={id}\n data-slot=\"multi-select-group\"\n className={cn(multiSelectGroupVariants(), className)}\n >\n <AriaHeader\n data-slot=\"multi-select-group-header\"\n className={multiSelectGroupHeaderVariants()}\n >\n {title}\n </AriaHeader>\n {children}\n </AriaListBoxSection>\n );\n },\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"multi-select.js","names":["AriaAutocomplete","AriaSearchField","AriaInput","AriaListBox","AriaListBoxItem","TooltipTrigger","Tooltip","AriaButton","AriaSelectValue","AriaSelect","AriaLabel","AriaFieldError","AriaPopover","AriaListBoxSection","AriaHeader"],"sources":["../../src/multi-select/multi-select.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n forwardRef,\n useContext,\n useRef,\n useState,\n type ContextType,\n type ReactNode,\n type RefObject,\n} from \"react\";\nimport {\n Autocomplete as AriaAutocomplete,\n Button as AriaButton,\n ButtonContext,\n FieldError as AriaFieldError,\n Header as AriaHeader,\n Input as AriaInput,\n Label as AriaLabel,\n ListBox as AriaListBox,\n ListBoxItem as AriaListBoxItem,\n ListBoxSection as AriaListBoxSection,\n Popover as AriaPopover,\n SearchField as AriaSearchField,\n Select as AriaSelect,\n SelectStateContext,\n SelectValue as AriaSelectValue,\n type Key,\n type SelectProps as AriaSelectProps,\n useFilter,\n} from \"react-aria-components\";\n\nimport { labelVariants } from \"../label/label.js\";\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { useFlip } from \"../lib/flip.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport {\n selectItemVariants,\n selectPopoverVariants,\n selectTriggerVariants,\n selectVariants,\n} from \"../select/select.js\";\nimport { Tooltip, TooltipTrigger } from \"../tooltip/tooltip.js\";\n\n/**\n * MultiSelect: pick several options from a list. A Select in multiple mode\n * (React Aria), so the keyboard, typeahead, focus and screen-reader behaviour\n * are Select's own; the list stays open while picking.\n *\n * The trigger shows the first picks as chips and the rest as \"+N\". A new chip\n * fades up from a blur and grows in on the snappy spring while the chips beside\n * it glide over; the count rolls up or down as it changes. A clear button scales\n * in beside the chevron when anything is picked. Options are `SelectItem`s, whose\n * tick pops in as an option is picked; `MultiSelectGroup` gathers them under a\n * heading.\n *\n * Opt in to `searchable` for a search field at the top of the list that filters\n * the options as you type (React Aria's Autocomplete, so the arrow keys and Enter\n * work from the field). Pass `onCreate` to also offer a \"Create “…”\" row when the\n * typed text matches no option.\n */\nexport interface MultiSelectProps extends Omit<\n AriaSelectProps<object, \"multiple\">,\n \"className\" | \"style\" | \"children\" | \"items\" | \"selectionMode\"\n> {\n /** `SelectItem` children. */\n children?: ReactNode;\n /** What is being chosen, and the control's accessible name. */\n label: ReactNode;\n labelHidden?: boolean;\n /** Shown while nothing is picked. @default \"Select options\" */\n placeholder?: string;\n /** Why the selection is wrong. Rendered under the control. */\n errorMessage?: ReactNode;\n /** Chips shown in the trigger before the rest become \"+N\". @default 2 */\n maxVisible?: number;\n /**\n * Words after the overflow count, so `\"more\"` reads \"+2 more\". Off by default:\n * the count alone, \"+2\".\n */\n moreLabel?: string;\n /**\n * While some picks are folded into \"+N\", hovering or focusing the trigger shows\n * a tooltip listing them. Off by default.\n *\n * @default false\n */\n overflowTooltip?: boolean;\n /** Accessible name of the clear button. @default \"Clear selections\" */\n clearLabel?: string;\n /** @default \"md\" */\n size?: \"sm\" | \"md\" | \"lg\";\n /** Extra classes for the wrapper. Merged last. */\n className?: string;\n /**\n * Puts a search field at the top of the list. Typing filters the options (and\n * groups) by their text, ignoring case and accents; the arrow keys and Enter\n * work from the field. Cleared each time the list closes. Off by default.\n * Turned on by `onCreate` too.\n *\n * @default false\n */\n searchable?: boolean;\n /** Accessible name of the search field. @default \"Search options\" */\n searchLabel?: string;\n /** Placeholder of the search field. @default \"Search\" */\n searchPlaceholder?: string;\n /** Shown in the list while the search matches nothing. @default \"No options found\" */\n emptyLabel?: string;\n /**\n * Makes the list creatable, and searchable. While the typed text (trimmed)\n * matches no option's text or id nor a picked value, ignoring case, the list ends\n * with a \"Create “text”\" row. Picking it calls `onCreate` with the text, adds\n * the text itself as a picked key (through `onChange`, as any pick) and clears\n * the search. Render a `SelectItem` whose `id` is that text so it lists like\n * the others; until then its chip shows the text.\n */\n onCreate?: (value: string) => void;\n /** The create row's text for the typed value. @default (value) => `Create “${value}”` */\n createLabel?: (value: string) => string;\n}\n\n/** The key of the create row. Never reaches `onChange`. */\nconst CREATE_KEY = \"__multi-select-create__\";\n\nconst defaultCreateLabel = (value: string) => `Create \\u201c${value}\\u201d`;\n\ntype SelectState = NonNullable<ContextType<typeof SelectStateContext>>;\n\n/** Whether `text` already names an option (by its text or its key) or a pick, ignoring case. */\nfunction isTaken(state: SelectState | null, text: string) {\n const want = text.toLocaleLowerCase();\n const keys = (state?.value as Key[] | undefined) ?? [];\n if (keys.some((key) => String(key).toLocaleLowerCase() === want)) return true;\n if (!state) return false;\n for (const key of state.collection.getKeys()) {\n const node = state.collection.getItem(key);\n if (\n node?.type === \"item\" &&\n node.key !== CREATE_KEY &&\n // The key too: creating text equal to an unpicked option's id would pick\n // that option AND tell the owner to create a duplicate.\n (node.textValue.toLocaleLowerCase() === want || String(node.key).toLocaleLowerCase() === want)\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * The list with a search field over it (React Aria's Autocomplete): the field\n * keeps focus and drives the options with the arrow keys, Enter picks.\n */\nfunction SearchList({\n query,\n onQueryChange,\n creatable,\n searchLabel,\n searchPlaceholder,\n emptyLabel,\n children,\n}: {\n query: string;\n onQueryChange: (query: string) => void;\n creatable: boolean;\n searchLabel: string;\n searchPlaceholder: string;\n emptyLabel: string;\n children: ReactNode;\n}) {\n const state = useContext(SelectStateContext);\n const { contains } = useFilter({ sensitivity: \"base\" });\n const text = query.trim();\n const offerCreate = creatable && text !== \"\" && !isTaken(state, text);\n return (\n <AriaAutocomplete\n inputValue={query}\n onInputChange={onQueryChange}\n filter={(textValue, input, node) =>\n node.key === CREATE_KEY ? offerCreate : contains(textValue, input)\n }\n >\n <AriaSearchField\n aria-label={searchLabel}\n autoFocus\n data-slot=\"multi-select-search\"\n className={multiSelectSearchClass}\n >\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n className=\"size-4 flex-none text-muted-foreground\"\n >\n <circle cx=\"7\" cy=\"7\" r=\"4.5\" />\n <path d=\"m10.5 10.5 3 3\" />\n </svg>\n <AriaInput\n placeholder={searchPlaceholder}\n data-slot=\"multi-select-search-input\"\n className=\"min-w-0 flex-1 appearance-none border-0 bg-transparent p-0 font-sans text-sm text-foreground outline-none placeholder:text-muted-foreground [&::-webkit-search-cancel-button]:hidden\"\n />\n </AriaSearchField>\n <AriaListBox\n data-slot=\"multi-select-list\"\n renderEmptyState={() => (\n <p\n data-slot=\"multi-select-empty\"\n className=\"m-0 px-2 py-1.5 text-sm text-muted-foreground\"\n >\n {emptyLabel}\n </p>\n )}\n className=\"min-h-0 flex-1 overflow-y-auto outline-none\"\n >\n {children}\n </AriaListBox>\n </AriaAutocomplete>\n );\n}\n\n/** The search row: a quiet field over the list, underlined while focused. */\nconst multiSelectSearchClass =\n \"relative mb-1 flex h-(--density-control-h-sm) flex-none items-center gap-2 border-b-(length:--border-width) border-border px-2 after:pointer-events-none after:absolute after:inset-x-0 after:bottom-0 after:h-(--focus-ring-width) after:bg-transparent has-[input:focus]:after:bg-(--color-control-edge)\";\n\n/** The row that creates the typed value. Kept in the collection; the filter shows it. */\nfunction CreateItem({ label }: { label: string }) {\n return (\n <AriaListBoxItem\n id={CREATE_KEY}\n textValue={label}\n data-slot=\"multi-select-create\"\n className={cnAll(selectItemVariants(), \"text-text-secondary data-[focused]:text-foreground\")}\n >\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n className=\"size-3.5 flex-none\"\n >\n <path d=\"M8 3.5v9M3.5 8h9\" />\n </svg>\n {label}\n </AriaListBoxItem>\n );\n}\n\n/** The picked options as chips, then \"+N\", reading Select's own state. */\nfunction Chips({\n placeholder,\n maxVisible,\n moreLabel,\n moreRef,\n}: {\n placeholder: string;\n maxVisible: number;\n moreLabel: string | undefined;\n moreRef: RefObject<HTMLSpanElement | null>;\n}) {\n const state = useContext(SelectStateContext);\n const keys = (state?.value as Key[] | undefined) ?? [];\n const labelOf = (key: Key) => state?.collection.getItem(key)?.textValue ?? String(key);\n const shown = keys.slice(0, maxVisible);\n const rest = keys.length - shown.length;\n const row = useRef<HTMLSpanElement>(null);\n // The chips beside a new or removed one glide to their new place.\n useFlip(row, keys.join(\"|\"), \"var(--duration-smooth)\", \"var(--ease-smooth)\");\n // Chips there at first paint never animate; later ones grow in.\n const [initial] = useState(() => new Set(keys));\n const [count, setCount] = useState({ rest, dir: 1 });\n if (count.rest !== rest) setCount({ rest, dir: rest > count.rest ? 1 : -1 });\n return (\n <span\n ref={row}\n aria-hidden=\"true\"\n className=\"relative flex min-h-6.5 min-w-0 flex-1 items-center gap-1.5 overflow-hidden\"\n >\n {keys.length === 0 ? (\n <span\n data-slot=\"multi-select-placeholder\"\n className=\"truncate text-muted-foreground motion-safe:animate-fade-in\"\n >\n {placeholder}\n </span>\n ) : null}\n {shown.map((key) => (\n <span\n key={String(key)}\n data-flip=\"\"\n data-slot=\"multi-select-chip\"\n className={cnAll(\n \"multi-select-chip\",\n !initial.has(key) && \"motion-safe:animate-chip-reveal\",\n )}\n >\n {labelOf(key)}\n </span>\n ))}\n {rest > 0 ? (\n <span\n ref={moreRef}\n data-flip=\"\"\n data-slot=\"multi-select-more\"\n className=\"multi-select-chip text-text-secondary tabular-nums\"\n >\n +\n <SwapText swapKey={String(rest)} dir={count.dir} motion=\"rise\">\n {rest}\n </SwapText>\n {/* The chip is a flex box, which drops a leading space in a bare text\n node; whitespace-pre keeps \"+2 more\" from reading \"+2more\". */}\n {moreLabel ? (\n <span data-slot=\"multi-select-more-label\" className=\"whitespace-pre\">\n {` ${moreLabel}`}\n </span>\n ) : null}\n </span>\n ) : null}\n </span>\n );\n}\n\n/** The labels of the picks folded into \"+N\", in the order they were picked. */\nfunction useHiddenLabels(maxVisible: number) {\n const state = useContext(SelectStateContext);\n const keys = (state?.value as Key[] | undefined) ?? [];\n return keys\n .slice(maxVisible)\n .map((key) => state?.collection.getItem(key)?.textValue ?? String(key));\n}\n\n/**\n * Wraps the trigger in a tooltip naming the picks hidden behind \"+N\", placed\n * against the +N chip rather than the whole field. Always rendered, so turning\n * `overflowTooltip` on or off never swaps the trigger element (and drops its\n * focus); disabled while off or while nothing is hidden, so it never opens empty.\n */\nfunction OverflowTooltip({\n enabled,\n maxVisible,\n anchorRef,\n children,\n}: {\n enabled: boolean;\n maxVisible: number;\n anchorRef: RefObject<HTMLSpanElement | null>;\n children: ReactNode;\n}) {\n const hidden = useHiddenLabels(maxVisible);\n return (\n <TooltipTrigger isDisabled={!enabled || hidden.length === 0}>\n {children}\n <Tooltip triggerRef={anchorRef} data-slot=\"multi-select-overflow-tooltip\">\n {hidden.join(\", \")}\n </Tooltip>\n </TooltipTrigger>\n );\n}\n\n/** Clears every pick. It sits outside the trigger, so it is its own tab stop. */\nfunction Clear({ label, disabled }: { label: string; disabled: boolean }) {\n const state = useContext(SelectStateContext);\n const keys = (state?.value as Key[] | undefined) ?? [];\n if (!state || keys.length === 0 || disabled) return null;\n // Select hands its trigger props to every Button inside it; clear that, or this\n // button would become a second trigger named by the label.\n return (\n <ButtonContext.Provider value={null}>\n <AriaButton\n aria-label={label}\n data-slot=\"multi-select-clear\"\n onPress={() => {\n state.setValue([]);\n state.close();\n }}\n className=\"multi-select-clear motion-safe:animate-field-clear-in motion-reduce:animate-fade-in\"\n >\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n className=\"size-3.5\"\n >\n <path d=\"m4 4 8 8M12 4l-8 8\" />\n </svg>\n </AriaButton>\n </ButtonContext.Provider>\n );\n}\n\nexport const MultiSelect = forwardRef<HTMLButtonElement, MultiSelectProps>(function MultiSelect(\n {\n children,\n label,\n labelHidden,\n placeholder = \"Select options\",\n errorMessage,\n maxVisible = 2,\n moreLabel,\n overflowTooltip = false,\n clearLabel = \"Clear selections\",\n size = \"md\",\n className,\n searchable = false,\n searchLabel = \"Search options\",\n searchPlaceholder = \"Search\",\n emptyLabel = \"No options found\",\n onCreate,\n createLabel = defaultCreateLabel,\n ...props\n },\n ref,\n) {\n const moreRef = useRef<HTMLSpanElement>(null);\n const creatable = onCreate != null;\n const withSearch = searchable || creatable;\n const [query, setQuery] = useState(\"\");\n // Creating adds a key no option has yet, so the value is held here: the create\n // row's own key is swapped for the typed text before anyone sees it.\n const [ownValue, setOwnValue] = useState<readonly Key[]>(() => props.defaultValue ?? []);\n const value = props.value ?? ownValue;\n const creatableProps: Partial<AriaSelectProps<object, \"multiple\">> = creatable\n ? {\n value,\n defaultValue: undefined,\n onChange: (keys) => {\n let next = keys;\n if (keys.includes(CREATE_KEY)) {\n const text = query.trim();\n next = keys.filter((key) => key !== CREATE_KEY);\n if (text !== \"\" && !next.includes(text)) next.push(text);\n setQuery(\"\");\n onCreate(text);\n }\n setOwnValue(next);\n props.onChange?.(next);\n },\n }\n : {};\n const trigger = (\n <AriaButton\n ref={ref}\n data-slot=\"multi-select-trigger\"\n className={cnAll(selectTriggerVariants({ size }), \"pe-16 multi-select-trigger\")}\n >\n <AriaSelectValue data-slot=\"multi-select-value\" className=\"sr-only\">\n {({ isPlaceholder, selectedText }) => (isPlaceholder ? placeholder : selectedText)}\n </AriaSelectValue>\n <Chips\n placeholder={placeholder}\n maxVisible={maxVisible}\n moreLabel={moreLabel}\n moreRef={moreRef}\n />\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"absolute end-(--density-control-pad-md) text-muted-foreground motion-safe:transition-[rotate] motion-safe:duration-(--duration-gentle) motion-safe:ease-gentle group-aria-expanded/trigger:rotate-180\"\n >\n <path d=\"m4 6 4 4 4-4\" />\n </svg>\n </AriaButton>\n );\n return (\n <AriaSelect<object, \"multiple\">\n {...props}\n {...creatableProps}\n onOpenChange={(isOpen) => {\n // Each opening starts from the whole list.\n if (!isOpen) setQuery(\"\");\n props.onOpenChange?.(isOpen);\n }}\n selectionMode=\"multiple\"\n data-slot=\"multi-select\"\n className={cn(selectVariants(), className)}\n >\n <AriaLabel\n data-slot=\"multi-select-label\"\n className={cnAll(labelVariants(), labelHidden && \"sr-only\")}\n >\n {label}\n </AriaLabel>\n <div className=\"relative\">\n <OverflowTooltip enabled={overflowTooltip} maxVisible={maxVisible} anchorRef={moreRef}>\n {trigger}\n </OverflowTooltip>\n <Clear label={clearLabel} disabled={props.isDisabled ?? false} />\n </div>\n {errorMessage == null ? null : (\n <AriaFieldError\n data-slot=\"multi-select-error\"\n className=\"text-sm leading-(--leading-normal) text-destructive-strong\"\n >\n {errorMessage}\n </AriaFieldError>\n )}\n <AriaPopover\n data-slot=\"multi-select-popover\"\n className={\n withSearch\n ? cnAll(selectPopoverVariants({ reveal: \"fade\" }), \"flex flex-col overflow-y-hidden\")\n : selectPopoverVariants()\n }\n >\n {withSearch ? (\n <SearchList\n query={query}\n onQueryChange={setQuery}\n creatable={creatable}\n searchLabel={searchLabel}\n searchPlaceholder={searchPlaceholder}\n emptyLabel={emptyLabel}\n >\n {children}\n {creatable ? <CreateItem label={createLabel(query.trim())} /> : null}\n </SearchList>\n ) : (\n <AriaListBox data-slot=\"multi-select-list\" className=\"outline-none\">\n {children}\n </AriaListBox>\n )}\n </AriaPopover>\n </AriaSelect>\n );\n});\n\nexport const multiSelectGroupVariants = cva([\"flex flex-col not-first:mt-1\"]);\n\nexport const multiSelectGroupHeaderVariants = cva([\n \"px-2 pt-1.5 pb-1 text-xs leading-(--leading-normal) font-semibold text-muted-foreground\",\n]);\n\n/**\n * A run of options under a heading, such as \"Countries\" and \"Regions\". React\n * Aria's ListBoxSection, so the heading names the group for assistive tech and\n * the arrow keys move straight through from one group to the next.\n *\n * @example\n * <MultiSelect label=\"Markets\">\n * <MultiSelectGroup title=\"Countries\">\n * <SelectItem id=\"IN\">India</SelectItem>\n * </MultiSelectGroup>\n * <MultiSelectGroup title=\"Regions\">\n * <SelectItem id=\"europe\">Europe</SelectItem>\n * </MultiSelectGroup>\n * </MultiSelect>\n */\nexport interface MultiSelectGroupProps {\n /** The heading drawn above the group, and the group's accessible name. */\n title: ReactNode;\n /** `SelectItem` children. */\n children?: ReactNode;\n /** Identifies the group in the collection. Defaults to a generated id. */\n id?: Key;\n /** Extra classes for the group. Merged last. */\n className?: string;\n}\n\nexport const MultiSelectGroup = forwardRef<HTMLElement, MultiSelectGroupProps>(\n function MultiSelectGroup({ title, children, id, className }, ref) {\n return (\n <AriaListBoxSection\n ref={ref}\n id={id}\n data-slot=\"multi-select-group\"\n className={cn(multiSelectGroupVariants(), className)}\n >\n <AriaHeader\n data-slot=\"multi-select-group-header\"\n className={multiSelectGroupHeaderVariants()}\n >\n {title}\n </AriaHeader>\n {children}\n </AriaListBoxSection>\n );\n },\n);\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { cn, cnAll } from "../lib/cn.js";
3
- import { fieldSurfaceVariants } from "../lib/field-surface.js";
4
3
  import { Label as Label$1 } from "../label/label.js";
5
4
  import { FieldMessage, RollingNumber } from "../lib/field-message.js";
5
+ import { fieldSurfaceVariants } from "../lib/field-surface.js";
6
6
  import { cva } from "class-variance-authority";
7
7
  import { forwardRef, useContext, useId, useRef, useState } from "react";
8
8
  import { Button, Group, Input, NumberField, NumberFieldStateContext } from "react-aria-components";
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
- import { fieldSurfaceVariants } from "../lib/field-surface.js";
4
3
  import { FieldMessage } from "../lib/field-message.js";
5
4
  import { FieldShell, useFieldWiring } from "../lib/field-shell.js";
5
+ import { fieldSurfaceVariants } from "../lib/field-surface.js";
6
6
  import { EyeMorph } from "../lib/eye-morph.js";
7
7
  import { forwardRef, useId, useState } from "react";
8
8
  import { Button, Group, Input, InputContext, useSlottedContext } from "react-aria-components";
@@ -2,10 +2,10 @@
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect, useLeavingItems } from "../lib/motion.js";
4
4
  import { SwapText } from "../lib/swap-text.js";
5
- import { fieldSurfaceVariants } from "../lib/field-surface.js";
6
5
  import { Label as Label$1 } from "../label/label.js";
7
6
  import { FieldMessage } from "../lib/field-message.js";
8
7
  import { useFieldWiring } from "../lib/field-shell.js";
8
+ import { fieldSurfaceVariants } from "../lib/field-surface.js";
9
9
  import { EyeMorph } from "../lib/eye-morph.js";
10
10
  import { forwardRef, useCallback, useEffect, useRef, useState } from "react";
11
11
  import { Button, Group, Input } from "react-aria-components";
@@ -2,8 +2,8 @@
2
2
  import { cn, cnAll } from "../lib/cn.js";
3
3
  import { useIsomorphicLayoutEffect } from "../lib/motion.js";
4
4
  import { SwapText } from "../lib/swap-text.js";
5
- import { fieldSurfaceVariants } from "../lib/field-surface.js";
6
5
  import { FieldShell, useFieldWiring } from "../lib/field-shell.js";
6
+ import { fieldSurfaceVariants } from "../lib/field-surface.js";
7
7
  import { SelectItem, selectPopoverVariants } from "../select/select.js";
8
8
  import { useFlagEmoji } from "../lib/flag-emoji.js";
9
9
  import { ScrollArea } from "../scroll-area/scroll-area.js";
@@ -44,7 +44,7 @@ const Popover$1 = forwardRef(function Popover$2({ children, className, placement
44
44
  children: [
45
45
  title == null ? null : /* @__PURE__ */ jsx("h3", {
46
46
  "data-slot": "popover-title",
47
- className: "m-0 text-sm leading-(--leading-tight) font-semibold text-foreground",
47
+ className: "m-0 font-display text-sm leading-(--leading-tight) font-semibold text-foreground",
48
48
  children: title
49
49
  }),
50
50
  description == null ? null : /* @__PURE__ */ jsx("p", {
@@ -1 +1 @@
1
- {"version":3,"file":"popover.js","names":["Popover","AriaPopover","Button"],"sources":["../../src/popover/popover.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, useContext, type JSX, type ReactNode } from \"react\";\nimport {\n OverlayTriggerStateContext,\n Popover as AriaPopover,\n PreviewTrigger,\n type PopoverProps as AriaPopoverProps,\n type PreviewTriggerProps,\n} from \"react-aria-components\";\n\nimport { Button } from \"../button/button.js\";\nimport { cnAll } from \"../lib/cn.js\";\n\n/**\n * Popover, ported from the legacy shadcn/Radix component.\n *\n * Census: 2 files, 2 uses. `asChild` on the trigger twice, `className`\n * twice, `align=\"start\"` once. `PopoverAnchor` is exported by the legacy\n * and imported by nobody, so it is not ported; neither are `side`,\n * `sideOffset`, `alignOffset`, `open` / `onOpenChange`, `modal`,\n * `collisionPadding` or `avoidCollisions` -- zero call sites each. Both\n * existing uses are uncontrolled.\n *\n * IT GAINS AN ACCESSIBLE NAME. The legacy renders `role=\"dialog\"` with no\n * `aria-label` and no `aria-labelledby`, so both call sites announce an\n * unnamed dialog. React Aria labels the popover from its trigger\n * automatically once the two are paired, which is the only way to build\n * one here.\n *\n * The surface is `card`: in the legacy theme `--popover` is byte-identical\n * to `--card` in both themes, so the role the migration plan proposed\n * already exists under another name.\n */\nexport const popoverVariants = cva([\n // w-72 (288px) is the legacy default width, as a spacing-scale step.\n \"w-72 rounded-panel border-(length:--border-width) border-border p-4\",\n \"bg-card text-card-foreground shadow-floating\",\n // The reset in @seekalfred/styles is OPT-IN, so a consumer may load\n // none: an unstyled document renders text in a serif, and content-box\n // would add the 16px padding on top of the width.\n \"box-border font-sans text-sm leading-(--leading-normal)\",\n // React Aria gives the popover tabindex=-1 and focuses it on open, so it\n // can take a focus ring it does not want.\n \"outline-none\",\n]);\n\n/**\n * Content anchored to a trigger.\n *\n * @example\n * <PopoverTrigger>\n * <Button>Pick a date</Button>\n * <Popover placement=\"bottom start\">...</Popover>\n * </PopoverTrigger>\n */\nexport interface PopoverProps\n extends Omit<AriaPopoverProps, \"className\" | \"style\" | \"children\"> {\n children?: ReactNode;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * Where it opens, as React Aria's single `placement` string. The legacy\n * split this across `side` and `align`; its one non-default call site is\n * `align=\"start\"`, which is `\"bottom start\"` here.\n *\n * @default \"bottom\"\n */\n placement?: AriaPopoverProps[\"placement\"];\n /**\n * How it comes in: `rise` grows out of the trigger (offset toward it, a little\n * small, then settles on a spring) and leaves quicker; `none` leaves the entrance\n * to the caller, as a picker that animates its own popover does.\n *\n * @default \"rise\"\n */\n entrance?: \"rise\" | \"none\";\n /**\n * A heading at the top of the panel. Together with `description` and\n * `actionLabel` it makes the message popover: a dark title, grey body text\n * and a full-width button, with `children` rendered below them.\n */\n title?: ReactNode;\n /** Grey body text under the title. */\n description?: ReactNode;\n /** The label of the full-width button at the bottom. Pressing it runs `onAction` and closes the popover. */\n actionLabel?: ReactNode;\n /** Runs when the action button is pressed, before the popover closes. */\n onAction?: () => void;\n}\n\nexport const Popover = forwardRef<HTMLElement, PopoverProps>(function Popover(\n {\n children,\n className,\n placement = \"bottom\",\n offset = 4,\n entrance = \"rise\",\n title,\n description,\n actionLabel,\n onAction,\n ...props\n },\n ref,\n) {\n // The open state the enclosing PopoverTrigger owns, so the action button can close it.\n const state = useContext(OverlayTriggerStateContext);\n return (\n <AriaPopover\n ref={ref}\n {...props}\n placement={placement}\n offset={offset}\n data-slot=\"popover\"\n className={cnAll(popoverVariants(), entrance === \"rise\" && \"popover-motion\", className)}\n >\n {/*\n No inner Dialog. Wrapping the content in one is the common React\n Aria idiom and it was in the first draft here -- but deleting it\n broke no assertion, so it was doing nothing: a Popover under a\n DialogTrigger already gets role=\"dialog\", a focus scope, and\n aria-labelledby pointing at the trigger. The wrapper was one more\n element and one more className to explain.\n */}\n {title == null ? null : (\n <h3\n data-slot=\"popover-title\"\n className=\"m-0 text-sm leading-(--leading-tight) font-semibold text-foreground\"\n >\n {title}\n </h3>\n )}\n {description == null ? null : (\n <p\n data-slot=\"popover-description\"\n className=\"m-0 mt-1 text-sm leading-(--leading-normal) text-text-secondary\"\n >\n {description}\n </p>\n )}\n {actionLabel == null ? null : (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n className=\"mt-4 w-full\"\n onPress={() => {\n onAction?.();\n state?.close();\n }}\n >\n {actionLabel}\n </Button>\n )}\n {children}\n </AriaPopover>\n );\n});\n\n/**\n * Pairs a trigger with a popover and owns the open state.\n *\n * React Aria's `DialogTrigger`, renamed for the migration: it takes the\n * trigger element directly as its first child, which is what `asChild` was\n * doing on both legacy call sites.\n */\nexport { DialogTrigger as PopoverTrigger } from \"react-aria-components\";\n\nexport interface PopoverHoverTriggerProps extends Omit<PreviewTriggerProps, \"delay\" | \"closeDelay\"> {\n /** The trigger, then the `Popover`. The trigger must be focusable (a `Button` or `Link`). */\n children: ReactNode;\n /**\n * Milliseconds the pointer rests on the trigger, or keyboard focus sits on\n * it, before the card opens.\n * @default 600\n */\n delay?: number;\n /**\n * Milliseconds after the pointer leaves the trigger and the card, or focus\n * leaves both, before it closes. Moving onto the card keeps it open.\n * @default 200\n */\n closeDelay?: number;\n}\n\n/**\n * Opens a popover on hover instead of press: a hover card.\n *\n * React Aria's `PreviewTrigger`. The card opens after `delay` when the pointer\n * rests on the trigger or keyboard focus reaches it, and on a long press on\n * touch. It stays open while the pointer travels onto it (a safe area covers\n * the gap) and closes `closeDelay` after the pointer or focus leaves both.\n * Escape closes it; Tab from the trigger moves into it. It is non-modal: no\n * focus trap and no underlay, and the page behind stays usable.\n *\n * The trigger keeps its own press action, so a Link still navigates and a\n * Button still runs `onPress`. Content anyone needs must not live only in the\n * card: touch without a long press never sees it.\n *\n * @example\n * <PopoverHoverTrigger delay={300}>\n * <Button variant=\"ghost\" aria-label=\"3 more sources\">+3</Button>\n * <Popover placement=\"bottom start\">...</Popover>\n * </PopoverHoverTrigger>\n */\nexport function PopoverHoverTrigger({\n delay = 600,\n closeDelay = 200,\n ...props\n}: PopoverHoverTriggerProps): JSX.Element {\n return <PreviewTrigger {...props} delay={delay} closeDelay={closeDelay} />;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,MAAa,kBAAkB,IAAI;CAEjC;CACA;CAIA;CAGA;AACF,CAAC;AA8CD,MAAaA,YAAU,WAAsC,SAASA,UACpE,EACE,UACA,WACA,YAAY,UACZ,SAAS,GACT,WAAW,QACX,OACA,aACA,aACA,UACA,GAAG,SAEL,KACA;CAEA,MAAM,QAAQ,WAAW,0BAA0B;CACnD,OACE,qBAACC,SAAD;EACO;EACL,GAAI;EACO;EACH;EACR,aAAU;EACV,WAAW,MAAM,gBAAgB,GAAG,aAAa,UAAU,kBAAkB,SAAS;EANxF,UAAA;GAgBG,SAAS,OAAO,OACf,oBAAC,MAAD;IACE,aAAU;IACV,WAAU;IAET,UAAA;GACC,CAAA;GAEL,eAAe,OAAO,OACrB,oBAAC,KAAD;IACE,aAAU;IACV,WAAU;IAET,UAAA;GACA,CAAA;GAEJ,eAAe,OAAO,OACrB,oBAACC,UAAD;IACE,SAAQ;IACR,MAAK;IACL,WAAU;IACV,eAAe;KACb,WAAW;KACX,OAAO,MAAM;IACf;IAEC,UAAA;GACK,CAAA;GAET;EACU;;AAEjB,CAAC;;;;;;;;;;;;;;;;;;;;;AAgDD,SAAgB,oBAAoB,EAClC,QAAQ,KACR,aAAa,KACb,GAAG,SACqC;CACxC,OAAO,oBAAC,gBAAD;EAAgB,GAAI;EAAc;EAAmB;CAAa,CAAA;AAC3E"}
1
+ {"version":3,"file":"popover.js","names":["Popover","AriaPopover","Button"],"sources":["../../src/popover/popover.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, useContext, type JSX, type ReactNode } from \"react\";\nimport {\n OverlayTriggerStateContext,\n Popover as AriaPopover,\n PreviewTrigger,\n type PopoverProps as AriaPopoverProps,\n type PreviewTriggerProps,\n} from \"react-aria-components\";\n\nimport { Button } from \"../button/button.js\";\nimport { cnAll } from \"../lib/cn.js\";\n\n/**\n * Popover, ported from the legacy shadcn/Radix component.\n *\n * Census: 2 files, 2 uses. `asChild` on the trigger twice, `className`\n * twice, `align=\"start\"` once. `PopoverAnchor` is exported by the legacy\n * and imported by nobody, so it is not ported; neither are `side`,\n * `sideOffset`, `alignOffset`, `open` / `onOpenChange`, `modal`,\n * `collisionPadding` or `avoidCollisions` -- zero call sites each. Both\n * existing uses are uncontrolled.\n *\n * IT GAINS AN ACCESSIBLE NAME. The legacy renders `role=\"dialog\"` with no\n * `aria-label` and no `aria-labelledby`, so both call sites announce an\n * unnamed dialog. React Aria labels the popover from its trigger\n * automatically once the two are paired, which is the only way to build\n * one here.\n *\n * The surface is `card`: in the legacy theme `--popover` is byte-identical\n * to `--card` in both themes, so the role the migration plan proposed\n * already exists under another name.\n */\nexport const popoverVariants = cva([\n // w-72 (288px) is the legacy default width, as a spacing-scale step.\n \"w-72 rounded-panel border-(length:--border-width) border-border p-4\",\n \"bg-card text-card-foreground shadow-floating\",\n // The reset in @seekalfred/styles is OPT-IN, so a consumer may load\n // none: an unstyled document renders text in a serif, and content-box\n // would add the 16px padding on top of the width.\n \"box-border font-sans text-sm leading-(--leading-normal)\",\n // React Aria gives the popover tabindex=-1 and focuses it on open, so it\n // can take a focus ring it does not want.\n \"outline-none\",\n]);\n\n/**\n * Content anchored to a trigger.\n *\n * @example\n * <PopoverTrigger>\n * <Button>Pick a date</Button>\n * <Popover placement=\"bottom start\">...</Popover>\n * </PopoverTrigger>\n */\nexport interface PopoverProps\n extends Omit<AriaPopoverProps, \"className\" | \"style\" | \"children\"> {\n children?: ReactNode;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * Where it opens, as React Aria's single `placement` string. The legacy\n * split this across `side` and `align`; its one non-default call site is\n * `align=\"start\"`, which is `\"bottom start\"` here.\n *\n * @default \"bottom\"\n */\n placement?: AriaPopoverProps[\"placement\"];\n /**\n * How it comes in: `rise` grows out of the trigger (offset toward it, a little\n * small, then settles on a spring) and leaves quicker; `none` leaves the entrance\n * to the caller, as a picker that animates its own popover does.\n *\n * @default \"rise\"\n */\n entrance?: \"rise\" | \"none\";\n /**\n * A heading at the top of the panel. Together with `description` and\n * `actionLabel` it makes the message popover: a dark title, grey body text\n * and a full-width button, with `children` rendered below them.\n */\n title?: ReactNode;\n /** Grey body text under the title. */\n description?: ReactNode;\n /** The label of the full-width button at the bottom. Pressing it runs `onAction` and closes the popover. */\n actionLabel?: ReactNode;\n /** Runs when the action button is pressed, before the popover closes. */\n onAction?: () => void;\n}\n\nexport const Popover = forwardRef<HTMLElement, PopoverProps>(function Popover(\n {\n children,\n className,\n placement = \"bottom\",\n offset = 4,\n entrance = \"rise\",\n title,\n description,\n actionLabel,\n onAction,\n ...props\n },\n ref,\n) {\n // The open state the enclosing PopoverTrigger owns, so the action button can close it.\n const state = useContext(OverlayTriggerStateContext);\n return (\n <AriaPopover\n ref={ref}\n {...props}\n placement={placement}\n offset={offset}\n data-slot=\"popover\"\n className={cnAll(popoverVariants(), entrance === \"rise\" && \"popover-motion\", className)}\n >\n {/*\n No inner Dialog. Wrapping the content in one is the common React\n Aria idiom and it was in the first draft here -- but deleting it\n broke no assertion, so it was doing nothing: a Popover under a\n DialogTrigger already gets role=\"dialog\", a focus scope, and\n aria-labelledby pointing at the trigger. The wrapper was one more\n element and one more className to explain.\n */}\n {title == null ? null : (\n <h3\n data-slot=\"popover-title\"\n className=\"m-0 font-display text-sm leading-(--leading-tight) font-semibold text-foreground\"\n >\n {title}\n </h3>\n )}\n {description == null ? null : (\n <p\n data-slot=\"popover-description\"\n className=\"m-0 mt-1 text-sm leading-(--leading-normal) text-text-secondary\"\n >\n {description}\n </p>\n )}\n {actionLabel == null ? null : (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n className=\"mt-4 w-full\"\n onPress={() => {\n onAction?.();\n state?.close();\n }}\n >\n {actionLabel}\n </Button>\n )}\n {children}\n </AriaPopover>\n );\n});\n\n/**\n * Pairs a trigger with a popover and owns the open state.\n *\n * React Aria's `DialogTrigger`, renamed for the migration: it takes the\n * trigger element directly as its first child, which is what `asChild` was\n * doing on both legacy call sites.\n */\nexport { DialogTrigger as PopoverTrigger } from \"react-aria-components\";\n\nexport interface PopoverHoverTriggerProps extends Omit<PreviewTriggerProps, \"delay\" | \"closeDelay\"> {\n /** The trigger, then the `Popover`. The trigger must be focusable (a `Button` or `Link`). */\n children: ReactNode;\n /**\n * Milliseconds the pointer rests on the trigger, or keyboard focus sits on\n * it, before the card opens.\n * @default 600\n */\n delay?: number;\n /**\n * Milliseconds after the pointer leaves the trigger and the card, or focus\n * leaves both, before it closes. Moving onto the card keeps it open.\n * @default 200\n */\n closeDelay?: number;\n}\n\n/**\n * Opens a popover on hover instead of press: a hover card.\n *\n * React Aria's `PreviewTrigger`. The card opens after `delay` when the pointer\n * rests on the trigger or keyboard focus reaches it, and on a long press on\n * touch. It stays open while the pointer travels onto it (a safe area covers\n * the gap) and closes `closeDelay` after the pointer or focus leaves both.\n * Escape closes it; Tab from the trigger moves into it. It is non-modal: no\n * focus trap and no underlay, and the page behind stays usable.\n *\n * The trigger keeps its own press action, so a Link still navigates and a\n * Button still runs `onPress`. Content anyone needs must not live only in the\n * card: touch without a long press never sees it.\n *\n * @example\n * <PopoverHoverTrigger delay={300}>\n * <Button variant=\"ghost\" aria-label=\"3 more sources\">+3</Button>\n * <Popover placement=\"bottom start\">...</Popover>\n * </PopoverHoverTrigger>\n */\nexport function PopoverHoverTrigger({\n delay = 600,\n closeDelay = 200,\n ...props\n}: PopoverHoverTriggerProps): JSX.Element {\n return <PreviewTrigger {...props} delay={delay} closeDelay={closeDelay} />;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,MAAa,kBAAkB,IAAI;CAEjC;CACA;CAIA;CAGA;AACF,CAAC;AA8CD,MAAaA,YAAU,WAAsC,SAASA,UACpE,EACE,UACA,WACA,YAAY,UACZ,SAAS,GACT,WAAW,QACX,OACA,aACA,aACA,UACA,GAAG,SAEL,KACA;CAEA,MAAM,QAAQ,WAAW,0BAA0B;CACnD,OACE,qBAACC,SAAD;EACO;EACL,GAAI;EACO;EACH;EACR,aAAU;EACV,WAAW,MAAM,gBAAgB,GAAG,aAAa,UAAU,kBAAkB,SAAS;EANxF,UAAA;GAgBG,SAAS,OAAO,OACf,oBAAC,MAAD;IACE,aAAU;IACV,WAAU;IAET,UAAA;GACC,CAAA;GAEL,eAAe,OAAO,OACrB,oBAAC,KAAD;IACE,aAAU;IACV,WAAU;IAET,UAAA;GACA,CAAA;GAEJ,eAAe,OAAO,OACrB,oBAACC,UAAD;IACE,SAAQ;IACR,MAAK;IACL,WAAU;IACV,eAAe;KACb,WAAW;KACX,OAAO,MAAM;IACf;IAEC,UAAA;GACK,CAAA;GAET;EACU;;AAEjB,CAAC;;;;;;;;;;;;;;;;;;;;;AAgDD,SAAgB,oBAAoB,EAClC,QAAQ,KACR,aAAa,KACb,GAAG,SACqC;CACxC,OAAO,oBAAC,gBAAD;EAAgB,GAAI;EAAc;EAAmB;CAAa,CAAA;AAC3E"}
@@ -1,2 +1,2 @@
1
- import { Progress, ProgressProps, progressFillVariants, progressTrackVariants, progressVariants } from "./progress.js";
2
- export { Progress, type ProgressProps, progressFillVariants, progressTrackVariants, progressVariants };
1
+ import { Progress, ProgressProps, progressFillVariants, progressTargetVariants, progressTrackVariants, progressVariants } from "./progress.js";
2
+ export { Progress, type ProgressProps, progressFillVariants, progressTargetVariants, progressTrackVariants, progressVariants };
@@ -1,2 +1,2 @@
1
- import { Progress, progressFillVariants, progressTrackVariants, progressVariants } from "./progress.js";
2
- export { Progress, progressFillVariants, progressTrackVariants, progressVariants };
1
+ import { Progress, progressFillVariants, progressTargetVariants, progressTrackVariants, progressVariants } from "./progress.js";
2
+ export { Progress, progressFillVariants, progressTargetVariants, progressTrackVariants, progressVariants };
@@ -31,13 +31,35 @@ import { ProgressBarProps } from "react-aria-components";
31
31
  * the track against the page at 5.25:1.
32
32
  *
33
33
  * Both are in the build-failing contrast contract.
34
+ *
35
+ * A TARGET MARKER is opt-in (`target`): a short upright tick that stands
36
+ * proud of the track at the value the bar should have reached -- "where the
37
+ * goal should be by now". The tick is decorative; its meaning is read to
38
+ * assistive technology as the bar's description (`targetLabel` plus the
39
+ * formatted value), and `showTargetLabel` also prints it under the tick.
40
+ *
41
+ * `surface="inverse"` is for a bar on the inverted (foreground-coloured)
42
+ * surface, such as a dark summary strip: the track is the inverse text colour
43
+ * mixed into the surface, the default fill and the copy take the inverse text
44
+ * colour, so the bar keeps its contrast where the default greys would vanish.
34
45
  */
35
46
  export declare const progressVariants: (props?: ({
36
47
  headerEmpty?: boolean | null | undefined;
37
48
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
38
- export declare const progressTrackVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
49
+ export declare const progressTrackVariants: (props?: ({
50
+ surface?: "default" | "inverse" | null | undefined;
51
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
52
+ /**
53
+ * The target tick: 2px wide, standing 4px proud of the track on both sides,
54
+ * with a 1px halo of the surface colour so it reads over the fill as well as
55
+ * over the track. Placed by an inline `inset-inline-start`, so it mirrors in
56
+ * right-to-left text.
57
+ */
58
+ export declare const progressTargetVariants: (props?: ({
59
+ surface?: "default" | "inverse" | null | undefined;
60
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
39
61
  export declare const progressFillVariants: (props?: ({
40
- tone?: "success" | "warning" | "danger" | "neutral" | "info" | null | undefined;
62
+ tone?: "neutral" | "success" | "warning" | "danger" | "info" | null | undefined;
41
63
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
42
64
  /**
43
65
  * A determinate or indeterminate progress bar.
@@ -84,6 +106,35 @@ export interface ProgressProps extends Omit<ProgressBarProps, "className" | "sty
84
106
  * @default true
85
107
  */
86
108
  showCompletion?: boolean;
109
+ /**
110
+ * Where the bar should be by now, on the same scale as `value`
111
+ * (`minValue`..`maxValue`, clamped). Draws an upright tick across the track
112
+ * at that point. The tick is decorative; `targetLabel` and the formatted
113
+ * target are read as the bar's description. Leave it out (the default) for
114
+ * no marker and the bar exactly as before. Ignored while indeterminate.
115
+ */
116
+ target?: number;
117
+ /**
118
+ * What the target means, such as "Expected by now". Read with the formatted
119
+ * target ("Expected by now: 40%") as the bar's description, and printed
120
+ * under the tick with `showTargetLabel`.
121
+ * @default "Target"
122
+ */
123
+ targetLabel?: string;
124
+ /**
125
+ * Print `targetLabel` and the target under the tick. It follows the tick and
126
+ * stays inside the bar near either end.
127
+ * @default false
128
+ */
129
+ showTargetLabel?: boolean;
130
+ /**
131
+ * The surface the bar sits on. `inverse` is the inverted, foreground-coloured
132
+ * surface (a dark strip in the light theme, a light one in the dark theme):
133
+ * the track, the default-toned fill, the target tick and the copy take
134
+ * colours that hold their contrast there. Explicit tones keep their colours.
135
+ * @default "default"
136
+ */
137
+ surface?: "default" | "inverse";
87
138
  /** Extra classes for the track. */
88
139
  trackClassName?: string;
89
140
  /** Extra classes for the fill -- a per-call-site colour the tone axis cannot express. */