@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,340 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect, useLeavingItems } from "../lib/motion.js";
4
+ import { Tooltip as Tooltip$1, TooltipTrigger as TooltipTrigger$1 } from "../tooltip/tooltip.js";
5
+ import { Fragment, useCallback, useEffect, useRef, useState } from "react";
6
+ import { Focusable } from "react-aria-components";
7
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
+ //#region src/floating-button-group/floating-button-group.tsx
9
+ const isAction = (item) => item.type !== "separator";
10
+ const PHASE = "col-start-1 row-start-1 inline-flex items-center gap-1.5 whitespace-nowrap";
11
+ function LeavingPhase({ id, onDone, children }) {
12
+ const remove = useCallback(() => onDone(id), [onDone, id]);
13
+ useExitBackstop(true, remove);
14
+ return /* @__PURE__ */ jsx("span", {
15
+ "aria-hidden": "true",
16
+ onAnimationEnd: (event) => {
17
+ if (event.target === event.currentTarget) remove();
18
+ },
19
+ className: cn(PHASE + " motion-safe:animate-label-out motion-reduce:animate-fade-out"),
20
+ children
21
+ });
22
+ }
23
+ /**
24
+ * Icon and label. Every label the item can show is laid out invisibly in the
25
+ * same grid cell, so the button always has the width of the widest one: a label
26
+ * change crossfades in place and never resizes the button, its neighbours or the
27
+ * tray.
28
+ */
29
+ function ItemBody({ label, icon, iconOnly, reserve }) {
30
+ const phaseRef = useRef(null);
31
+ const node = /* @__PURE__ */ jsxs(Fragment$1, { children: [icon ? /* @__PURE__ */ jsx("span", {
32
+ className: "inline-flex flex-none [&_svg]:size-(--icon-sm)",
33
+ children: icon
34
+ }) : null, iconOnly ? null : /* @__PURE__ */ jsx("span", {
35
+ className: "block",
36
+ children: label
37
+ })] });
38
+ const { leaving, changed, remove } = useLeavingItems(label, node, () => phaseRef.current);
39
+ const ghosts = iconOnly ? [] : Array.from(/* @__PURE__ */ new Set([label, ...reserve]));
40
+ return /* @__PURE__ */ jsxs("span", {
41
+ className: "relative z-2 inline-grid items-center justify-items-center group-aria-[orientation=vertical]/fg:justify-items-start",
42
+ children: [
43
+ ghosts.map((text) => /* @__PURE__ */ jsxs("span", {
44
+ "aria-hidden": "true",
45
+ className: cn(PHASE + " pointer-events-none invisible"),
46
+ children: [icon ? /* @__PURE__ */ jsx("span", { className: "block size-4 flex-none" }) : null, /* @__PURE__ */ jsx("span", {
47
+ className: "block",
48
+ children: text
49
+ })]
50
+ }, text)),
51
+ leaving.map((entry) => /* @__PURE__ */ jsx(LeavingPhase, {
52
+ id: entry.id,
53
+ onDone: remove,
54
+ children: entry.value
55
+ }, entry.id)),
56
+ /* @__PURE__ */ jsx("span", {
57
+ ref: phaseRef,
58
+ className: cn(PHASE + (changed ? " motion-safe:animate-label-in motion-reduce:animate-fade-in" : "")),
59
+ children: node
60
+ }, label)
61
+ ]
62
+ });
63
+ }
64
+ function TipContent({ label, shortcut }) {
65
+ return /* @__PURE__ */ jsxs("span", {
66
+ className: "inline-flex items-baseline gap-3 whitespace-nowrap",
67
+ children: [label, shortcut ? /* @__PURE__ */ jsx("kbd", {
68
+ className: "font-[inherit] tabular-nums opacity-70",
69
+ children: shortcut
70
+ }) : null]
71
+ });
72
+ }
73
+ const MORPH = "translate var(--duration-morph) var(--ease-morph), width var(--duration-morph) var(--ease-morph), height var(--duration-morph) var(--ease-morph)";
74
+ const SCALE = "scale var(--duration-snappy) var(--ease-snappy)";
75
+ const BACKGROUND = "background-color var(--duration-quick) var(--ease-settle)";
76
+ const FADE_IN = "opacity var(--duration-quick) var(--ease-settle)";
77
+ const FADE_OUT = "opacity var(--duration-standard) var(--ease-settle)";
78
+ const FADE_INSTANT = "opacity var(--duration-instant) var(--ease-settle)";
79
+ /**
80
+ * Separate soft buttons in a quiet tray. One shared highlight morphs, in
81
+ * position and size, to whichever button the pointer or keyboard focus is on,
82
+ * fades when the pointer leaves the tray, and settles darker in place while a
83
+ * button is pressed. It is the toolbar's main hover and focus indicator. Arrow
84
+ * keys move focus, Home and End jump to the ends, Enter and Space act.
85
+ *
86
+ * @example
87
+ * <FloatingButtonGroup label="Board actions" items={[{ id: "undo", label: "Undo", icon: <UndoIcon aria-hidden />, iconOnly: true, shortcut: "⌘Z" }]} />
88
+ */
89
+ function FloatingButtonGroup({ items, label, variant = "muted", size = "md", orientation = "horizontal", iconOnly: groupIconOnly = false, tooltipSide, onAction, className }) {
90
+ const tray = useRef(null);
91
+ const highlight = useRef(null);
92
+ const nodes = useRef(/* @__PURE__ */ new Map());
93
+ const [hoverId, setHoverId] = useState(null);
94
+ const [focusId, setFocusId] = useState(null);
95
+ const [pressing, setPressing] = useState(false);
96
+ const [tabStop, setTabStop] = useState(null);
97
+ const actions = items.filter(isAction);
98
+ const byId = new Map(actions.map((action) => [action.id, action]));
99
+ const enabled = (id) => !!id && !!byId.get(id) && !byId.get(id)?.isDisabled;
100
+ const activeId = enabled(hoverId) ? hoverId : focusId && byId.has(focusId) ? focusId : null;
101
+ const stop = enabled(tabStop) ? tabStop : actions.find((action) => !action.isDisabled)?.id ?? null;
102
+ const vertical = orientation === "vertical";
103
+ const side = tooltipSide ?? (vertical ? "right" : "top");
104
+ const idsKey = actions.map((action) => action.id).join("\0");
105
+ const visible = useRef(false);
106
+ const traveling = useRef(false);
107
+ const travelTimer = useRef(void 0);
108
+ const activeRef = useRef(activeId);
109
+ const pressingRef = useRef(pressing);
110
+ /** The button's box in tray coordinates, exact to the device pixel (offsetLeft rounds to whole pixels) and free of ancestor transforms. */
111
+ const measure = useCallback((node) => {
112
+ const root = tray.current;
113
+ if (!root) return null;
114
+ const outer = root.getBoundingClientRect();
115
+ const box = node.getBoundingClientRect();
116
+ const unscale = outer.width ? root.offsetWidth / outer.width : 1;
117
+ const dpr = window.devicePixelRatio || 1;
118
+ const snap = (value) => Math.round(value * unscale * dpr) / dpr;
119
+ return {
120
+ x: snap(box.left - outer.left) - root.clientLeft,
121
+ y: snap(box.top - outer.top) - root.clientTop,
122
+ width: snap(box.width),
123
+ height: snap(box.height)
124
+ };
125
+ }, []);
126
+ /** Moves the highlight to a button. "move" travels on the morph spring; "follow" keeps up with a layout change without travelling. */
127
+ const place = useCallback((id, mode) => {
128
+ const el = highlight.current;
129
+ if (!el) return;
130
+ const reduced = prefersReducedMotion();
131
+ const node = id ? nodes.current.get(id) : void 0;
132
+ if (!node) {
133
+ el.style.transition = [
134
+ BACKGROUND,
135
+ SCALE,
136
+ reduced ? FADE_INSTANT : FADE_OUT
137
+ ].join(", ");
138
+ el.style.opacity = "0";
139
+ visible.current = false;
140
+ return;
141
+ }
142
+ const rect = measure(node);
143
+ if (!rect) return;
144
+ const fade = reduced ? FADE_INSTANT : FADE_IN;
145
+ if (!reduced && visible.current && (mode === "move" || traveling.current)) {
146
+ traveling.current = true;
147
+ clearTimeout(travelTimer.current);
148
+ travelTimer.current = setTimeout(() => {
149
+ traveling.current = false;
150
+ }, 740);
151
+ el.style.transition = [
152
+ MORPH,
153
+ SCALE,
154
+ fade,
155
+ BACKGROUND
156
+ ].join(", ");
157
+ } else {
158
+ traveling.current = false;
159
+ el.style.transition = [
160
+ SCALE,
161
+ fade,
162
+ BACKGROUND
163
+ ].join(", ");
164
+ if (!visible.current && !reduced) {
165
+ el.style.transition = "none";
166
+ el.style.scale = "0.94";
167
+ el.offsetWidth;
168
+ el.style.transition = [
169
+ SCALE,
170
+ fade,
171
+ BACKGROUND
172
+ ].join(", ");
173
+ el.style.scale = pressingRef.current ? "0.965" : "1";
174
+ }
175
+ }
176
+ el.style.translate = rect.x + "px " + rect.y + "px";
177
+ el.style.width = rect.width + "px";
178
+ el.style.height = rect.height + "px";
179
+ el.style.opacity = "1";
180
+ visible.current = true;
181
+ }, [measure]);
182
+ useIsomorphicLayoutEffect(() => {
183
+ activeRef.current = activeId;
184
+ place(activeId, "move");
185
+ }, [activeId, place]);
186
+ useEffect(() => {
187
+ pressingRef.current = pressing;
188
+ const el = highlight.current;
189
+ if (!el) return;
190
+ el.style.scale = pressing && activeId ? "0.965" : "1";
191
+ }, [activeId, pressing]);
192
+ useEffect(() => {
193
+ const root = tray.current;
194
+ if (!root || typeof ResizeObserver === "undefined") return;
195
+ let frame = 0;
196
+ const observer = new ResizeObserver(() => {
197
+ cancelAnimationFrame(frame);
198
+ frame = requestAnimationFrame(() => {
199
+ if (activeRef.current) place(activeRef.current, "follow");
200
+ });
201
+ });
202
+ observer.observe(root);
203
+ nodes.current.forEach((node) => observer.observe(node));
204
+ return () => {
205
+ cancelAnimationFrame(frame);
206
+ observer.disconnect();
207
+ };
208
+ }, [idsKey, place]);
209
+ useEffect(() => {
210
+ const root = tray.current;
211
+ if (!root) return;
212
+ const leave = () => {
213
+ setHoverId(null);
214
+ setPressing(false);
215
+ };
216
+ root.addEventListener("pointerleave", leave);
217
+ return () => root.removeEventListener("pointerleave", leave);
218
+ }, []);
219
+ useEffect(() => () => clearTimeout(travelTimer.current), []);
220
+ const buttons = () => Array.from(tray.current?.querySelectorAll("[data-fg-item]") ?? []);
221
+ const onKeyDown = (event) => {
222
+ const all = buttons();
223
+ const index = all.findIndex((node) => node === document.activeElement);
224
+ if (index < 0) return;
225
+ const next = vertical ? "ArrowDown" : "ArrowRight";
226
+ const previous = vertical ? "ArrowUp" : "ArrowLeft";
227
+ const usable = (node) => node.getAttribute("aria-disabled") !== "true";
228
+ let target;
229
+ if (event.key === "Home") target = all.find(usable);
230
+ else if (event.key === "End") target = [...all].reverse().find(usable);
231
+ else if (event.key === next || event.key === previous) {
232
+ const step = event.key === next ? 1 : -1;
233
+ for (let offset = 1; offset <= all.length; offset += 1) {
234
+ const candidate = all[(index + step * offset + all.length * offset) % all.length];
235
+ if (usable(candidate)) {
236
+ target = candidate;
237
+ break;
238
+ }
239
+ }
240
+ } else return;
241
+ event.preventDefault();
242
+ if (!target) return;
243
+ setHoverId(null);
244
+ target.focus();
245
+ };
246
+ const onBlur = (event) => {
247
+ if (tray.current?.contains(event.relatedTarget)) return;
248
+ setFocusId(null);
249
+ setPressing(false);
250
+ };
251
+ return /* @__PURE__ */ jsxs("div", {
252
+ ref: tray,
253
+ role: "toolbar",
254
+ "aria-label": label,
255
+ "aria-orientation": orientation,
256
+ "data-variant": variant,
257
+ "data-size": size,
258
+ onKeyDown,
259
+ onBlur,
260
+ className: cn("floating-group group/fg", className),
261
+ children: [/* @__PURE__ */ jsx("span", {
262
+ ref: highlight,
263
+ "aria-hidden": "true",
264
+ "data-pressing": pressing && activeId ? "" : void 0,
265
+ className: "floating-group-highlight"
266
+ }), items.map((item, position) => {
267
+ if (!isAction(item)) return /* @__PURE__ */ jsx("span", {
268
+ role: "separator",
269
+ "aria-orientation": vertical ? "horizontal" : "vertical",
270
+ className: "floating-group-separator"
271
+ }, item.id ?? "separator-" + position);
272
+ const { id, label: itemLabel, icon, onSelect, isDisabled = false, isPressed, shortcut, reserveLabels = [] } = item;
273
+ const iconOnly = !!icon && (item.iconOnly ?? groupIconOnly);
274
+ const button = /* @__PURE__ */ jsx("button", {
275
+ ref: (node) => {
276
+ if (node) nodes.current.set(id, node);
277
+ else nodes.current.delete(id);
278
+ },
279
+ type: "button",
280
+ "data-fg-item": "",
281
+ "data-active": activeId === id ? "" : void 0,
282
+ "data-icon": icon ? "" : void 0,
283
+ "data-icon-only": iconOnly ? "" : void 0,
284
+ "aria-label": itemLabel,
285
+ "aria-pressed": isPressed === void 0 ? void 0 : isPressed,
286
+ "aria-disabled": isDisabled || void 0,
287
+ tabIndex: stop === id ? 0 : -1,
288
+ onPointerMove: (event) => {
289
+ if (event.pointerType === "mouse" || event.pointerType === "pen") setHoverId(id);
290
+ },
291
+ onPointerDown: (event) => {
292
+ if (isDisabled || event.button !== 0) return;
293
+ setHoverId(id);
294
+ setPressing(true);
295
+ },
296
+ onPointerUp: () => setPressing(false),
297
+ onPointerCancel: () => setPressing(false),
298
+ onPointerLeave: () => setPressing(false),
299
+ onKeyDown: (event) => {
300
+ if ((event.key === "Enter" || event.key === " ") && !event.repeat && !isDisabled) setPressing(true);
301
+ },
302
+ onKeyUp: (event) => {
303
+ if (event.key === "Enter" || event.key === " ") setPressing(false);
304
+ },
305
+ onFocus: (event) => {
306
+ setTabStop(id);
307
+ let keyboard = true;
308
+ try {
309
+ keyboard = event.currentTarget.matches(":focus-visible");
310
+ } catch {}
311
+ setFocusId(keyboard ? id : null);
312
+ },
313
+ onClick: () => {
314
+ if (isDisabled) return;
315
+ onSelect?.();
316
+ onAction?.(id);
317
+ },
318
+ className: cn("floating-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)"),
319
+ children: /* @__PURE__ */ jsx(ItemBody, {
320
+ label: itemLabel,
321
+ icon,
322
+ iconOnly,
323
+ reserve: reserveLabels
324
+ })
325
+ });
326
+ if (!iconOnly && !shortcut) return /* @__PURE__ */ jsx(Fragment, { children: button }, id);
327
+ return /* @__PURE__ */ jsxs(TooltipTrigger$1, { children: [/* @__PURE__ */ jsx(Focusable, { children: button }), /* @__PURE__ */ jsx(Tooltip$1, {
328
+ placement: side,
329
+ children: /* @__PURE__ */ jsx(TipContent, {
330
+ label: itemLabel,
331
+ shortcut
332
+ })
333
+ })] }, id);
334
+ })]
335
+ });
336
+ }
337
+ //#endregion
338
+ export { FloatingButtonGroup };
339
+
340
+ //# sourceMappingURL=floating-button-group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floating-button-group.js","names":["TooltipTrigger","Tooltip"],"sources":["../../src/floating-button-group/floating-button-group.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n Fragment,\n useCallback,\n useEffect,\n useRef,\n useState,\n type FocusEvent,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport { Focusable } from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport {\n prefersReducedMotion,\n useExitBackstop,\n useIsomorphicLayoutEffect,\n useLeavingItems,\n} from \"../lib/motion.js\";\nimport { Tooltip, TooltipTrigger } from \"../tooltip/tooltip.js\";\n\nexport type FloatingButtonGroupVariant = \"muted\" | \"floating\";\nexport type FloatingButtonGroupSize = \"sm\" | \"md\";\nexport type FloatingButtonGroupOrientation = \"horizontal\" | \"vertical\";\n\nexport interface FloatingButtonGroupAction {\n type?: \"action\";\n /** Stable key, also passed to onAction. */\n id: string;\n /** Visible label, and the accessible name when the item shows only its icon. */\n label: string;\n /** An icon before the label. Mark it `aria-hidden`. */\n icon?: ReactNode;\n /** Shows only the icon. The label moves into a tooltip and the accessible name. Overrides the group's iconOnly. */\n iconOnly?: boolean;\n onSelect?: () => void;\n isDisabled?: boolean;\n /** Marks an action that switches a mode on, such as Present. Sets aria-pressed and keeps a quiet tint while on. */\n isPressed?: boolean;\n /** A shortcut hint shown in the tooltip, such as \"⌘Z\". The component does not bind the key. */\n shortcut?: string;\n /** Other labels this item can switch to, such as [\"Copied\"] for Share. The button keeps the width of the widest, so a label change never moves anything. */\n reserveLabels?: readonly string[];\n}\n\nexport interface FloatingButtonGroupSeparator {\n type: \"separator\";\n id?: string;\n}\n\nexport type FloatingButtonGroupItem = FloatingButtonGroupAction | FloatingButtonGroupSeparator;\n\nexport interface FloatingButtonGroupProps {\n items: readonly FloatingButtonGroupItem[];\n /** Accessible name of the toolbar, such as \"Board actions\". */\n label: string;\n /** `muted` sits quietly on a page; `floating` raises the tray with the floating shadow for overlays such as a selection toolbar. @default \"muted\" */\n variant?: FloatingButtonGroupVariant;\n /** @default \"md\" */\n size?: FloatingButtonGroupSize;\n /** @default \"horizontal\" */\n orientation?: FloatingButtonGroupOrientation;\n /** Shows every item as an icon only, with its label in a tooltip. Items with their own iconOnly win. */\n iconOnly?: boolean;\n /** Side of the item tooltips. Defaults to top for a row and right for a column. */\n tooltipSide?: \"top\" | \"bottom\" | \"left\" | \"right\";\n /** Called with the item id after the item's own onSelect. */\n onAction?: (id: string) => void;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nconst isAction = (item: FloatingButtonGroupItem): item is FloatingButtonGroupAction =>\n item.type !== \"separator\";\n\nconst PHASE = \"col-start-1 row-start-1 inline-flex items-center gap-1.5 whitespace-nowrap\";\n\nfunction LeavingPhase({\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={cn(PHASE + \" motion-safe:animate-label-out motion-reduce:animate-fade-out\")}\n >\n {children}\n </span>\n );\n}\n\n/**\n * Icon and label. Every label the item can show is laid out invisibly in the\n * same grid cell, so the button always has the width of the widest one: a label\n * change crossfades in place and never resizes the button, its neighbours or the\n * tray.\n */\nfunction ItemBody({\n label,\n icon,\n iconOnly,\n reserve,\n}: {\n label: string;\n icon?: ReactNode;\n iconOnly: boolean;\n reserve: readonly string[];\n}) {\n const phaseRef = useRef<HTMLSpanElement>(null);\n const node = (\n <>\n {icon ? <span className=\"inline-flex flex-none [&_svg]:size-(--icon-sm)\">{icon}</span> : null}\n {iconOnly ? null : <span className=\"block\">{label}</span>}\n </>\n );\n const { leaving, changed, remove } = useLeavingItems(label, node, () => phaseRef.current);\n const ghosts = iconOnly ? [] : Array.from(new Set([label, ...reserve]));\n return (\n <span className=\"relative z-2 inline-grid items-center justify-items-center group-aria-[orientation=vertical]/fg:justify-items-start\">\n {ghosts.map((text) => (\n <span\n key={text}\n aria-hidden=\"true\"\n className={cn(PHASE + \" pointer-events-none invisible\")}\n >\n {icon ? <span className=\"block size-4 flex-none\" /> : null}\n <span className=\"block\">{text}</span>\n </span>\n ))}\n {leaving.map((entry) => (\n <LeavingPhase key={entry.id} id={entry.id} onDone={remove}>\n {entry.value}\n </LeavingPhase>\n ))}\n <span\n key={label}\n ref={phaseRef}\n className={cn(\n PHASE + (changed ? \" motion-safe:animate-label-in motion-reduce:animate-fade-in\" : \"\"),\n )}\n >\n {node}\n </span>\n </span>\n );\n}\n\nfunction TipContent({ label, shortcut }: { label: string; shortcut?: string }) {\n return (\n <span className=\"inline-flex items-baseline gap-3 whitespace-nowrap\">\n {label}\n {shortcut ? <kbd className=\"font-[inherit] tabular-nums opacity-70\">{shortcut}</kbd> : null}\n </span>\n );\n}\n\nconst MORPH =\n \"translate var(--duration-morph) var(--ease-morph), width var(--duration-morph) var(--ease-morph), height var(--duration-morph) var(--ease-morph)\";\nconst SCALE = \"scale var(--duration-snappy) var(--ease-snappy)\";\nconst BACKGROUND = \"background-color var(--duration-quick) var(--ease-settle)\";\nconst FADE_IN = \"opacity var(--duration-quick) var(--ease-settle)\";\nconst FADE_OUT = \"opacity var(--duration-standard) var(--ease-settle)\";\nconst FADE_INSTANT = \"opacity var(--duration-instant) var(--ease-settle)\";\n\nconst focusRing =\n \"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/**\n * Separate soft buttons in a quiet tray. One shared highlight morphs, in\n * position and size, to whichever button the pointer or keyboard focus is on,\n * fades when the pointer leaves the tray, and settles darker in place while a\n * button is pressed. It is the toolbar's main hover and focus indicator. Arrow\n * keys move focus, Home and End jump to the ends, Enter and Space act.\n *\n * @example\n * <FloatingButtonGroup label=\"Board actions\" items={[{ id: \"undo\", label: \"Undo\", icon: <UndoIcon aria-hidden />, iconOnly: true, shortcut: \"⌘Z\" }]} />\n */\nexport function FloatingButtonGroup({\n items,\n label,\n variant = \"muted\",\n size = \"md\",\n orientation = \"horizontal\",\n iconOnly: groupIconOnly = false,\n tooltipSide,\n onAction,\n className,\n}: FloatingButtonGroupProps) {\n const tray = useRef<HTMLDivElement>(null);\n const highlight = useRef<HTMLSpanElement>(null);\n const nodes = useRef(new Map<string, HTMLButtonElement>());\n const [hoverId, setHoverId] = useState<string | null>(null);\n const [focusId, setFocusId] = useState<string | null>(null);\n const [pressing, setPressing] = useState(false);\n const [tabStop, setTabStop] = useState<string | null>(null);\n\n const actions = items.filter(isAction);\n const byId = new Map(actions.map((action) => [action.id, action]));\n const enabled = (id: string | null) => !!id && !!byId.get(id) && !byId.get(id)?.isDisabled;\n // Pointer wins over keyboard focus while it is in the tray; a disabled item under the pointer hands back to focus.\n const activeId = enabled(hoverId) ? hoverId : focusId && byId.has(focusId) ? focusId : null;\n const stop = enabled(tabStop)\n ? tabStop\n : (actions.find((action) => !action.isDisabled)?.id ?? null);\n const vertical = orientation === \"vertical\";\n const side = tooltipSide ?? (vertical ? \"right\" : \"top\");\n const idsKey = actions.map((action) => action.id).join(\"\\u0000\");\n\n const visible = useRef(false);\n const traveling = useRef(false);\n const travelTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const activeRef = useRef(activeId);\n const pressingRef = useRef(pressing);\n\n /** The button's box in tray coordinates, exact to the device pixel (offsetLeft rounds to whole pixels) and free of ancestor transforms. */\n const measure = useCallback((node: HTMLElement) => {\n const root = tray.current;\n if (!root) return null;\n const outer = root.getBoundingClientRect();\n const box = node.getBoundingClientRect();\n const unscale = outer.width ? root.offsetWidth / outer.width : 1;\n const dpr = window.devicePixelRatio || 1;\n const snap = (value: number) => Math.round(value * unscale * dpr) / dpr;\n return {\n x: snap(box.left - outer.left) - root.clientLeft,\n y: snap(box.top - outer.top) - root.clientTop,\n width: snap(box.width),\n height: snap(box.height),\n };\n }, []);\n\n /** Moves the highlight to a button. \"move\" travels on the morph spring; \"follow\" keeps up with a layout change without travelling. */\n const place = useCallback(\n (id: string | null, mode: \"move\" | \"follow\") => {\n const el = highlight.current;\n if (!el) return;\n const reduced = prefersReducedMotion();\n const node = id ? nodes.current.get(id) : undefined;\n if (!node) {\n el.style.transition = [BACKGROUND, SCALE, reduced ? FADE_INSTANT : FADE_OUT].join(\", \");\n el.style.opacity = \"0\";\n visible.current = false;\n return;\n }\n const rect = measure(node);\n if (!rect) return;\n const fade = reduced ? FADE_INSTANT : FADE_IN;\n // Travel between buttons on the morph spring. Appearing from nothing, and every move with reduced motion, lands in place.\n const travel = !reduced && visible.current && (mode === \"move\" || traveling.current);\n if (travel) {\n traveling.current = true;\n clearTimeout(travelTimer.current);\n travelTimer.current = setTimeout(() => {\n traveling.current = false;\n }, 740);\n el.style.transition = [MORPH, SCALE, fade, BACKGROUND].join(\", \");\n } else {\n traveling.current = false;\n el.style.transition = [SCALE, fade, BACKGROUND].join(\", \");\n if (!visible.current && !reduced) {\n // It grows into place as it appears.\n el.style.transition = \"none\";\n el.style.scale = \"0.94\";\n void el.offsetWidth;\n el.style.transition = [SCALE, fade, BACKGROUND].join(\", \");\n el.style.scale = pressingRef.current ? \"0.965\" : \"1\";\n }\n }\n el.style.translate = rect.x + \"px \" + rect.y + \"px\";\n el.style.width = rect.width + \"px\";\n el.style.height = rect.height + \"px\";\n el.style.opacity = \"1\";\n visible.current = true;\n },\n [measure],\n );\n\n useIsomorphicLayoutEffect(() => {\n activeRef.current = activeId;\n place(activeId, \"move\");\n }, [activeId, place]);\n\n useEffect(() => {\n pressingRef.current = pressing;\n const el = highlight.current;\n if (!el) return;\n el.style.scale = pressing && activeId ? \"0.965\" : \"1\";\n }, [activeId, pressing]);\n\n // A font that loads or a container that reflows moves the buttons; the highlight follows without travel.\n // The follow is deferred a frame, so moving it inside the notification cannot report a ResizeObserver loop.\n useEffect(() => {\n const root = tray.current;\n if (!root || typeof ResizeObserver === \"undefined\") return;\n let frame = 0;\n const observer = new ResizeObserver(() => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(() => {\n if (activeRef.current) place(activeRef.current, \"follow\");\n });\n });\n observer.observe(root);\n nodes.current.forEach((node) => observer.observe(node));\n return () => {\n cancelAnimationFrame(frame);\n observer.disconnect();\n };\n }, [idsKey, place]);\n\n // A native listener, because React bubbles pointer events through portals: moving from a button onto its tooltip\n // would not count as leaving the tray, and the highlight would stay behind.\n useEffect(() => {\n const root = tray.current;\n if (!root) return;\n const leave = () => {\n setHoverId(null);\n setPressing(false);\n };\n root.addEventListener(\"pointerleave\", leave);\n return () => root.removeEventListener(\"pointerleave\", leave);\n }, []);\n\n useEffect(() => () => clearTimeout(travelTimer.current), []);\n\n const buttons = () =>\n Array.from(tray.current?.querySelectorAll<HTMLButtonElement>(\"[data-fg-item]\") ?? []);\n\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n const all = buttons();\n const index = all.findIndex((node) => node === document.activeElement);\n if (index < 0) return;\n const next = vertical ? \"ArrowDown\" : \"ArrowRight\";\n const previous = vertical ? \"ArrowUp\" : \"ArrowLeft\";\n const usable = (node: HTMLButtonElement) => node.getAttribute(\"aria-disabled\") !== \"true\";\n let target: HTMLButtonElement | undefined;\n if (event.key === \"Home\") target = all.find(usable);\n else if (event.key === \"End\") target = [...all].reverse().find(usable);\n else if (event.key === next || event.key === previous) {\n const step = event.key === next ? 1 : -1;\n for (let offset = 1; offset <= all.length; offset += 1) {\n const candidate = all[(index + step * offset + all.length * offset) % all.length]!;\n if (usable(candidate)) {\n target = candidate;\n break;\n }\n }\n } else return;\n event.preventDefault();\n if (!target) return;\n // The keyboard takes the highlight back until the pointer moves again.\n setHoverId(null);\n target.focus();\n };\n\n const onBlur = (event: FocusEvent<HTMLDivElement>) => {\n if (tray.current?.contains(event.relatedTarget)) return;\n setFocusId(null);\n setPressing(false);\n };\n\n return (\n <div\n ref={tray}\n role=\"toolbar\"\n aria-label={label}\n aria-orientation={orientation}\n data-variant={variant}\n data-size={size}\n onKeyDown={onKeyDown}\n onBlur={onBlur}\n className={cn(\"floating-group group/fg\", className)}\n >\n <span\n ref={highlight}\n aria-hidden=\"true\"\n data-pressing={pressing && activeId ? \"\" : undefined}\n className=\"floating-group-highlight\"\n />\n {items.map((item, position) => {\n if (!isAction(item))\n return (\n <span\n key={item.id ?? \"separator-\" + position}\n role=\"separator\"\n aria-orientation={vertical ? \"horizontal\" : \"vertical\"}\n className=\"floating-group-separator\"\n />\n );\n const {\n id,\n label: itemLabel,\n icon,\n onSelect,\n isDisabled = false,\n isPressed,\n shortcut,\n reserveLabels = [],\n } = item;\n const iconOnly = !!icon && (item.iconOnly ?? groupIconOnly);\n const button = (\n <button\n ref={(node) => {\n if (node) nodes.current.set(id, node);\n else nodes.current.delete(id);\n }}\n type=\"button\"\n data-fg-item=\"\"\n data-active={activeId === id ? \"\" : undefined}\n data-icon={icon ? \"\" : undefined}\n data-icon-only={iconOnly ? \"\" : undefined}\n aria-label={itemLabel}\n aria-pressed={isPressed === undefined ? undefined : isPressed}\n aria-disabled={isDisabled || undefined}\n tabIndex={stop === id ? 0 : -1}\n onPointerMove={(event) => {\n if (event.pointerType === \"mouse\" || event.pointerType === \"pen\") setHoverId(id);\n }}\n onPointerDown={(event) => {\n if (isDisabled || event.button !== 0) return;\n setHoverId(id);\n setPressing(true);\n }}\n onPointerUp={() => setPressing(false)}\n onPointerCancel={() => setPressing(false)}\n onPointerLeave={() => setPressing(false)}\n onKeyDown={(event) => {\n if ((event.key === \"Enter\" || event.key === \" \") && !event.repeat && !isDisabled)\n setPressing(true);\n }}\n onKeyUp={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") setPressing(false);\n }}\n onFocus={(event) => {\n setTabStop(id);\n let keyboard = true;\n try {\n keyboard = event.currentTarget.matches(\":focus-visible\");\n } catch {\n /* older engines */\n }\n setFocusId(keyboard ? id : null);\n }}\n onClick={() => {\n if (isDisabled) return;\n onSelect?.();\n onAction?.(id);\n }}\n className={cn(\"floating-group-item \" + focusRing)}\n >\n <ItemBody label={itemLabel} icon={icon} iconOnly={iconOnly} reserve={reserveLabels} />\n </button>\n );\n // Tooltips render no wrapper element, so every button stays a direct child of the tray and measures against it.\n if (!iconOnly && !shortcut) return <Fragment key={id}>{button}</Fragment>;\n return (\n <TooltipTrigger key={id}>\n <Focusable>{button}</Focusable>\n <Tooltip placement={side}>\n <TipContent label={itemLabel} shortcut={shortcut} />\n </Tooltip>\n </TooltipTrigger>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { FloatingButtonGroup, FloatingButtonGroupAction, FloatingButtonGroupItem, FloatingButtonGroupOrientation, FloatingButtonGroupProps, FloatingButtonGroupSeparator, FloatingButtonGroupSize, FloatingButtonGroupVariant } from "./floating-button-group.js";
2
+ export { FloatingButtonGroup, type FloatingButtonGroupAction, type FloatingButtonGroupItem, type FloatingButtonGroupOrientation, type FloatingButtonGroupProps, type FloatingButtonGroupSeparator, type FloatingButtonGroupSize, type FloatingButtonGroupVariant };
@@ -0,0 +1,2 @@
1
+ import { FloatingButtonGroup } from "./floating-button-group.js";
2
+ export { FloatingButtonGroup };
@@ -0,0 +1,55 @@
1
+ import { SVGProps } from "react";
2
+ //#region src/icon-morph/icon-morph.d.ts
3
+ export type IconMorphName = "menu" | "play" | "plus" | "copy" | "theme" | "chevron" | "bell" | "heart";
4
+ export interface IconMorphIconProps extends Omit<SVGProps<SVGSVGElement>, "ref" | "children"> {
5
+ /**
6
+ * The second state of the icon. menu: close, play: pause, plus: check, copy: check,
7
+ * theme: moon, chevron: turned, bell: notifications on, heart: liked.
8
+ */
9
+ state?: boolean;
10
+ /** Rendered size in px. Defaults to 24. */
11
+ size?: number;
12
+ /** Stroke width in the 24 unit grid. Defaults to 1.75. */
13
+ strokeWidth?: number;
14
+ }
15
+ /**
16
+ * A family of icons that morph between two states instead of swapping. Lines
17
+ * rotate and slide into place, shapes interpolate their outlines, and a few
18
+ * carry a small moment of their own: the bell rings when it turns on and the
19
+ * heart bursts when liked. Every morph rides an interruptible spring, so
20
+ * toggling quickly reverses from wherever the icon is. The icons are
21
+ * decorative; put them inside a button that carries the accessible name and
22
+ * `aria-pressed`.
23
+ */
24
+ export interface IconMorphProps extends IconMorphIconProps {
25
+ name: IconMorphName;
26
+ }
27
+ /** Menu to close: the outer lines slide to the middle as they rotate into an X, and the middle line folds away. */
28
+ export declare function MenuIcon({ state, ...props }: IconMorphIconProps): import("react").JSX.Element;
29
+ /** Play to pause: the triangle splits into two halves that straighten into bars. */
30
+ export declare function PlayPauseIcon({ state, ...props }: IconMorphIconProps): import("react").JSX.Element;
31
+ /** Plus to check: each bar swings its ends to become one leg of the check. */
32
+ export declare function PlusCheckIcon({ state, ...props }: IconMorphIconProps): import("react").JSX.Element;
33
+ /** Copy to check: the back sheet tucks under the front one, the front one settles away, and the check draws in. */
34
+ export declare function CopyCheckIcon({ state, ...props }: IconMorphIconProps): import("react").JSX.Element;
35
+ /**
36
+ * Sun to moon: the disc grows while a shadow slides across it from the corner, carving the crescent, and the rays turn and fold in.
37
+ * The crescent is drawn as an outline: the disc's edge outside the shadow, and the shadow's edge inside the disc.
38
+ */
39
+ export declare function SunMoonIcon({ state, ...props }: IconMorphIconProps): import("react").JSX.Element;
40
+ export interface ChevronIconProps extends IconMorphIconProps {
41
+ /** Degrees the chevron turns when `state` is true. 180 flips down to up; 90 turns right to down. */
42
+ turn?: number;
43
+ /** Where the chevron points at rest. */
44
+ pointing?: "down" | "right";
45
+ }
46
+ /** A chevron that turns on a spring, for disclosure and sort controls. */
47
+ export declare function ChevronIcon({ state, turn, pointing, style, ...props }: ChevronIconProps): import("react").JSX.Element;
48
+ /** Notifications on and off: turning on rings the bell from its top, with the clapper lagging behind; turning off strikes it through. */
49
+ export declare function BellIcon({ state, ...props }: IconMorphIconProps): import("react").JSX.Element;
50
+ /** Like: the heart fills with a pop, and a ring and a few sparks burst out once. Unliking simply empties it. */
51
+ export declare function HeartIcon({ state, className, ...props }: IconMorphIconProps): import("react").JSX.Element;
52
+ /** One entry point for every morphing icon: pick it by `name` and drive it with `state`. */
53
+ export declare function IconMorph({ name, ...props }: IconMorphProps): import("react").JSX.Element;
54
+ //#endregion
55
+ //# sourceMappingURL=icon-morph.d.ts.map