@seekalfred/primitives 0.5.1 → 0.6.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 (215) hide show
  1. package/README.md +664 -9
  2. package/dist/accordion/accordion.js +10 -4
  3. package/dist/accordion/accordion.js.map +1 -1
  4. package/dist/action-button/action-button.d.ts +50 -0
  5. package/dist/action-button/action-button.js +159 -0
  6. package/dist/action-button/action-button.js.map +1 -0
  7. package/dist/action-button/index.d.ts +2 -0
  8. package/dist/action-button/index.js +2 -0
  9. package/dist/alert/alert.d.ts +15 -1
  10. package/dist/alert/alert.js +116 -20
  11. package/dist/alert/alert.js.map +1 -1
  12. package/dist/avatar/avatar.d.ts +6 -0
  13. package/dist/avatar/avatar.js +47 -16
  14. package/dist/avatar/avatar.js.map +1 -1
  15. package/dist/badge/badge.d.ts +11 -4
  16. package/dist/badge/badge.js +35 -5
  17. package/dist/badge/badge.js.map +1 -1
  18. package/dist/breadcrumb/breadcrumb.d.ts +0 -27
  19. package/dist/breadcrumb/breadcrumb.js +20 -6
  20. package/dist/breadcrumb/breadcrumb.js.map +1 -1
  21. package/dist/button/button.d.ts +35 -22
  22. package/dist/button/button.js +177 -34
  23. package/dist/button/button.js.map +1 -1
  24. package/dist/button-group/button-group.d.ts +76 -0
  25. package/dist/button-group/button-group.js +403 -0
  26. package/dist/button-group/button-group.js.map +1 -0
  27. package/dist/button-group/index.d.ts +2 -0
  28. package/dist/button-group/index.js +2 -0
  29. package/dist/calendar/calendar.d.ts +1 -1
  30. package/dist/calendar/calendar.js +257 -30
  31. package/dist/calendar/calendar.js.map +1 -1
  32. package/dist/card/card.d.ts +9 -2
  33. package/dist/card/card.js +15 -4
  34. package/dist/card/card.js.map +1 -1
  35. package/dist/checkbox/checkbox.d.ts +0 -7
  36. package/dist/checkbox/checkbox.js +24 -27
  37. package/dist/checkbox/checkbox.js.map +1 -1
  38. package/dist/confirm-morph/confirm-morph.d.ts +85 -0
  39. package/dist/confirm-morph/confirm-morph.js +652 -0
  40. package/dist/confirm-morph/confirm-morph.js.map +1 -0
  41. package/dist/confirm-morph/index.d.ts +2 -0
  42. package/dist/confirm-morph/index.js +2 -0
  43. package/dist/copy-button/copy-button.d.ts +32 -0
  44. package/dist/copy-button/copy-button.js +134 -0
  45. package/dist/copy-button/copy-button.js.map +1 -0
  46. package/dist/copy-button/index.d.ts +2 -0
  47. package/dist/copy-button/index.js +2 -0
  48. package/dist/date-picker/date-picker.d.ts +51 -0
  49. package/dist/date-picker/date-picker.js +241 -0
  50. package/dist/date-picker/date-picker.js.map +1 -0
  51. package/dist/date-picker/date-range-picker.d.ts +78 -0
  52. package/dist/date-picker/date-range-picker.js +1015 -0
  53. package/dist/date-picker/date-range-picker.js.map +1 -0
  54. package/dist/date-picker/index.d.ts +4 -0
  55. package/dist/date-picker/index.js +4 -0
  56. package/dist/date-picker/picker-parts.d.ts +46 -0
  57. package/dist/date-picker/picker-parts.js +135 -0
  58. package/dist/date-picker/picker-parts.js.map +1 -0
  59. package/dist/dialog/dialog.d.ts +10 -14
  60. package/dist/dialog/dialog.js +39 -11
  61. package/dist/dialog/dialog.js.map +1 -1
  62. package/dist/empty-state/empty-state.d.ts +44 -0
  63. package/dist/empty-state/empty-state.js +83 -0
  64. package/dist/empty-state/empty-state.js.map +1 -0
  65. package/dist/empty-state/index.d.ts +2 -0
  66. package/dist/empty-state/index.js +2 -0
  67. package/dist/expanding-button-group/expanding-button-group.d.ts +54 -0
  68. package/dist/expanding-button-group/expanding-button-group.js +350 -0
  69. package/dist/expanding-button-group/expanding-button-group.js.map +1 -0
  70. package/dist/expanding-button-group/index.d.ts +2 -0
  71. package/dist/expanding-button-group/index.js +2 -0
  72. package/dist/file-drop-zone/file-drop-zone.d.ts +71 -0
  73. package/dist/file-drop-zone/file-drop-zone.js +116 -0
  74. package/dist/file-drop-zone/file-drop-zone.js.map +1 -0
  75. package/dist/file-drop-zone/index.d.ts +2 -0
  76. package/dist/file-drop-zone/index.js +2 -0
  77. package/dist/filter-bar/filter-bar.d.ts +121 -0
  78. package/dist/filter-bar/filter-bar.js +300 -0
  79. package/dist/filter-bar/filter-bar.js.map +1 -0
  80. package/dist/filter-bar/index.d.ts +2 -0
  81. package/dist/filter-bar/index.js +2 -0
  82. package/dist/floating-button-group/floating-button-group.d.ts +61 -0
  83. package/dist/floating-button-group/floating-button-group.js +340 -0
  84. package/dist/floating-button-group/floating-button-group.js.map +1 -0
  85. package/dist/floating-button-group/index.d.ts +2 -0
  86. package/dist/floating-button-group/index.js +2 -0
  87. package/dist/icon-morph/icon-morph.d.ts +55 -0
  88. package/dist/icon-morph/icon-morph.js +331 -0
  89. package/dist/icon-morph/icon-morph.js.map +1 -0
  90. package/dist/icon-morph/index.d.ts +2 -0
  91. package/dist/icon-morph/index.js +2 -0
  92. package/dist/index.d.ts +24 -2
  93. package/dist/index.js +29 -7
  94. package/dist/input/input.d.ts +22 -0
  95. package/dist/input/input.js +36 -12
  96. package/dist/input/input.js.map +1 -1
  97. package/dist/lib/count-to.js +113 -0
  98. package/dist/lib/count-to.js.map +1 -0
  99. package/dist/lib/eye-morph.js +62 -0
  100. package/dist/lib/eye-morph.js.map +1 -0
  101. package/dist/lib/field-message.js +336 -0
  102. package/dist/lib/field-message.js.map +1 -0
  103. package/dist/lib/field-shell.js +81 -0
  104. package/dist/lib/field-shell.js.map +1 -0
  105. package/dist/lib/field-surface.js +4 -2
  106. package/dist/lib/field-surface.js.map +1 -1
  107. package/dist/lib/flip.js +80 -0
  108. package/dist/lib/flip.js.map +1 -0
  109. package/dist/lib/height-follow.js +59 -0
  110. package/dist/lib/height-follow.js.map +1 -0
  111. package/dist/lib/icon-swap.js +47 -0
  112. package/dist/lib/icon-swap.js.map +1 -0
  113. package/dist/lib/leaving-list.js +57 -0
  114. package/dist/lib/leaving-list.js.map +1 -0
  115. package/dist/lib/merge-refs.js +3 -3
  116. package/dist/lib/merge-refs.js.map +1 -1
  117. package/dist/lib/morph-text.js +128 -0
  118. package/dist/lib/morph-text.js.map +1 -0
  119. package/dist/lib/motion.js +162 -0
  120. package/dist/lib/motion.js.map +1 -0
  121. package/dist/lib/pausable-timeout.js +67 -0
  122. package/dist/lib/pausable-timeout.js.map +1 -0
  123. package/dist/lib/rolling-value.js +31 -0
  124. package/dist/lib/rolling-value.js.map +1 -0
  125. package/dist/lib/spring-progress.js +114 -0
  126. package/dist/lib/spring-progress.js.map +1 -0
  127. package/dist/lib/svg-id.js +10 -0
  128. package/dist/lib/svg-id.js.map +1 -0
  129. package/dist/lib/swap-text.js +77 -0
  130. package/dist/lib/swap-text.js.map +1 -0
  131. package/dist/lib/use-copy-feedback.js +36 -0
  132. package/dist/lib/use-copy-feedback.js.map +1 -0
  133. package/dist/page-header/index.d.ts +2 -0
  134. package/dist/page-header/index.js +2 -0
  135. package/dist/page-header/page-header.d.ts +75 -0
  136. package/dist/page-header/page-header.js +83 -0
  137. package/dist/page-header/page-header.js.map +1 -0
  138. package/dist/password-field/index.d.ts +2 -0
  139. package/dist/password-field/index.js +2 -0
  140. package/dist/password-field/password-field.d.ts +37 -0
  141. package/dist/password-field/password-field.js +101 -0
  142. package/dist/password-field/password-field.js.map +1 -0
  143. package/dist/password-strength/index.d.ts +2 -0
  144. package/dist/password-strength/index.js +2 -0
  145. package/dist/password-strength/password-strength.d.ts +127 -0
  146. package/dist/password-strength/password-strength.js +455 -0
  147. package/dist/password-strength/password-strength.js.map +1 -0
  148. package/dist/popover/popover.d.ts +8 -0
  149. package/dist/popover/popover.js +3 -3
  150. package/dist/popover/popover.js.map +1 -1
  151. package/dist/progress/progress.js +46 -5
  152. package/dist/progress/progress.js.map +1 -1
  153. package/dist/radio-group/radio-group.js +7 -6
  154. package/dist/radio-group/radio-group.js.map +1 -1
  155. package/dist/scroll-area/scroll-area.d.ts +10 -0
  156. package/dist/scroll-area/scroll-area.js +25 -3
  157. package/dist/scroll-area/scroll-area.js.map +1 -1
  158. package/dist/search-field/index.d.ts +2 -0
  159. package/dist/search-field/index.js +2 -0
  160. package/dist/search-field/search-field.d.ts +26 -0
  161. package/dist/search-field/search-field.js +155 -0
  162. package/dist/search-field/search-field.js.map +1 -0
  163. package/dist/select/select.js +79 -21
  164. package/dist/select/select.js.map +1 -1
  165. package/dist/sheet/sheet.js +2 -1
  166. package/dist/sheet/sheet.js.map +1 -1
  167. package/dist/side-nav/index.d.ts +2 -0
  168. package/dist/side-nav/index.js +2 -0
  169. package/dist/side-nav/side-nav.d.ts +72 -0
  170. package/dist/side-nav/side-nav.js +142 -0
  171. package/dist/side-nav/side-nav.js.map +1 -0
  172. package/dist/skeleton/skeleton.d.ts +8 -2
  173. package/dist/skeleton/skeleton.js +6 -2
  174. package/dist/skeleton/skeleton.js.map +1 -1
  175. package/dist/slider/slider.js +61 -7
  176. package/dist/slider/slider.js.map +1 -1
  177. package/dist/split-button/index.d.ts +2 -0
  178. package/dist/split-button/index.js +2 -0
  179. package/dist/split-button/split-button.d.ts +46 -0
  180. package/dist/split-button/split-button.js +122 -0
  181. package/dist/split-button/split-button.js.map +1 -0
  182. package/dist/stat-card/index.d.ts +2 -0
  183. package/dist/stat-card/index.js +2 -0
  184. package/dist/stat-card/stat-card.d.ts +81 -0
  185. package/dist/stat-card/stat-card.js +152 -0
  186. package/dist/stat-card/stat-card.js.map +1 -0
  187. package/dist/styles.css +1 -1
  188. package/dist/switch/switch.js +9 -8
  189. package/dist/switch/switch.js.map +1 -1
  190. package/dist/table/index.d.ts +2 -2
  191. package/dist/table/index.js +2 -2
  192. package/dist/table/table.d.ts +36 -5
  193. package/dist/table/table.js +112 -11
  194. package/dist/table/table.js.map +1 -1
  195. package/dist/tabs/tabs.js +142 -6
  196. package/dist/tabs/tabs.js.map +1 -1
  197. package/dist/textarea/textarea.d.ts +23 -0
  198. package/dist/textarea/textarea.js +24 -2
  199. package/dist/textarea/textarea.js.map +1 -1
  200. package/dist/theme-switch/index.d.ts +2 -0
  201. package/dist/theme-switch/index.js +2 -0
  202. package/dist/theme-switch/theme-switch.d.ts +37 -0
  203. package/dist/theme-switch/theme-switch.js +123 -0
  204. package/dist/theme-switch/theme-switch.js.map +1 -0
  205. package/dist/time-picker/index.d.ts +2 -0
  206. package/dist/time-picker/index.js +2 -0
  207. package/dist/time-picker/time-picker.d.ts +54 -0
  208. package/dist/time-picker/time-picker.js +201 -0
  209. package/dist/time-picker/time-picker.js.map +1 -0
  210. package/dist/toast/toast.js +58 -38
  211. package/dist/toast/toast.js.map +1 -1
  212. package/dist/tooltip/tooltip.d.ts +1 -1
  213. package/dist/tooltip/tooltip.js +63 -14
  214. package/dist/tooltip/tooltip.js.map +1 -1
  215. package/package.json +83 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expanding-button-group.js","names":[],"sources":["../../src/expanding-button-group/expanding-button-group.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n type CSSProperties,\n type FocusEvent,\n type KeyboardEvent,\n type PointerEvent,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { IconSwap } from \"../lib/icon-swap.js\";\nimport { useExitBackstop, useIsomorphicLayoutEffect, useLeavingItems } from \"../lib/motion.js\";\n\nexport type ExpandingButtonGroupSize = \"sm\" | \"md\";\nexport type ExpandingButtonGroupTone = \"neutral\" | \"danger\";\n\nexport interface ExpandingButtonGroupItem {\n /** Stable key for the action. Also the value passed to onAction. */\n id: string;\n /** Visible label once the button expands, and its accessible name at all times. Use a short verb, such as \"Archive\". */\n label: string;\n /** Icon shown while collapsed. A 16px icon fits; it is sized by the group. Mark it `aria-hidden`. */\n icon: ReactNode;\n /** Runs the action. Return false, or reject, to skip the confirmation. A promise keeps the button busy until it settles. */\n onSelect?: () => void | boolean | Promise<void | boolean>;\n /** Past tense word shown in place after the action, such as \"Archived\". The icon becomes a check for confirmDuration. Omit for actions that open something. */\n doneLabel?: string;\n /** `danger` colours the expanded state with the destructive token, for destructive actions such as Delete. */\n tone?: ExpandingButtonGroupTone;\n /** Dimmed and inert. Stays focusable and still reveals its label, so people can learn what it would do. */\n isDisabled?: boolean;\n}\n\nexport interface ExpandingButtonGroupProps {\n items: readonly ExpandingButtonGroupItem[];\n /** Accessible name of the toolbar, such as \"Message actions\". */\n label: string;\n /** @default \"md\" */\n size?: ExpandingButtonGroupSize;\n /**\n * Id of the action whose label shows at rest, usually the primary one. Defaults to the first enabled action: one action is always\n * expanded. Pointing at or focusing another moves the expansion there, and it returns here when the pointer and focus leave.\n */\n defaultExpanded?: string | null;\n /** Called with the item id whenever an action runs, before its own onSelect. */\n onAction?: (id: string) => void;\n /** How long the confirmation stays, in milliseconds. @default 1400 */\n confirmDuration?: number;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\n/** Geometry per size. The icon keeps the same left inset collapsed and expanded, so it never moves while the label is revealed. */\nconst metrics = {\n sm: { height: 28, icon: 14, start: 7, gap: 5, end: 11 },\n md: { height: 36, icon: 16, start: 10, gap: 7, end: 14 },\n} as const;\n\n/** Pixels the pointer must travel into a neighbouring action before the expansion moves to it. */\nconst hysteresis = 4;\n\n/** The confirmation tick draws itself from its short stroke. */\nfunction DrawnCheck() {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M4 12.5l5 5L20 6.5\"\n pathLength={1}\n className=\"[stroke-dasharray:1] motion-safe:animate-check-draw-quick\"\n />\n </svg>\n );\n}\n\nfunction LeavingWord({\n id,\n onDone,\n children,\n}: {\n id: number;\n onDone: (id: number) => void;\n children: ReactNode;\n}) {\n const remove = useCallback(() => onDone(id), [onDone, id]);\n useExitBackstop(true, remove);\n return (\n <span\n aria-hidden=\"true\"\n onAnimationEnd={(event) => {\n if (event.target === event.currentTarget) remove();\n }}\n className=\"col-start-1 row-start-1 whitespace-nowrap motion-safe:animate-word-out motion-reduce:animate-fade-out\"\n >\n {children}\n </span>\n );\n}\n\ninterface ItemProps {\n item: ExpandingButtonGroupItem;\n size: ExpandingButtonGroupSize;\n expanded: boolean;\n done: boolean;\n busy: boolean;\n tabStop: boolean;\n onPointerDown: (item: ExpandingButtonGroupItem, event: PointerEvent<HTMLButtonElement>) => void;\n onClick: (item: ExpandingButtonGroupItem) => void;\n onFocus: (item: ExpandingButtonGroupItem, event: FocusEvent<HTMLButtonElement>) => void;\n}\n\nfunction Item({\n item,\n size,\n expanded,\n done,\n busy,\n tabStop,\n onPointerDown,\n onClick,\n onFocus,\n}: ItemProps) {\n const m = metrics[size];\n const labelRef = useRef<HTMLSpanElement>(null);\n const doneRef = useRef<HTMLSpanElement>(null);\n const wordRef = useRef<HTMLSpanElement>(null);\n const [text, setText] = useState(0);\n\n // Hidden copies of both words share the label's grid cell, so the slot is always as wide as the longer one. The confirmation\n // swap then never changes the width, and the server render already has the right width before anything is measured.\n // Changes under half a pixel are ignored, so a re-measure never makes the row wobble.\n useIsomorphicLayoutEffect(() => {\n const measure = () => {\n const next = Math.max(\n labelRef.current?.getBoundingClientRect().width ?? 0,\n doneRef.current?.getBoundingClientRect().width ?? 0,\n );\n setText((current) => (Math.abs(current - next) < 0.5 ? current : next));\n };\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n // A frame later, so the state it sets cannot report a ResizeObserver loop.\n let frame = 0;\n const observer = new ResizeObserver(() => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(measure);\n });\n if (labelRef.current) observer.observe(labelRef.current);\n if (doneRef.current) observer.observe(doneRef.current);\n return () => {\n cancelAnimationFrame(frame);\n observer.disconnect();\n };\n }, [item.label, item.doneLabel, size]);\n\n const showDone = done && !!item.doneLabel;\n const measured = text > 0;\n // Width rides the morph spring, so neighbours slide and the group's own width follows without a jump.\n const target = expanded && measured ? m.start + m.icon + m.gap + text + m.end : m.height;\n const word = showDone ? item.doneLabel! : item.label;\n const { leaving, changed, remove } = useLeavingItems(word, word, () => wordRef.current);\n\n return (\n <button\n type=\"button\"\n style={{ width: target }}\n data-ebg-item=\"\"\n data-id={item.id}\n data-tone={item.tone ?? \"neutral\"}\n data-state={showDone ? \"done\" : \"idle\"}\n data-expanded={expanded || undefined}\n data-unmeasured={measured ? undefined : \"\"}\n aria-label={item.label}\n aria-disabled={item.isDisabled || undefined}\n aria-busy={busy || undefined}\n tabIndex={tabStop ? 0 : -1}\n onPointerDown={(event) => onPointerDown(item, event)}\n onClick={() => onClick(item)}\n onFocus={(event) => onFocus(item, event)}\n className=\"expanding-group-item focus-visible:outline-solid focus-visible:outline-(length:--focus-ring-width) focus-visible:outline-offset-(--focus-ring-offset) focus-visible:outline-(--color-ring)\"\n >\n <span className=\"expanding-group-icon\" aria-hidden=\"true\">\n <IconSwap iconKey={showDone ? \"done\" : \"icon\"} tight fill className=\"size-full\">\n {showDone ? <DrawnCheck /> : item.icon}\n </IconSwap>\n </span>\n {/* Outgoing and incoming words share one cell, so a confirmation crossfades in place while the width springs between them. */}\n <span className=\"expanding-group-label\" aria-hidden=\"true\">\n {leaving.map((entry) => (\n <LeavingWord key={entry.id} id={entry.id} onDone={remove}>\n {entry.value}\n </LeavingWord>\n ))}\n <span\n key={word}\n ref={wordRef}\n className={cn(\n \"col-start-1 row-start-1 whitespace-nowrap\" +\n (changed ? \" motion-safe:animate-word-in motion-reduce:animate-fade-in\" : \"\"),\n )}\n >\n {word}\n </span>\n {/* Invisible copies of both words hold the slot at the longer one; they are also what gets measured for the width target. */}\n <span\n ref={labelRef}\n className=\"pointer-events-none invisible col-start-1 row-start-1 whitespace-nowrap\"\n >\n {item.label}\n </span>\n {item.doneLabel ? (\n <span\n ref={doneRef}\n className=\"pointer-events-none invisible col-start-1 row-start-1 whitespace-nowrap\"\n >\n {item.doneLabel}\n </span>\n ) : null}\n </span>\n </button>\n );\n}\n\n/**\n * A compact pill of icon buttons for actions. One action always shows its label;\n * the one you point at or focus takes over the label while its neighbours slide\n * aside, and an action can confirm in place: the icon draws a check and the label\n * becomes a past tense word. On touch, the first tap reveals the label and a\n * second tap runs the action.\n *\n * Arrow keys move focus, Home and End jump to the ends. A disabled action stays\n * focusable and still reveals its label, so people can learn what it would do.\n *\n * @example\n * <ExpandingButtonGroup label=\"Message actions\" items={[{ id: \"archive\", label: \"Archive\", doneLabel: \"Archived\", icon: <ArchiveIcon aria-hidden /> }]} />\n */\nexport function ExpandingButtonGroup({\n items,\n label,\n size = \"md\",\n defaultExpanded = null,\n onAction,\n confirmDuration = 1400,\n className,\n}: ExpandingButtonGroupProps) {\n const root = useRef<HTMLDivElement>(null);\n const [intent, setIntent] = useState<string | null>(null);\n const [done, setDone] = useState<{ id: string; seq: number } | null>(null);\n const [busy, setBusy] = useState<string | null>(null);\n const [announcement, setAnnouncement] = useState(\"\");\n const [focused, setFocused] = useState<string | null>(null);\n // The width only transitions once the group has been painted and measured, so the first widths land at once.\n const [settled, setSettled] = useState(false);\n\n // Refs mirror what the pointer and keyboard are doing right now, so leave and blur handlers can fall back correctly.\n const pointerOver = useRef<string | null>(null);\n const pointerAt = useRef<{ x: number; y: number } | null>(null);\n const keyFocus = useRef<string | null>(null);\n const touchOpen = useRef<string | null>(null);\n const press = useRef<{ id: string; kind: string; wasExpanded: boolean } | null>(null);\n const doneRef = useRef<string | null>(null);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const mounted = useRef(true);\n const seq = useRef(0);\n\n useEffect(() => {\n mounted.current = true;\n const frames = requestAnimationFrame(() => requestAnimationFrame(() => setSettled(true)));\n return () => {\n mounted.current = false;\n cancelAnimationFrame(frames);\n if (timer.current) clearTimeout(timer.current);\n };\n }, []);\n\n const has = (id: string | null) => !!id && items.some((item) => item.id === id);\n // One action is always expanded: the resting one is defaultExpanded, else the first enabled action.\n const firstEnabled = items.find((item) => !item.isDisabled)?.id ?? items[0]?.id ?? null;\n const restId = has(defaultExpanded) ? defaultExpanded : firstEnabled;\n const expandedId = has(intent) ? intent : restId;\n const tabStop = has(focused) ? focused : restId;\n\n // A tap outside closes a label opened by a first tap.\n useEffect(() => {\n if (!intent || touchOpen.current !== intent) return;\n const onDown = (event: globalThis.PointerEvent) => {\n if (root.current?.contains(event.target as Node)) return;\n touchOpen.current = null;\n setIntent(doneRef.current ?? keyFocus.current);\n };\n document.addEventListener(\"pointerdown\", onDown, true);\n return () => document.removeEventListener(\"pointerdown\", onDown, true);\n }, [intent]);\n\n const confirm = useCallback(\n (item: ExpandingButtonGroupItem) => {\n if (timer.current) clearTimeout(timer.current);\n seq.current += 1;\n doneRef.current = item.id;\n setDone({ id: item.id, seq: seq.current });\n // A zero width space alternates so the same word is announced again on a repeat action.\n setAnnouncement((item.doneLabel ?? \"\") + (seq.current % 2 ? \"\" : \"​\"));\n timer.current = setTimeout(() => {\n doneRef.current = null;\n setDone(null);\n // Collapse unless the pointer or the keyboard is still on it.\n if (pointerOver.current !== item.id && keyFocus.current !== item.id) {\n if (touchOpen.current === item.id) touchOpen.current = null;\n setIntent((current) =>\n current === item.id ? (pointerOver.current ?? keyFocus.current) : current,\n );\n }\n }, confirmDuration);\n },\n [confirmDuration],\n );\n\n const activate = useCallback(\n async (item: ExpandingButtonGroupItem) => {\n if (item.isDisabled || busy === item.id || doneRef.current === item.id) return;\n setIntent(item.id);\n onAction?.(item.id);\n let result: void | boolean;\n try {\n const pending = item.onSelect?.();\n if (pending instanceof Promise) {\n setBusy(item.id);\n result = await pending;\n } else result = pending;\n } catch {\n result = false;\n } finally {\n if (mounted.current) setBusy((current) => (current === item.id ? null : current));\n }\n if (!mounted.current || result === false || !item.doneLabel) return;\n confirm(item);\n },\n [busy, confirm, onAction],\n );\n\n const onItemPointerDown = (\n item: ExpandingButtonGroupItem,\n event: PointerEvent<HTMLButtonElement>,\n ) => {\n press.current = { id: item.id, kind: event.pointerType, wasExpanded: expandedId === item.id };\n };\n\n const onItemClick = (item: ExpandingButtonGroupItem) => {\n const last = press.current;\n press.current = null;\n // Touch has no hover: the first tap opens the label, the second runs it. An item already showing its label runs at once.\n if (last && last.id === item.id && last.kind === \"touch\" && !last.wasExpanded) {\n touchOpen.current = item.id;\n setIntent(item.id);\n return;\n }\n if (last?.kind === \"touch\") touchOpen.current = item.id;\n void activate(item);\n };\n\n const onItemFocus = (item: ExpandingButtonGroupItem, event: FocusEvent<HTMLButtonElement>) => {\n setFocused(item.id);\n // Only keyboard focus expands; a mouse click or a tap already has its own path, and would otherwise leave the label open.\n let visible = true;\n try {\n visible = event.currentTarget.matches(\":focus-visible\");\n } catch {\n /* older engines expand on any focus */\n }\n if (!visible) return;\n keyFocus.current = item.id;\n setIntent(item.id);\n };\n\n const onBlur = (event: FocusEvent<HTMLDivElement>) => {\n if (root.current?.contains(event.relatedTarget)) return;\n keyFocus.current = null;\n touchOpen.current = null;\n setIntent(pointerOver.current ?? doneRef.current);\n };\n\n // Hover follows real pointer movement only. Expanding moves the buttons, and a button that slides under a resting pointer\n // must not take over, or two neighbours could trade places back and forth.\n const onPointerMove = (event: PointerEvent<HTMLDivElement>) => {\n if (event.pointerType !== \"mouse\") return;\n const at = pointerAt.current;\n if (at && Math.abs(at.x - event.clientX) < 1 && Math.abs(at.y - event.clientY) < 1) return;\n pointerAt.current = { x: event.clientX, y: event.clientY };\n const button = (event.target as HTMLElement).closest<HTMLElement>(\"[data-ebg-item]\");\n const id = button?.dataset.id;\n if (!button || !id || id === pointerOver.current) return;\n // Hysteresis: moving from one action to the next needs the pointer a few pixels inside the new one, so resting on a\n // boundary never flips between them.\n if (pointerOver.current) {\n const box = button.getBoundingClientRect();\n if (event.clientX < box.left + hysteresis || event.clientX > box.right - hysteresis) return;\n }\n pointerOver.current = id;\n setIntent(id);\n };\n\n const onPointerLeave = (event: PointerEvent<HTMLDivElement>) => {\n if (event.pointerType !== \"mouse\") return;\n pointerAt.current = null;\n pointerOver.current = null;\n setIntent(doneRef.current ?? keyFocus.current);\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n const list = Array.from(\n root.current?.querySelectorAll<HTMLButtonElement>(\"[data-ebg-item]\") ?? [],\n );\n const index = list.findIndex((button) => button === document.activeElement);\n if (index < 0) return;\n const last = list.length - 1;\n const target =\n event.key === \"ArrowRight\"\n ? index === last\n ? 0\n : index + 1\n : event.key === \"ArrowLeft\"\n ? index === 0\n ? last\n : index - 1\n : event.key === \"Home\"\n ? 0\n : event.key === \"End\"\n ? last\n : -1;\n if (target < 0) return;\n event.preventDefault();\n list[target]!.focus();\n };\n\n const m = metrics[size];\n const vars = {\n \"--ebg-h\": m.height + \"px\",\n \"--ebg-icon\": m.icon + \"px\",\n \"--ebg-start\": m.start + \"px\",\n \"--ebg-gap\": m.gap + \"px\",\n \"--ebg-end\": m.end + \"px\",\n } as CSSProperties;\n\n return (\n <div\n ref={root}\n role=\"toolbar\"\n aria-label={label}\n aria-orientation=\"horizontal\"\n data-size={size}\n data-settled={settled ? \"\" : undefined}\n style={vars}\n onKeyDown={onKeyDown}\n onBlur={onBlur}\n onPointerMove={onPointerMove}\n onPointerLeave={onPointerLeave}\n className={cn(\"expanding-group\", className)}\n >\n {items.map((item) => (\n <Item\n key={item.id}\n item={item}\n size={size}\n expanded={expandedId === item.id}\n done={done?.id === item.id}\n busy={busy === item.id}\n tabStop={tabStop === item.id}\n onPointerDown={onItemPointerDown}\n onClick={onItemClick}\n onFocus={onItemFocus}\n />\n ))}\n <span className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {announcement}\n </span>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { ExpandingButtonGroup, ExpandingButtonGroupItem, ExpandingButtonGroupProps, ExpandingButtonGroupSize, ExpandingButtonGroupTone } from "./expanding-button-group.js";
2
+ export { ExpandingButtonGroup, type ExpandingButtonGroupItem, type ExpandingButtonGroupProps, type ExpandingButtonGroupSize, type ExpandingButtonGroupTone };
@@ -0,0 +1,2 @@
1
+ import { ExpandingButtonGroup } from "./expanding-button-group.js";
2
+ export { ExpandingButtonGroup };
@@ -0,0 +1,71 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/file-drop-zone/file-drop-zone.d.ts
3
+ /**
4
+ * FileDropZone: a dashed area a file can be dropped on, with a "Choose file"
5
+ * button that opens the system's file dialog.
6
+ *
7
+ * Built on React Aria's `DropZone` and `FileTrigger`. The browser's own
8
+ * `<input type="file">` ("Choose Files / No file chosen") is never shown: its
9
+ * look differs on every browser and it cannot be styled. FileTrigger keeps a
10
+ * real file input, visually hidden and out of the Tab order, and opens it when
11
+ * the button is pressed, so the keyboard path is Tab to the button, then Enter
12
+ * or Space.
13
+ *
14
+ * The zone states what it takes before a file is chosen: `hint` is where the
15
+ * accepted types and the size limit go.
16
+ *
17
+ * Every string in the cva blocks below is a STATIC literal. Tailwind scans
18
+ * source text, so a class assembled at runtime is invisible to it and would
19
+ * simply never be emitted.
20
+ */
21
+ export declare const fileDropZoneVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
22
+ export declare const fileDropZonePromptVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
23
+ export declare const fileDropZoneHintVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
24
+ /**
25
+ * A place to drop or choose files.
26
+ *
27
+ * @example
28
+ * <FileDropZone
29
+ * label="Contract"
30
+ * prompt="Drop a contract here"
31
+ * hint="PDF only, up to 20 MB."
32
+ * acceptedFileTypes={["application/pdf"]}
33
+ * onFiles={(files) => upload(files[0])}
34
+ * />
35
+ */
36
+ export interface FileDropZoneProps {
37
+ /** What the file is. The drop zone's accessible name. */
38
+ label: string;
39
+ /** The zone's main line: what to drop here, or the chosen file's name. */
40
+ prompt: ReactNode;
41
+ /**
42
+ * What the prompt says while a file is over the zone. When it is given the
43
+ * prompt swaps to it (the new text rises in, the old lifts away) and back again.
44
+ */
45
+ dropPrompt?: ReactNode;
46
+ /** The types and size accepted, and anything else to know before choosing. */
47
+ hint?: ReactNode;
48
+ /**
49
+ * MIME types or extensions the file dialog offers, as for a native
50
+ * `accept`. A drop is not filtered: what was dropped is handed over and the
51
+ * consumer decides what it can read.
52
+ */
53
+ acceptedFileTypes?: string[];
54
+ /** Take several files at once. Otherwise only the first is handed over. */
55
+ allowsMultiple?: boolean;
56
+ /** Called with the chosen or dropped files. */
57
+ onFiles: (files: File[]) => void;
58
+ /**
59
+ * The button's text.
60
+ *
61
+ * @default "Choose file", or "Choose files" with `allowsMultiple`
62
+ */
63
+ buttonLabel?: string;
64
+ /** Take no file: the button is disabled and a drop is refused. */
65
+ isDisabled?: boolean;
66
+ /** Extra classes for the zone. Merged last, so they win conflicts with ours. */
67
+ className?: string;
68
+ }
69
+ export declare const FileDropZone: import("react").ForwardRefExoticComponent<FileDropZoneProps & import("react").RefAttributes<HTMLDivElement>>;
70
+ //#endregion
71
+ //# sourceMappingURL=file-drop-zone.d.ts.map
@@ -0,0 +1,116 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { mergeRefs } from "../lib/merge-refs.js";
4
+ import { Button as Button$1 } from "../button/button.js";
5
+ import { SwapText } from "../lib/swap-text.js";
6
+ import { cva } from "class-variance-authority";
7
+ import { forwardRef, useMemo, useRef } from "react";
8
+ import { DropZone, FileTrigger } from "react-aria-components";
9
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
10
+ //#region src/file-drop-zone/file-drop-zone.tsx
11
+ /**
12
+ * FileDropZone: a dashed area a file can be dropped on, with a "Choose file"
13
+ * button that opens the system's file dialog.
14
+ *
15
+ * Built on React Aria's `DropZone` and `FileTrigger`. The browser's own
16
+ * `<input type="file">` ("Choose Files / No file chosen") is never shown: its
17
+ * look differs on every browser and it cannot be styled. FileTrigger keeps a
18
+ * real file input, visually hidden and out of the Tab order, and opens it when
19
+ * the button is pressed, so the keyboard path is Tab to the button, then Enter
20
+ * or Space.
21
+ *
22
+ * The zone states what it takes before a file is chosen: `hint` is where the
23
+ * accepted types and the size limit go.
24
+ *
25
+ * Every string in the cva blocks below is a STATIC literal. Tailwind scans
26
+ * source text, so a class assembled at runtime is invisible to it and would
27
+ * simply never be emitted.
28
+ */
29
+ const fileDropZoneVariants = cva([
30
+ "flex flex-col items-center gap-2 px-4 py-8 text-center",
31
+ "rounded-md border-(length:--border-width) border-dashed border-border",
32
+ "box-border font-sans text-foreground",
33
+ "outline-none",
34
+ "drop-zone-motion",
35
+ "data-[hovered]:bg-muted",
36
+ "data-[drop-target]:border-primary data-[drop-target]:bg-muted",
37
+ "data-[focus-visible]:outline-solid",
38
+ "data-[focus-visible]:outline-(length:--focus-ring-width)",
39
+ "data-[focus-visible]:outline-offset-(--focus-ring-offset)",
40
+ "data-[focus-visible]:outline-(--color-ring)",
41
+ "data-[disabled]:cursor-not-allowed"
42
+ ]);
43
+ const fileDropZonePromptVariants = cva("m-0 text-sm leading-(--leading-normal)");
44
+ const fileDropZoneHintVariants = cva("m-0 text-xs leading-(--leading-normal) text-muted-foreground");
45
+ async function filesOf(event) {
46
+ const items = event.items.filter((item) => item.kind === "file");
47
+ return Promise.all(items.map((item) => item.getFile()));
48
+ }
49
+ const FileDropZone = forwardRef(function FileDropZone({ label, prompt, dropPrompt, hint, acceptedFileTypes, allowsMultiple = false, onFiles, buttonLabel, isDisabled = false, className }, ref) {
50
+ const hand = (files) => {
51
+ if (isDisabled || files.length === 0) return;
52
+ onFiles(allowsMultiple ? files : files.slice(0, 1));
53
+ };
54
+ const zone = useRef(null);
55
+ const refs = useMemo(() => mergeRefs(ref, zone), [ref]);
56
+ const lean = (x, y) => {
57
+ const el = zone.current;
58
+ if (!el) return;
59
+ el.style.setProperty("--drop-x", `${x / Math.max(1, el.offsetWidth) * 100}%`);
60
+ el.style.setProperty("--drop-y", `${y / Math.max(1, el.offsetHeight) * 100}%`);
61
+ };
62
+ return /* @__PURE__ */ jsx(DropZone, {
63
+ ref: refs,
64
+ "aria-label": label,
65
+ "data-slot": "file-drop-zone",
66
+ isDisabled,
67
+ className: cn(fileDropZoneVariants(), className),
68
+ onDropEnter: (event) => lean(event.x, event.y),
69
+ onDropMove: (event) => lean(event.x, event.y),
70
+ onDrop: (event) => {
71
+ filesOf(event).then(hand);
72
+ },
73
+ children: ({ isDropTarget }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
74
+ /* @__PURE__ */ jsx("span", {
75
+ "aria-hidden": "true",
76
+ "data-slot": "file-drop-zone-wash",
77
+ className: "drop-wash"
78
+ }),
79
+ /* @__PURE__ */ jsx("span", {
80
+ "aria-hidden": "true",
81
+ "data-slot": "file-drop-zone-glow",
82
+ className: "drop-glow"
83
+ }),
84
+ /* @__PURE__ */ jsx("p", {
85
+ "data-slot": "file-drop-zone-prompt",
86
+ className: cn(fileDropZonePromptVariants(), "relative"),
87
+ children: dropPrompt == null ? prompt : /* @__PURE__ */ jsx(SwapText, {
88
+ swapKey: isDropTarget ? "drop" : "prompt",
89
+ className: "block",
90
+ children: isDropTarget ? dropPrompt : prompt
91
+ })
92
+ }),
93
+ /* @__PURE__ */ jsx(FileTrigger, {
94
+ acceptedFileTypes,
95
+ allowsMultiple,
96
+ onSelect: (list) => hand(Array.from(list ?? [])),
97
+ children: /* @__PURE__ */ jsx(Button$1, {
98
+ variant: "outlineNeutral",
99
+ size: "sm",
100
+ isDisabled,
101
+ className: "relative",
102
+ children: buttonLabel ?? (allowsMultiple ? "Choose files" : "Choose file")
103
+ })
104
+ }),
105
+ hint == null ? null : /* @__PURE__ */ jsx("p", {
106
+ "data-slot": "file-drop-zone-hint",
107
+ className: cn(fileDropZoneHintVariants(), "relative"),
108
+ children: hint
109
+ })
110
+ ] })
111
+ });
112
+ });
113
+ //#endregion
114
+ export { FileDropZone, fileDropZoneHintVariants, fileDropZonePromptVariants, fileDropZoneVariants };
115
+
116
+ //# sourceMappingURL=file-drop-zone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-drop-zone.js","names":["AriaDropZone","AriaFileTrigger","Button"],"sources":["../../src/file-drop-zone/file-drop-zone.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, useMemo, useRef, type ReactNode } from \"react\";\nimport {\n DropZone as AriaDropZone,\n FileTrigger as AriaFileTrigger,\n type DropZoneProps as AriaDropZoneProps,\n type FileDropItem,\n} from \"react-aria-components\";\n\nimport { Button } from \"../button/button.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * FileDropZone: a dashed area a file can be dropped on, with a \"Choose file\"\n * button that opens the system's file dialog.\n *\n * Built on React Aria's `DropZone` and `FileTrigger`. The browser's own\n * `<input type=\"file\">` (\"Choose Files / No file chosen\") is never shown: its\n * look differs on every browser and it cannot be styled. FileTrigger keeps a\n * real file input, visually hidden and out of the Tab order, and opens it when\n * the button is pressed, so the keyboard path is Tab to the button, then Enter\n * or Space.\n *\n * The zone states what it takes before a file is chosen: `hint` is where the\n * accepted types and the size limit go.\n *\n * Every string in the cva blocks below is a STATIC literal. Tailwind scans\n * source text, so a class assembled at runtime is invisible to it and would\n * simply never be emitted.\n */\nexport const fileDropZoneVariants = cva([\n \"flex flex-col items-center gap-2 px-4 py-8 text-center\",\n \"rounded-md border-(length:--border-width) border-dashed border-border\",\n // The reset in @seekalfred/styles is opt-in, so a consumer may load none.\n \"box-border font-sans text-foreground\",\n \"outline-none\",\n // Fill and edge ease, a file over it breathes and light follows the pointer (see drop-zone-motion).\n \"drop-zone-motion\",\n \"data-[hovered]:bg-muted\",\n \"data-[drop-target]:border-primary data-[drop-target]:bg-muted\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n // Disabled dims only the button (its own disabled look). Dimming the whole\n // zone took the prompt and hint below the 4.5:1 contrast floor.\n \"data-[disabled]:cursor-not-allowed\",\n]);\n\nexport const fileDropZonePromptVariants = cva(\"m-0 text-sm leading-(--leading-normal)\");\n\nexport const fileDropZoneHintVariants = cva(\n \"m-0 text-xs leading-(--leading-normal) text-muted-foreground\",\n);\n\n/**\n * A place to drop or choose files.\n *\n * @example\n * <FileDropZone\n * label=\"Contract\"\n * prompt=\"Drop a contract here\"\n * hint=\"PDF only, up to 20 MB.\"\n * acceptedFileTypes={[\"application/pdf\"]}\n * onFiles={(files) => upload(files[0])}\n * />\n */\nexport interface FileDropZoneProps {\n /** What the file is. The drop zone's accessible name. */\n label: string;\n /** The zone's main line: what to drop here, or the chosen file's name. */\n prompt: ReactNode;\n /**\n * What the prompt says while a file is over the zone. When it is given the\n * prompt swaps to it (the new text rises in, the old lifts away) and back again.\n */\n dropPrompt?: ReactNode;\n /** The types and size accepted, and anything else to know before choosing. */\n hint?: ReactNode;\n /**\n * MIME types or extensions the file dialog offers, as for a native\n * `accept`. A drop is not filtered: what was dropped is handed over and the\n * consumer decides what it can read.\n */\n acceptedFileTypes?: string[];\n /** Take several files at once. Otherwise only the first is handed over. */\n allowsMultiple?: boolean;\n /** Called with the chosen or dropped files. */\n onFiles: (files: File[]) => void;\n /**\n * The button's text.\n *\n * @default \"Choose file\", or \"Choose files\" with `allowsMultiple`\n */\n buttonLabel?: string;\n /** Take no file: the button is disabled and a drop is refused. */\n isDisabled?: boolean;\n /** Extra classes for the zone. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\ntype DropEvent = Parameters<NonNullable<AriaDropZoneProps[\"onDrop\"]>>[0];\n\nasync function filesOf(event: DropEvent): Promise<File[]> {\n const items = event.items.filter((item): item is FileDropItem => item.kind === \"file\");\n return Promise.all(items.map((item) => item.getFile()));\n}\n\nexport const FileDropZone = forwardRef<HTMLDivElement, FileDropZoneProps>(function FileDropZone(\n {\n label,\n prompt,\n dropPrompt,\n hint,\n acceptedFileTypes,\n allowsMultiple = false,\n onFiles,\n buttonLabel,\n isDisabled = false,\n className,\n },\n ref,\n) {\n const hand = (files: File[]) => {\n if (isDisabled || files.length === 0) return;\n onFiles(allowsMultiple ? files : files.slice(0, 1));\n };\n const zone = useRef<HTMLDivElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, zone), [ref]);\n // Where the file is over the zone, as percentages, for the light that follows it.\n const lean = (x: number, y: number) => {\n const el = zone.current;\n if (!el) return;\n el.style.setProperty(\"--drop-x\", `${(x / Math.max(1, el.offsetWidth)) * 100}%`);\n el.style.setProperty(\"--drop-y\", `${(y / Math.max(1, el.offsetHeight)) * 100}%`);\n };\n return (\n <AriaDropZone\n ref={refs}\n aria-label={label}\n data-slot=\"file-drop-zone\"\n isDisabled={isDisabled}\n className={cn(fileDropZoneVariants(), className)}\n onDropEnter={(event) => lean(event.x, event.y)}\n onDropMove={(event) => lean(event.x, event.y)}\n onDrop={(event) => {\n void filesOf(event).then(hand);\n }}\n >\n {({ isDropTarget }) => (\n <>\n {/* The wash and the edge light sit under the content and follow the file. */}\n <span aria-hidden=\"true\" data-slot=\"file-drop-zone-wash\" className=\"drop-wash\" />\n <span aria-hidden=\"true\" data-slot=\"file-drop-zone-glow\" className=\"drop-glow\" />\n <p data-slot=\"file-drop-zone-prompt\" className={cn(fileDropZonePromptVariants(), \"relative\")}>\n {dropPrompt == null ? (\n prompt\n ) : (\n <SwapText swapKey={isDropTarget ? \"drop\" : \"prompt\"} className=\"block\">\n {isDropTarget ? dropPrompt : prompt}\n </SwapText>\n )}\n </p>\n <AriaFileTrigger\n acceptedFileTypes={acceptedFileTypes}\n allowsMultiple={allowsMultiple}\n onSelect={(list) => hand(Array.from(list ?? []))}\n >\n <Button variant=\"outlineNeutral\" size=\"sm\" isDisabled={isDisabled} className=\"relative\">\n {buttonLabel ?? (allowsMultiple ? \"Choose files\" : \"Choose file\")}\n </Button>\n </AriaFileTrigger>\n {hint == null ? null : (\n <p\n data-slot=\"file-drop-zone-hint\"\n className={cn(fileDropZoneHintVariants(), \"relative\")}\n >\n {hint}\n </p>\n )}\n </>\n )}\n </AriaDropZone>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,uBAAuB,IAAI;CACtC;CACA;CAEA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;AACF,CAAC;AAED,MAAa,6BAA6B,IAAI,wCAAwC;AAEtF,MAAa,2BAA2B,IACtC,8DACF;AAkDA,eAAe,QAAQ,OAAmC;CACxD,MAAM,QAAQ,MAAM,MAAM,QAAQ,SAA+B,KAAK,SAAS,MAAM;CACrF,OAAO,QAAQ,IAAI,MAAM,KAAK,SAAS,KAAK,QAAQ,CAAC,CAAC;AACxD;AAEA,MAAa,eAAe,WAA8C,SAAS,aACjF,EACE,OACA,QACA,YACA,MACA,mBACA,iBAAiB,OACjB,SACA,aACA,aAAa,OACb,aAEF,KACA;CACA,MAAM,QAAQ,UAAkB;EAC9B,IAAI,cAAc,MAAM,WAAW,GAAG;EACtC,QAAQ,iBAAiB,QAAQ,MAAM,MAAM,GAAG,CAAC,CAAC;CACpD;CACA,MAAM,OAAO,OAA8B,IAAI;CAC/C,MAAM,OAAO,cAAc,UAAU,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC;CAEtD,MAAM,QAAQ,GAAW,MAAc;EACrC,MAAM,KAAK,KAAK;EAChB,IAAI,CAAC,IAAI;EACT,GAAG,MAAM,YAAY,YAAY,GAAI,IAAI,KAAK,IAAI,GAAG,GAAG,WAAW,IAAK,IAAI,EAAE;EAC9E,GAAG,MAAM,YAAY,YAAY,GAAI,IAAI,KAAK,IAAI,GAAG,GAAG,YAAY,IAAK,IAAI,EAAE;CACjF;CACA,OACE,oBAACA,UAAD;EACE,KAAK;EACL,cAAY;EACZ,aAAU;EACE;EACZ,WAAW,GAAG,qBAAqB,GAAG,SAAS;EAC/C,cAAc,UAAU,KAAK,MAAM,GAAG,MAAM,CAAC;EAC7C,aAAa,UAAU,KAAK,MAAM,GAAG,MAAM,CAAC;EAC5C,SAAS,UAAU;GACjB,QAAa,KAAK,CAAC,CAAC,KAAK,IAAI;EAC/B;EAEE,WAAA,EAAE,mBACF,qBAAA,YAAA,EAAA,UAAA;GAEE,oBAAC,QAAD;IAAM,eAAY;IAAO,aAAU;IAAsB,WAAU;GAAa,CAAA;GAChF,oBAAC,QAAD;IAAM,eAAY;IAAO,aAAU;IAAsB,WAAU;GAAa,CAAA;GAChF,oBAAC,KAAD;IAAG,aAAU;IAAwB,WAAW,GAAG,2BAA2B,GAAG,UAAU;IACxF,UAAA,cAAc,OACb,SAEA,oBAAC,UAAD;KAAU,SAAS,eAAe,SAAS;KAAU,WAAU;KAC5D,UAAA,eAAe,aAAa;IACrB,CAAA;GAEX,CAAA;GACH,oBAACC,aAAD;IACqB;IACH;IAChB,WAAW,SAAS,KAAK,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC;IAE/C,UAAA,oBAACC,UAAD;KAAQ,SAAQ;KAAiB,MAAK;KAAiB;KAAY,WAAU;KAC1E,UAAA,gBAAgB,iBAAiB,iBAAiB;IAC7C,CAAA;GACO,CAAA;GAChB,QAAQ,OAAO,OACd,oBAAC,KAAD;IACE,aAAU;IACV,WAAW,GAAG,yBAAyB,GAAG,UAAU;IAEnD,UAAA;GACA,CAAA;EAEL,EAAA,CAAA;CAEQ,CAAA;AAElB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { FileDropZone, FileDropZoneProps, fileDropZoneHintVariants, fileDropZonePromptVariants, fileDropZoneVariants } from "./file-drop-zone.js";
2
+ export { FileDropZone, type FileDropZoneProps, fileDropZoneHintVariants, fileDropZonePromptVariants, fileDropZoneVariants };
@@ -0,0 +1,2 @@
1
+ import { FileDropZone, fileDropZoneHintVariants, fileDropZonePromptVariants, fileDropZoneVariants } from "./file-drop-zone.js";
2
+ export { FileDropZone, fileDropZoneHintVariants, fileDropZonePromptVariants, fileDropZoneVariants };
@@ -0,0 +1,121 @@
1
+ import { ReactNode } from "react";
2
+ import { Key } from "react-aria-components";
3
+ //#region src/filter-bar/filter-bar.d.ts
4
+ /**
5
+ * FilterBar -- a list toolbar that keeps the controls people use every time
6
+ * in the row and puts every other dropdown filter behind ONE "Filters"
7
+ * button.
8
+ *
9
+ * Why it exists: list screens grew a row of four to seven dropdowns beside
10
+ * the search box and the status tabs. At desktop width that row wraps onto a
11
+ * second line; on a phone it scrolls sideways. The bar keeps the row to one
12
+ * line and makes what IS filtered visible as chips instead of as the
13
+ * resting text of a dropdown.
14
+ *
15
+ * Three regions:
16
+ *
17
+ * - `inline`: always in the row, at its start (status tabs, the search box).
18
+ * - the Filters button: a popover with a one-column form, one full-width
19
+ * Select per filter with its label above. The button carries a count badge
20
+ * of the filters that are set.
21
+ * - `end`: always in the row, at its end (a count, Export, a view toggle).
22
+ *
23
+ * While any filter is set, a list of chips sits under the row, each reading
24
+ * "Label: option label" with its own remove button, plus "Clear all".
25
+ *
26
+ * The bar holds NO filter state. Every value and every change belongs to the
27
+ * caller: removing a chip hands the filter's `emptyValue` back to its
28
+ * `onChange`; Clear all does that for each set filter, or calls
29
+ * `onClearAll` when the caller resets its filters in one step.
30
+ *
31
+ * Accessibility comes from the parts: the popover is a dialog named by its
32
+ * trigger, the trigger carries `aria-expanded`, focus moves into the popover
33
+ * and is contained there, Escape closes it and focus returns to the trigger.
34
+ */
35
+ export declare const filterBarVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
36
+ export declare const filterBarRowVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
37
+ export declare const filterBarChipVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
38
+ /** One option of a dropdown filter. */
39
+ export interface FilterBarOption {
40
+ /** The option's value, handed to `onChange`. */
41
+ id: Key;
42
+ /** The option's text: in the list, on the trigger and on the chip. */
43
+ label: string;
44
+ }
45
+ /** A dropdown filter, rendered as a Select in the popover. */
46
+ export interface FilterBarSelectField {
47
+ /** Stable identity, used as the React key. */
48
+ id: string;
49
+ /** The field's name: the label above the dropdown and the start of its chip. */
50
+ label: string;
51
+ /** The current value. `null` reads as not set. */
52
+ value: Key | null;
53
+ /** The value that means "not filtered" (the "All" option's id). */
54
+ emptyValue: Key;
55
+ /** The options, the "All" option included. */
56
+ options: readonly FilterBarOption[];
57
+ /** Told the chosen option's id, or `emptyValue` when the chip is removed. */
58
+ onChange: (value: Key) => void;
59
+ }
60
+ /**
61
+ * A filter that is not a plain dropdown (a date range, a searchable picker):
62
+ * the caller renders the control and says what its chip reads.
63
+ */
64
+ export interface FilterBarCustomField {
65
+ /** Stable identity, used as the React key. */
66
+ id: string;
67
+ /** The start of its chip. The control rendered by `render` names itself. */
68
+ label: string;
69
+ /** The control, rendered in the popover. */
70
+ render: () => ReactNode;
71
+ /** The chip's value text while the filter is set, or `null` while it is not. */
72
+ activeLabel: string | null;
73
+ /** Unsets the filter: the chip's remove button and Clear all call it. */
74
+ onClear: () => void;
75
+ }
76
+ export type FilterBarField = FilterBarSelectField | FilterBarCustomField;
77
+ export interface FilterBarProps {
78
+ /** Always in the row, at its start: status tabs and the search box. */
79
+ inline?: ReactNode;
80
+ /** Always in the row, at its end: a count, Export, a view toggle. */
81
+ end?: ReactNode;
82
+ /** The filters behind the Filters button. With none, no button renders. */
83
+ filters: readonly FilterBarField[];
84
+ /** Resets every filter in one step. Without it, each set filter is handed its `emptyValue`. */
85
+ onClearAll?: () => void;
86
+ /**
87
+ * The button's text, and the popover's name.
88
+ *
89
+ * @default "Filters"
90
+ */
91
+ buttonLabel?: string;
92
+ /**
93
+ * The text of both Clear all buttons.
94
+ *
95
+ * @default "Clear all"
96
+ */
97
+ clearAllLabel?: string;
98
+ /**
99
+ * The text of the button that closes the popover.
100
+ *
101
+ * @default "Done"
102
+ */
103
+ doneLabel?: string;
104
+ /**
105
+ * The accessible name of the list of chips.
106
+ *
107
+ * @default "Filters set"
108
+ */
109
+ chipsLabel?: string;
110
+ /**
111
+ * The accessible name of a chip's remove button, given the chip's text.
112
+ *
113
+ * @default (text) => `Remove filter ${text}`
114
+ */
115
+ removeLabel?: (chipText: string) => string;
116
+ /** Extra classes on the root. Merged last, so they win conflicts with ours. */
117
+ className?: string;
118
+ }
119
+ export declare const FilterBar: import("react").ForwardRefExoticComponent<FilterBarProps & import("react").RefAttributes<HTMLDivElement>>;
120
+ //#endregion
121
+ //# sourceMappingURL=filter-bar.d.ts.map