@milktop/react-ui 0.0.0-stage → 0.1.2

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 (250) hide show
  1. package/README.md +173 -2
  2. package/dist/accordion/Accordion.d.ts +65 -0
  3. package/dist/accordion/Accordion.js +61 -0
  4. package/dist/accordion/accordion.css +17 -0
  5. package/dist/action-bar/ActionBar.d.ts +33 -0
  6. package/dist/action-bar/ActionBar.js +48 -0
  7. package/dist/action-bar/action-bar.css +22 -0
  8. package/dist/alert/Alert.d.ts +28 -0
  9. package/dist/alert/Alert.js +12 -0
  10. package/dist/alert/alert.css +32 -0
  11. package/dist/app-shell/AppShell.d.ts +38 -0
  12. package/dist/app-shell/AppShell.js +92 -0
  13. package/dist/app-shell/Topbar.d.ts +20 -0
  14. package/dist/app-shell/Topbar.js +13 -0
  15. package/dist/app-shell/app-shell.css +41 -0
  16. package/dist/app-shell/shell.d.ts +9 -0
  17. package/dist/app-shell/shell.js +3 -0
  18. package/dist/attachments/Attachments.d.ts +84 -0
  19. package/dist/attachments/Attachments.js +235 -0
  20. package/dist/attachments/attachments.css +86 -0
  21. package/dist/avatar/Avatar.d.ts +31 -0
  22. package/dist/avatar/Avatar.js +41 -0
  23. package/dist/avatar/AvatarGroup.d.ts +41 -0
  24. package/dist/avatar/AvatarGroup.js +17 -0
  25. package/dist/avatar/avatar.css +73 -0
  26. package/dist/badge/Badge.d.ts +26 -0
  27. package/dist/badge/Badge.js +10 -0
  28. package/dist/badge/badge.css +24 -0
  29. package/dist/banner/Banner.d.ts +33 -0
  30. package/dist/banner/Banner.js +14 -0
  31. package/dist/banner/banner.css +25 -0
  32. package/dist/breadcrumbs/Breadcrumbs.d.ts +39 -0
  33. package/dist/breadcrumbs/Breadcrumbs.js +28 -0
  34. package/dist/breadcrumbs/breadcrumbs.css +18 -0
  35. package/dist/button/Button.d.ts +40 -0
  36. package/dist/button/Button.js +19 -0
  37. package/dist/button/button.css +63 -0
  38. package/dist/card/Card.d.ts +355 -0
  39. package/dist/card/Card.js +56 -0
  40. package/dist/card/card.css +43 -0
  41. package/dist/chart/AreaChart.d.ts +19 -0
  42. package/dist/chart/AreaChart.js +61 -0
  43. package/dist/chart/BarChart.d.ts +3 -0
  44. package/dist/chart/BarChart.js +60 -0
  45. package/dist/chart/ChartFrame.d.ts +25 -0
  46. package/dist/chart/ChartFrame.js +22 -0
  47. package/dist/chart/DonutChart.d.ts +20 -0
  48. package/dist/chart/DonutChart.js +41 -0
  49. package/dist/chart/Sparkline.d.ts +26 -0
  50. package/dist/chart/Sparkline.js +36 -0
  51. package/dist/chart/chart.css +51 -0
  52. package/dist/chart/chart.d.ts +23 -0
  53. package/dist/chart/chart.js +19 -0
  54. package/dist/checkbox/Checkbox.d.ts +50 -0
  55. package/dist/checkbox/Checkbox.js +19 -0
  56. package/dist/checkbox/CheckboxGroup.d.ts +44 -0
  57. package/dist/checkbox/CheckboxGroup.js +80 -0
  58. package/dist/checkbox/checkbox.css +49 -0
  59. package/dist/collapsible/Collapsible.d.ts +41 -0
  60. package/dist/collapsible/Collapsible.js +15 -0
  61. package/dist/collapsible/collapsible.css +20 -0
  62. package/dist/color-scheme.d.ts +18 -0
  63. package/dist/color-scheme.js +84 -0
  64. package/dist/command/Command.d.ts +64 -0
  65. package/dist/command/Command.js +162 -0
  66. package/dist/command/command.css +47 -0
  67. package/dist/confirm/confirm.css +5 -0
  68. package/dist/confirm/confirm.d.ts +42 -0
  69. package/dist/confirm/confirm.js +115 -0
  70. package/dist/copy-button/CopyButton.d.ts +30 -0
  71. package/dist/copy-button/CopyButton.js +23 -0
  72. package/dist/copy-button/copy-button.css +12 -0
  73. package/dist/css.d.ts +2 -0
  74. package/dist/data-list/DataList.d.ts +55 -0
  75. package/dist/data-list/DataList.js +43 -0
  76. package/dist/data-list/data-list.css +19 -0
  77. package/dist/date-picker/DatePicker.d.ts +95 -0
  78. package/dist/date-picker/DatePicker.js +237 -0
  79. package/dist/date-picker/date-picker.css +53 -0
  80. package/dist/dialog/Dialog.d.ts +98 -0
  81. package/dist/dialog/Dialog.js +92 -0
  82. package/dist/dialog/dialog.css +31 -0
  83. package/dist/divider/Divider.d.ts +27 -0
  84. package/dist/divider/Divider.js +8 -0
  85. package/dist/divider/divider.css +12 -0
  86. package/dist/editable/Editable.d.ts +47 -0
  87. package/dist/editable/Editable.js +51 -0
  88. package/dist/editable/editable.css +25 -0
  89. package/dist/editor/Editor.d.ts +59 -0
  90. package/dist/editor/Editor.js +220 -0
  91. package/dist/editor/editor.css +42 -0
  92. package/dist/editor/image.d.ts +1 -0
  93. package/dist/editor/image.js +23 -0
  94. package/dist/empty-state/EmptyState.d.ts +22 -0
  95. package/dist/empty-state/EmptyState.js +9 -0
  96. package/dist/empty-state/empty-state.css +14 -0
  97. package/dist/event-calendar/EventCalendar.d.ts +213 -0
  98. package/dist/event-calendar/EventCalendar.js +686 -0
  99. package/dist/event-calendar/dates.d.ts +8 -0
  100. package/dist/event-calendar/dates.js +21 -0
  101. package/dist/event-calendar/event-calendar.css +157 -0
  102. package/dist/field/Field.d.ts +66 -0
  103. package/dist/field/Field.js +23 -0
  104. package/dist/field/field.css +26 -0
  105. package/dist/file-upload/FileUpload.d.ts +60 -0
  106. package/dist/file-upload/FileUpload.js +102 -0
  107. package/dist/file-upload/file-upload.css +32 -0
  108. package/dist/format/FormatNumber.d.ts +42 -0
  109. package/dist/format/FormatNumber.js +59 -0
  110. package/dist/format/LocaleProvider.d.ts +33 -0
  111. package/dist/format/LocaleProvider.js +48 -0
  112. package/dist/format/format.d.ts +37 -0
  113. package/dist/format/format.js +74 -0
  114. package/dist/format/messages.d.ts +211 -0
  115. package/dist/format/messages.js +202 -0
  116. package/dist/highlight/Highlight.d.ts +11 -0
  117. package/dist/highlight/Highlight.js +14 -0
  118. package/dist/highlight/highlight.css +17 -0
  119. package/dist/hover-card/HoverCard.d.ts +47 -0
  120. package/dist/hover-card/HoverCard.js +18 -0
  121. package/dist/hover-card/hover-card.css +19 -0
  122. package/dist/index.d.ts +84 -0
  123. package/dist/index.js +85 -0
  124. package/dist/input/Input.d.ts +41 -0
  125. package/dist/input/Input.js +48 -0
  126. package/dist/input/input.css +25 -0
  127. package/dist/kbd/Kbd.d.ts +19 -0
  128. package/dist/kbd/Kbd.js +20 -0
  129. package/dist/kbd/kbd.css +8 -0
  130. package/dist/menu/Menu.d.ts +223 -0
  131. package/dist/menu/Menu.js +275 -0
  132. package/dist/menu/menu.css +38 -0
  133. package/dist/native-select/NativeSelect.d.ts +36 -0
  134. package/dist/native-select/NativeSelect.js +51 -0
  135. package/dist/native-select/native-select.css +16 -0
  136. package/dist/number-input/NumberInput.d.ts +56 -0
  137. package/dist/number-input/NumberInput.js +67 -0
  138. package/dist/number-input/number-input.css +21 -0
  139. package/dist/page/Page.d.ts +30 -0
  140. package/dist/page/Page.js +9 -0
  141. package/dist/page/page.css +17 -0
  142. package/dist/pagination/Pagination.d.ts +31 -0
  143. package/dist/pagination/Pagination.js +58 -0
  144. package/dist/pagination/pagination.css +15 -0
  145. package/dist/password-input/PasswordInput.d.ts +47 -0
  146. package/dist/password-input/PasswordInput.js +35 -0
  147. package/dist/password-input/password-input.css +18 -0
  148. package/dist/pin-input/PinInput.d.ts +45 -0
  149. package/dist/pin-input/PinInput.js +22 -0
  150. package/dist/pin-input/pin-input.css +13 -0
  151. package/dist/popover/Popover.d.ts +65 -0
  152. package/dist/popover/Popover.js +26 -0
  153. package/dist/popover/popover.css +16 -0
  154. package/dist/progress/Progress.d.ts +56 -0
  155. package/dist/progress/Progress.js +28 -0
  156. package/dist/progress/progress.css +24 -0
  157. package/dist/prose/Prose.d.ts +23 -0
  158. package/dist/prose/Prose.js +10 -0
  159. package/dist/prose/prose.css +29 -0
  160. package/dist/radio-group/RadioGroup.d.ts +59 -0
  161. package/dist/radio-group/RadioGroup.js +37 -0
  162. package/dist/radio-group/radio-group.css +30 -0
  163. package/dist/rolling-number/RollingNumber.d.ts +27 -0
  164. package/dist/rolling-number/RollingNumber.js +18 -0
  165. package/dist/rolling-number/rolling-number.css +10 -0
  166. package/dist/segment-group/Segmented.d.ts +68 -0
  167. package/dist/segment-group/Segmented.js +79 -0
  168. package/dist/segment-group/segment-group.css +25 -0
  169. package/dist/select/ListSelect.d.ts +5 -0
  170. package/dist/select/ListSelect.js +82 -0
  171. package/dist/select/SearchSelect.d.ts +43 -0
  172. package/dist/select/SearchSelect.js +219 -0
  173. package/dist/select/Select.d.ts +147 -0
  174. package/dist/select/Select.js +32 -0
  175. package/dist/select/items.d.ts +17 -0
  176. package/dist/select/items.js +14 -0
  177. package/dist/select/search-select.css +45 -0
  178. package/dist/select/select.css +52 -0
  179. package/dist/sheet/Sheet.d.ts +88 -0
  180. package/dist/sheet/Sheet.js +66 -0
  181. package/dist/sheet/sheet.css +44 -0
  182. package/dist/sidebar/NavGroup.d.ts +29 -0
  183. package/dist/sidebar/NavGroup.js +78 -0
  184. package/dist/sidebar/NavItem.d.ts +33 -0
  185. package/dist/sidebar/NavItem.js +50 -0
  186. package/dist/sidebar/NavSection.d.ts +20 -0
  187. package/dist/sidebar/NavSection.js +44 -0
  188. package/dist/sidebar/Sidebar.d.ts +65 -0
  189. package/dist/sidebar/Sidebar.js +59 -0
  190. package/dist/sidebar/SidebarUser.d.ts +31 -0
  191. package/dist/sidebar/SidebarUser.js +14 -0
  192. package/dist/sidebar/context.d.ts +13 -0
  193. package/dist/sidebar/context.js +6 -0
  194. package/dist/sidebar/sidebar.css +109 -0
  195. package/dist/skeleton/Skeleton.d.ts +25 -0
  196. package/dist/skeleton/Skeleton.js +8 -0
  197. package/dist/skeleton/skeleton.css +13 -0
  198. package/dist/slider/Slider.d.ts +70 -0
  199. package/dist/slider/Slider.js +65 -0
  200. package/dist/slider/slider.css +17 -0
  201. package/dist/sortable-list/SortableList.d.ts +46 -0
  202. package/dist/sortable-list/SortableList.js +85 -0
  203. package/dist/sortable-list/sortable-list.css +25 -0
  204. package/dist/spinner/Spinner.d.ts +18 -0
  205. package/dist/spinner/Spinner.js +11 -0
  206. package/dist/spinner/spinner.css +9 -0
  207. package/dist/stat/Stat.d.ts +42 -0
  208. package/dist/stat/Stat.js +21 -0
  209. package/dist/stat/Stats.d.ts +23 -0
  210. package/dist/stat/Stats.js +8 -0
  211. package/dist/stat/stat.css +34 -0
  212. package/dist/swatch-picker/SwatchPicker.d.ts +45 -0
  213. package/dist/swatch-picker/SwatchPicker.js +54 -0
  214. package/dist/swatch-picker/swatch-picker.css +38 -0
  215. package/dist/switch/Switch.d.ts +39 -0
  216. package/dist/switch/Switch.js +17 -0
  217. package/dist/switch/switch.css +13 -0
  218. package/dist/table/Table.d.ts +90 -0
  219. package/dist/table/Table.js +145 -0
  220. package/dist/table/table.css +44 -0
  221. package/dist/tabs/Tabs.d.ts +87 -0
  222. package/dist/tabs/Tabs.js +90 -0
  223. package/dist/tabs/tabs.css +53 -0
  224. package/dist/tags-input/TagsInput.d.ts +69 -0
  225. package/dist/tags-input/TagsInput.js +95 -0
  226. package/dist/tags-input/tags-input.css +37 -0
  227. package/dist/textarea/Textarea.d.ts +24 -0
  228. package/dist/textarea/Textarea.js +51 -0
  229. package/dist/textarea/textarea.css +14 -0
  230. package/dist/theme-toggle/ThemeToggle.d.ts +49 -0
  231. package/dist/theme-toggle/ThemeToggle.js +71 -0
  232. package/dist/theme-toggle/theme-toggle.css +23 -0
  233. package/dist/theme.css +121 -0
  234. package/dist/time-input/TimeInput.d.ts +25 -0
  235. package/dist/time-input/TimeInput.js +20 -0
  236. package/dist/timeline/Timeline.d.ts +50 -0
  237. package/dist/timeline/Timeline.js +15 -0
  238. package/dist/timeline/timeline.css +29 -0
  239. package/dist/toast/Toaster.d.ts +36 -0
  240. package/dist/toast/Toaster.js +25 -0
  241. package/dist/toast/toast.css +33 -0
  242. package/dist/tooltip/Tooltip.d.ts +55 -0
  243. package/dist/tooltip/Tooltip.js +48 -0
  244. package/dist/tooltip/tooltip.css +18 -0
  245. package/dist/tree-view/TreeView.d.ts +86 -0
  246. package/dist/tree-view/TreeView.js +74 -0
  247. package/dist/tree-view/tree-view.css +29 -0
  248. package/dist/utils.d.ts +19 -0
  249. package/dist/utils.js +59 -0
  250. package/package.json +99 -4
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Tooltip } from '../tooltip/Tooltip.js';
3
+ import { Avatar, customSize } from './Avatar.js';
4
+ import { cx, partsOf } from '../utils.js';
5
+ import { useMessages } from '../format/LocaleProvider.js';
6
+ import '../theme.css';
7
+ import './avatar.css';
8
+ export function AvatarGroup({ items = null, max = null, size = 'md', color = 'accent', square = false, letters = 2, tooltip = false, spacing = 'normal', label = null, className, classes, children, ...rest }) {
9
+ const t = useMessages();
10
+ const part = partsOf('avatar-group', classes);
11
+ const people = (items ?? []).map((item) => (typeof item === 'string' ? { name: item } : item));
12
+ const shown = max && people.length > max ? people.slice(0, max) : people;
13
+ const hidden = people.slice(shown.length);
14
+ const custom = customSize(size);
15
+ const names = hidden.map((person) => person.name).join(', ');
16
+ return (_jsxs("div", { ...rest, ...part('root'), className: cx(className, classes?.root), role: "group", "aria-label": label ?? (items ? `${people.length} people` : undefined), "data-size": custom ? 'custom' : size, "data-spacing": spacing, children: [shown.map((person, i) => (_jsx(Avatar, { size: size, square: square, letters: letters, color: person.color ?? color, name: person.name, src: person.src, tooltip: tooltip || null }, person.name ?? person.src ?? i))), children, hidden.length > 0 && (_jsx(Tooltip, { content: names, children: _jsxs("span", { ...part('more'), "data-square": square || undefined, "aria-label": `${t.common.more(hidden.length)}: ${names}`, style: custom ? { '--avatar-size': custom } : undefined, children: ["+", hidden.length] }) }))] }));
17
+ }
@@ -0,0 +1,73 @@
1
+ @layer ui {
2
+ [data-scope=avatar][data-part=root] {
3
+ position: relative; display: inline-grid; place-items: center; flex-shrink: 0; width: 2.5rem; height: 2.5rem;
4
+ overflow: hidden; border-radius: 9999px; vertical-align: middle; user-select: none;
5
+ background: var(--ui-accent-soft); color: var(--ui-accent-soft-text); font-family: var(--ui-font); font-size: 14px; font-weight: 600;
6
+ &[data-square] { border-radius: var(--ui-radius); }
7
+ &[data-size=sm] { width: 1.75rem; height: 1.75rem; font-size: 12px; }
8
+ &[data-size=lg] { width: 3.5rem; height: 3.5rem; font-size: 18px; }
9
+ &[data-size=xl] { width: 5rem; height: 5rem; font-size: 24px; }
10
+ /* Custom sizes: the initials grow in step with the named sizes. */
11
+ &[data-size=custom] { width: var(--avatar-size); height: var(--avatar-size); font-size: calc(6px + 0.2 * var(--avatar-size)); }
12
+
13
+ &[data-color=gray] { background: #f4f4f5; color: #3f3f46; }
14
+ &[data-color=red] { background: #fee2e2; color: #991b1b; }
15
+ &[data-color=orange] { background: #ffedd5; color: #9a3412; }
16
+ &[data-color=amber] { background: #fef3c7; color: #92400e; }
17
+ &[data-color=green] { background: #dcfce7; color: #166534; }
18
+ &[data-color=teal] { background: #ccfbf1; color: #115e59; }
19
+ &[data-color=blue] { background: #dbeafe; color: #1e40af; }
20
+ &[data-color=purple] { background: #f3e8ff; color: #6b21a8; }
21
+ &[data-color=pink] { background: #fce7f3; color: #9d174d; }
22
+ :is(.dark, [data-theme=dark]) & {
23
+ &[data-color=gray] { background: #3f3f46; color: #e4e4e7; }
24
+ &[data-color=red] { background: #7f1d1d; color: #fecaca; }
25
+ &[data-color=orange] { background: #7c2d12; color: #fed7aa; }
26
+ &[data-color=amber] { background: #78350f; color: #fde68a; }
27
+ &[data-color=green] { background: #14532d; color: #bbf7d0; }
28
+ &[data-color=teal] { background: #134e4a; color: #99f6e4; }
29
+ &[data-color=blue] { background: #1e3a8a; color: #bfdbfe; }
30
+ &[data-color=purple] { background: #581c87; color: #e9d5ff; }
31
+ &[data-color=pink] { background: #831843; color: #fbcfe8; }
32
+ }
33
+ }
34
+ [data-scope=avatar][data-part=image] { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
35
+ [data-scope=avatar][data-part=fallback] { display: grid; place-items: center; width: 100%; height: 100%; line-height: 1; }
36
+ [data-scope=avatar][data-part=person] { font-size: 1.6em; }
37
+ [data-scope=avatar][data-part=tooltip-target] { position: absolute; inset: 0; }
38
+ }
39
+
40
+ /* A status dot on the corner: the avatar stops clipping (its image rounds itself), so the dot can sit over the edge. */
41
+ @layer ui {
42
+ [data-scope=avatar][data-part=root][data-has-status] { overflow: visible;
43
+ & > :is([data-part=image], [data-part=fallback]) { border-radius: inherit; overflow: hidden; } }
44
+ [data-scope=avatar][data-part=status] { position: absolute; right: 0; bottom: 0; width: 28%; height: 28%; min-width: 0.5rem; min-height: 0.5rem;
45
+ box-sizing: border-box; border-radius: 9999px; box-shadow: 0 0 0 2px var(--ui-avatar-ring, var(--ui-surface));
46
+ &[data-status=online] { background: #22c55e; }
47
+ &[data-status=away] { background: #f59e0b; }
48
+ &[data-status=busy] { background: var(--ui-danger); }
49
+ &[data-status=offline] { background: var(--ui-muted); } }
50
+ }
51
+
52
+ /* A stack: each overlaps the one before, ringed in --ui-avatar-ring so the
53
+ edges read. Later ones sit on top. */
54
+ @layer ui {
55
+ [data-scope=avatar-group][data-part=root] { display: inline-flex; align-items: center; vertical-align: middle;
56
+ --ui-avatar-ring: var(--ui-surface); --overlap-size: 0.625rem; --overlap-scale: 1;
57
+ --overlap: calc(var(--overlap-size) * var(--overlap-scale));
58
+ &[data-size=sm] { --overlap-size: 0.5rem; }
59
+ &[data-size=lg] { --overlap-size: 0.875rem; }
60
+ &[data-size=xl] { --overlap-size: 1.25rem; }
61
+ &[data-spacing=tight] { --overlap-scale: 1.5; }
62
+ &[data-spacing=loose] { --overlap-scale: 0.4; }
63
+ & > :is([data-scope=avatar], [data-part=more]) { box-shadow: 0 0 0 2px var(--ui-avatar-ring); }
64
+ & > :is([data-scope=avatar], [data-part=more]):not(:first-child) { margin-left: calc(var(--overlap) * -1); } }
65
+ [data-scope=avatar-group][data-part=more] {
66
+ display: inline-grid; place-items: center; flex-shrink: 0; width: 2.5rem; height: 2.5rem; border-radius: 9999px; box-sizing: border-box;
67
+ background: var(--ui-hover); color: var(--ui-muted); font-family: var(--ui-font); font-size: 12px; font-weight: 600; cursor: default;
68
+ [data-size=sm] > & { width: 1.75rem; height: 1.75rem; font-size: 11px; }
69
+ [data-size=lg] > & { width: 3.5rem; height: 3.5rem; font-size: 15px; }
70
+ [data-size=xl] > & { width: 5rem; height: 5rem; font-size: 20px; }
71
+ [data-size=custom] > & { width: var(--avatar-size); height: var(--avatar-size); font-size: calc(4px + 0.2 * var(--avatar-size)); }
72
+ &[data-square] { border-radius: var(--ui-radius); } }
73
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A short label, like a status or a count.
3
+ *
4
+ * <Badge variant="success" dot>Paid</Badge>
5
+ *
6
+ * - `variant`: 'neutral' (default), 'accent', 'success', 'warning', 'danger' or 'outline'
7
+ * - `size`: 'sm' or 'md'
8
+ * - `icon`: an Iconify name before the text; `dot`: a coloured dot instead
9
+ * - `onRemove`: an ✕ after the text that calls it: a tag (an applied
10
+ * filter, a record's label); `removeLabel` names it ('Remove …')
11
+ */
12
+ import type { ComponentProps } from 'react';
13
+ import 'iconify-icon';
14
+ import { type Classes } from '../utils.js';
15
+ import '../theme.css';
16
+ import './badge.css';
17
+ export type BadgeProps = ComponentProps<'span'> & {
18
+ variant?: 'neutral' | 'accent' | 'success' | 'warning' | 'danger' | 'outline';
19
+ size?: 'sm' | 'md';
20
+ icon?: string | null;
21
+ dot?: boolean;
22
+ onRemove?: (() => void) | null;
23
+ removeLabel?: string;
24
+ classes?: Classes<'root' | 'dot' | 'icon' | 'remove'>;
25
+ };
26
+ export declare function Badge({ variant, size, icon, dot, onRemove, removeLabel, className, classes, children, ...rest }: BadgeProps): import("react").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import 'iconify-icon';
3
+ import { cx, partsOf } from '../utils.js';
4
+ import '../theme.css';
5
+ import './badge.css';
6
+ export function Badge({ variant = 'neutral', size = 'md', icon = null, dot = false, onRemove = null, removeLabel, className, classes, children, ...rest }) {
7
+ const part = partsOf('badge', classes);
8
+ return (_jsxs("span", { ...rest, ...part('root'), className: cx(className, classes?.root), "data-variant": variant, "data-size": size, children: [dot ? _jsx("span", { ...part('dot'), "aria-hidden": "true" })
9
+ : icon && _jsx("iconify-icon", { ...part('icon'), icon: icon, "aria-hidden": "true" }), children, onRemove && (_jsx("button", { type: "button", ...part('remove'), onClick: onRemove, "aria-label": removeLabel ?? `Remove ${typeof children === 'string' ? children : ''}`.trim(), children: _jsx("iconify-icon", { icon: "lucide:x", "aria-hidden": "true" }) }))] }));
10
+ }
@@ -0,0 +1,24 @@
1
+ @layer ui {
2
+ [data-scope=badge][data-part=root] {
3
+ display: inline-flex; align-items: center; gap: 0.25rem; height: 1.375rem; padding: 0 0.5rem; box-sizing: border-box;
4
+ vertical-align: middle; border: 1px solid transparent; border-radius: 9999px;
5
+ font-family: var(--ui-font); font-size: 12px; font-weight: 500; line-height: 1; white-space: nowrap;
6
+ background: var(--ui-hover); color: var(--ui-text);
7
+ &[data-size=sm] { height: 1.125rem; padding: 0 0.375rem; font-size: 11px; }
8
+ &[data-variant=accent] { background: var(--ui-accent-soft); color: var(--ui-accent-soft-text); }
9
+ &[data-variant=success] { background: #dcfce7; color: #166534; }
10
+ &[data-variant=warning] { background: #fef3c7; color: #92400e; }
11
+ &[data-variant=danger] { background: #fee2e2; color: #991b1b; }
12
+ &[data-variant=outline] { background: transparent; border-color: var(--ui-border); }
13
+ :is(.dark, [data-theme=dark]) &[data-variant=success] { background: #14532d; color: #bbf7d0; }
14
+ :is(.dark, [data-theme=dark]) &[data-variant=warning] { background: #451a03; color: #fde68a; }
15
+ :is(.dark, [data-theme=dark]) &[data-variant=danger] { background: #450a0a; color: #fecaca; }
16
+ }
17
+ [data-scope=badge][data-part=icon] { display: block; width: 1em; height: 1em; }
18
+ [data-scope=badge][data-part=dot] { width: 0.375rem; height: 0.375rem; border-radius: 9999px; background: currentColor; }
19
+ /* A tag's ✕: a small round button at the end. */
20
+ [data-scope=badge][data-part=remove] { display: inline-grid; place-items: center; width: 1rem; height: 1rem; margin-right: -0.25rem; padding: 0;
21
+ border: none; border-radius: 9999px; background: transparent; color: inherit; font-size: 11px; cursor: pointer; opacity: 0.65;
22
+ &:hover { opacity: 1; background: color-mix(in srgb, currentColor 14%, transparent); }
23
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); opacity: 1; } }
24
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * A slim, prominent strip for an announcement or a page-wide message, e.g.
3
+ * across the top of the content. For a message within the content, use Alert.
4
+ *
5
+ * <Banner icon="lucide:sparkles" dismissible>
6
+ * Notes can now be shared with parents. <a href="/help/notes">See how</a>
7
+ * </Banner>
8
+ *
9
+ * - `variant`: 'accent' (default, solid), 'soft', 'neutral', 'success', 'warning' or 'danger'
10
+ * - `icon`: an Iconify name before the message
11
+ * - `actions`: buttons or links at the end
12
+ * - `dismissible`: a close button, which hides it and calls `onDismiss`
13
+ * - `open`, `defaultOpen`: whether it shows (true); `onOpenChange`
14
+ * - `full`: no rounding, to run edge to edge
15
+ */
16
+ import type { HTMLAttributes, ReactNode } from 'react';
17
+ import 'iconify-icon';
18
+ import { type Classes } from '../utils.js';
19
+ import '../theme.css';
20
+ import './banner.css';
21
+ export type BannerProps = HTMLAttributes<HTMLDivElement> & {
22
+ variant?: 'accent' | 'soft' | 'neutral' | 'success' | 'warning' | 'danger';
23
+ icon?: string | null;
24
+ actions?: ReactNode;
25
+ dismissible?: boolean;
26
+ onDismiss?: () => void;
27
+ open?: boolean;
28
+ defaultOpen?: boolean;
29
+ onOpenChange?: (open: boolean) => void;
30
+ full?: boolean;
31
+ classes?: Classes<'root' | 'icon' | 'description' | 'actions' | 'close'>;
32
+ };
33
+ export declare function Banner({ variant, icon, actions, dismissible, onDismiss, open: openProp, defaultOpen, onOpenChange, full, className, classes, children, ...rest }: BannerProps): import("react").JSX.Element | null;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import 'iconify-icon';
3
+ import { cx, partsOf, useControllable } from '../utils.js';
4
+ import { useMessages } from '../format/LocaleProvider.js';
5
+ import '../theme.css';
6
+ import './banner.css';
7
+ export function Banner({ variant = 'accent', icon = null, actions = null, dismissible = false, onDismiss, open: openProp, defaultOpen = true, onOpenChange, full = false, className, classes, children, ...rest }) {
8
+ const t = useMessages();
9
+ const part = partsOf('banner', classes);
10
+ const [open, setOpen] = useControllable(openProp, defaultOpen, onOpenChange);
11
+ if (!open)
12
+ return null;
13
+ return (_jsxs("div", { ...rest, ...part('root'), className: cx(className, classes?.root), "data-variant": variant, "data-full": full || undefined, role: "status", children: [icon && _jsx("iconify-icon", { ...part('icon'), icon: icon, "aria-hidden": "true" }), _jsx("div", { ...part('description'), children: children }), actions && _jsx("div", { ...part('actions'), children: actions }), dismissible && (_jsx("button", { ...part('close'), type: "button", "aria-label": t.common.dismiss, onClick: () => { setOpen(false); onDismiss?.(); }, children: _jsx("iconify-icon", { icon: "lucide:x" }) }))] }));
14
+ }
@@ -0,0 +1,25 @@
1
+ @layer ui {
2
+ [data-scope=banner][data-part=root] {
3
+ display: flex; align-items: center; gap: 0.75rem; width: 100%; min-height: 2.5rem; padding: 0.5rem 1rem; box-sizing: border-box;
4
+ border-radius: calc(var(--ui-radius) + 2px); font-family: var(--ui-font); font-size: 14px;
5
+ background: var(--ui-accent); color: var(--ui-accent-text);
6
+ &[data-full] { border-radius: 0; }
7
+ &[data-variant=soft] { background: var(--ui-accent-soft); color: var(--ui-accent-soft-text); }
8
+ &[data-variant=neutral] { background: var(--ui-surface); color: var(--ui-text); border: 1px solid var(--ui-border); }
9
+ &[data-variant=success] { background: color-mix(in srgb, var(--ui-success) 14%, var(--ui-surface)); color: color-mix(in srgb, var(--ui-success) 70%, var(--ui-text)); }
10
+ &[data-variant=warning] { background: #fef3c7; color: #92400e; }
11
+ &[data-variant=danger] { background: color-mix(in srgb, var(--ui-danger) 12%, var(--ui-surface)); color: var(--ui-danger); }
12
+ :is(.dark, [data-theme=dark]) &[data-variant=warning] { background: #451a03; color: #fde68a; }
13
+ }
14
+ [data-scope=banner][data-part=icon] { flex-shrink: 0; font-size: 16px; }
15
+ [data-scope=banner][data-part=description] { flex-grow: 1; min-width: 0;
16
+ /* Links in the message take the banner's colour, underlined. */
17
+ & a { color: inherit; font-weight: 600; text-decoration: underline; text-underline-offset: 2px; } }
18
+ [data-scope=banner][data-part=actions] { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; }
19
+ [data-scope=banner][data-part=close] {
20
+ display: grid; place-items: center; width: 1.75rem; height: 1.75rem; flex-shrink: 0; margin-right: -0.375rem;
21
+ border: none; border-radius: var(--ui-radius); background: transparent; color: inherit; cursor: pointer; opacity: 0.8;
22
+ &:hover { opacity: 1; background: color-mix(in srgb, currentColor 12%, transparent); }
23
+ &:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }
24
+ }
25
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Where the page sits: links from the top down to the current page.
3
+ *
4
+ * <Breadcrumbs items={[{ label: 'Home', href: '/' }, { label: 'Students' }]} />
5
+ *
6
+ * - `items`: `{ label, href, icon }`; the last is the current page
7
+ * - `separator`: text between crumbs ('/', '›'); a chevron by default
8
+ * - `max`: with more items than this, the middle ones fold into a "…" button
9
+ * that lists them in a menu
10
+ * - `label`: what assistive tech calls the nav ('Breadcrumb')
11
+ * - `renderLink`: draws a crumb's link yourself (your router's Link), given
12
+ * the crumb and the props to spread on it: `(item, props) => <Link to={item.href} {...props} />`
13
+ */
14
+ import type { HTMLAttributes, ReactNode } from 'react';
15
+ import 'iconify-icon';
16
+ import { type Classes } from '../utils.js';
17
+ import '../theme.css';
18
+ import './breadcrumbs.css';
19
+ export type Crumb = {
20
+ label: ReactNode;
21
+ href?: string | null;
22
+ icon?: string | null;
23
+ };
24
+ type CrumbLinkProps = {
25
+ 'data-scope': string;
26
+ 'data-part': string;
27
+ className?: string;
28
+ children: ReactNode;
29
+ };
30
+ export type BreadcrumbsProps = HTMLAttributes<HTMLElement> & {
31
+ items?: Crumb[];
32
+ separator?: ReactNode;
33
+ max?: number | null;
34
+ label?: string;
35
+ renderLink?: (item: Crumb, props: CrumbLinkProps) => ReactNode;
36
+ classes?: Classes<'root' | 'list' | 'crumb' | 'separator' | 'link' | 'more' | 'icon' | 'text'>;
37
+ };
38
+ export declare function Breadcrumbs({ items, separator, max, label: labelProp, renderLink, className, classes, ...rest }: BreadcrumbsProps): import("react").JSX.Element;
39
+ export {};
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import 'iconify-icon';
3
+ import { Menu } from '../menu/Menu.js';
4
+ import { cx, partsOf } from '../utils.js';
5
+ import { useMessages } from '../format/LocaleProvider.js';
6
+ import '../theme.css';
7
+ import './breadcrumbs.css';
8
+ export function Breadcrumbs({ items = [], separator = null, max = null, label: labelProp, renderLink, className, classes, ...rest }) {
9
+ const t = useMessages();
10
+ const label = labelProp ?? t.breadcrumbs.label;
11
+ const part = partsOf('breadcrumbs', classes);
12
+ // The first crumb, a "…" for the folded middle, and the last max - 1.
13
+ const folded = !!max && items.length > max;
14
+ const hidden = folded ? items.slice(1, items.length - (max - 1)) : [];
15
+ const shown = folded ? [items[0], 'fold', ...items.slice(items.length - (max - 1))] : items;
16
+ return (_jsx("nav", { ...rest, ...part('root'), className: cx(className, classes?.root), "aria-label": label, children: _jsx("ol", { ...part('list'), children: shown.map((item, i) => {
17
+ const current = i === shown.length - 1;
18
+ const content = item !== 'fold' && _jsxs(_Fragment, { children: [item.icon && _jsx("iconify-icon", { ...part('icon'), icon: item.icon, "aria-hidden": "true" }), _jsx("span", { ...part('text'), children: item.label })] });
19
+ return (_jsxs("li", { ...part('crumb'), children: [i > 0 && (separator
20
+ ? _jsx("span", { ...part('separator'), "aria-hidden": "true", children: separator })
21
+ : _jsx("iconify-icon", { ...part('separator'), icon: "lucide:chevron-right", "aria-hidden": "true" })), item === 'fold'
22
+ // The folded crumbs, in a menu of links.
23
+ ? _jsx(Menu, { items: hidden.map((crumb, j) => ({ label: crumb.label, value: j, href: crumb.href ?? undefined, icon: crumb.icon ?? undefined })), trigger: _jsx("button", { ...part('more'), type: "button", "aria-label": t.common.more(hidden.length), children: "\u2026" }) })
24
+ : item.href && !current
25
+ ? renderLink ? renderLink(item, { ...part('link'), children: content }) : _jsx("a", { ...part('link'), href: item.href, children: content })
26
+ : _jsx("span", { ...part('link'), "data-current": current || undefined, "aria-current": current ? 'page' : undefined, children: content })] }, i));
27
+ }) }) }));
28
+ }
@@ -0,0 +1,18 @@
1
+ @layer ui {
2
+ [data-scope=breadcrumbs][data-part=root] { display: block; min-width: 0; font-family: var(--ui-font); font-size: 14px; }
3
+ [data-scope=breadcrumbs][data-part=list] { display: flex; align-items: center; gap: 0.375rem; margin: 0; padding: 0;
4
+ list-style: none; min-width: 0; overflow: hidden; }
5
+ [data-scope=breadcrumbs][data-part=crumb] { display: flex; align-items: center; gap: 0.375rem; min-width: 0; }
6
+ [data-scope=breadcrumbs][data-part=separator] { color: var(--ui-muted); opacity: 0.7; flex-shrink: 0; }
7
+ /* Squeezed, the crumbs shrink and their text ends in an ellipsis rather than overlapping. */
8
+ [data-scope=breadcrumbs][data-part=link] { display: inline-flex; align-items: center; gap: 0.375rem; min-width: 0; color: var(--ui-muted);
9
+ text-decoration: none; white-space: nowrap; border-radius: 4px;
10
+ a&:hover { color: var(--ui-text); }
11
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); outline-offset: 2px; }
12
+ &[data-current] { color: var(--ui-text); font-weight: 500; } }
13
+ [data-scope=breadcrumbs][data-part=more] { padding: 0 0.375rem; border: none; background: transparent; border-radius: 4px;
14
+ color: var(--ui-muted); font: inherit; cursor: pointer;
15
+ &:hover { background: var(--ui-hover); color: var(--ui-text); } }
16
+ [data-scope=breadcrumbs][data-part=text] { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
17
+ [data-scope=breadcrumbs][data-part=icon] { font-size: 15px; flex-shrink: 0; }
18
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * A button. Other attributes (onClick, aria-label, form…) go on the <button>.
3
+ *
4
+ * <Button variant="primary" icon="lucide:save" loading={saving}>Save</Button>
5
+ *
6
+ * - `variant`: 'default', 'primary', 'danger', 'soft', 'ghost' or 'link'
7
+ * - `size`: 'sm', 'md' (matching the inputs' height) or 'lg'
8
+ * - `icon`, `iconEnd`: Iconify names; with no text it is an icon button, so give it an aria-label
9
+ * - `loading`: a spinner in place of the icon; also disables it
10
+ * - `round`: a circle for icon buttons, a pill with text
11
+ * - `block`: full width
12
+ * - `count`: a badge on the corner (hidden at 0 or null), "99+" past `max`
13
+ * - `dot`: a small dot there instead, for "something new"
14
+ * - `pulse`: a ring ripples out from the badge or dot (not when the system asks for less motion)
15
+ * - `countColor`: 'danger', 'accent', 'success', 'warning' or 'neutral'
16
+ * - `countLabel`: what the count is, for assistive tech ('new': "Inbox, 3 new")
17
+ * - `type`: 'button' by default (pass 'submit' for forms)
18
+ */
19
+ import type { ComponentProps } from 'react';
20
+ import 'iconify-icon';
21
+ import { type Classes } from '../utils.js';
22
+ import '../theme.css';
23
+ import './button.css';
24
+ export type ButtonProps = ComponentProps<'button'> & {
25
+ variant?: 'default' | 'primary' | 'danger' | 'soft' | 'ghost' | 'link';
26
+ size?: 'sm' | 'md' | 'lg';
27
+ icon?: string | null;
28
+ iconEnd?: string | null;
29
+ loading?: boolean;
30
+ round?: boolean;
31
+ block?: boolean;
32
+ count?: number | null;
33
+ max?: number;
34
+ dot?: boolean;
35
+ pulse?: boolean;
36
+ countColor?: 'danger' | 'accent' | 'success' | 'warning' | 'neutral';
37
+ countLabel?: string;
38
+ classes?: Classes<'root' | 'spinner' | 'icon' | 'badge' | 'spoken'>;
39
+ };
40
+ export declare function Button({ variant, size, icon, iconEnd, loading, round, block, count, max, dot, pulse, countColor, countLabel: countLabelProp, type, disabled, className, classes, children, ...rest }: ButtonProps): import("react").JSX.Element;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import 'iconify-icon';
3
+ import { cx, partsOf } from '../utils.js';
4
+ import { useMessages } from '../format/LocaleProvider.js';
5
+ import '../theme.css';
6
+ import './button.css';
7
+ export function Button({ variant = 'default', size = 'md', icon = null, iconEnd = null, loading = false, round = false, block = false, count = null, max = 99, dot = false, pulse = false, countColor = 'danger', countLabel: countLabelProp, type = 'button', disabled = false, className, classes, children, ...rest }) {
8
+ const t = useMessages();
9
+ const countLabel = countLabelProp ?? t.button.countLabel;
10
+ const part = partsOf('button', classes);
11
+ const badge = count && count > 0 ? (count > max ? `${max}+` : String(count)) : dot ? '' : null;
12
+ // Read after the label by assistive tech: "Inbox, 3 new".
13
+ const spoken = count && count > 0 ? `${count} ${countLabel}` : dot ? countLabel : '';
14
+ // An aria-label hides the button's text from assistive tech, so the count joins the label.
15
+ const label = rest['aria-label'] && spoken ? `${rest['aria-label']}, ${spoken}` : rest['aria-label'];
16
+ return (_jsxs("button", { ...rest, ...part('root'), className: cx(className, classes?.root), type: type, disabled: disabled || loading, "aria-label": label, "aria-busy": loading || undefined, "data-variant": variant, "data-size": size, "data-round": round || undefined, "data-block": block || undefined, "data-loading": loading || undefined, "data-icon-only": (children == null && !(icon && iconEnd)) || undefined, children: [loading
17
+ ? _jsx("span", { ...part('spinner'), "aria-hidden": "true" })
18
+ : icon && _jsx("iconify-icon", { ...part('icon'), icon: icon, "aria-hidden": "true" }), children, iconEnd && _jsx("iconify-icon", { ...part('icon'), icon: iconEnd, "aria-hidden": "true" }), badge != null && _jsxs(_Fragment, { children: [_jsx("span", { ...part('badge'), "aria-hidden": "true", "data-dot": badge === '' || undefined, "data-pulse": pulse || undefined, "data-color": countColor, children: badge }), !rest['aria-label'] && _jsxs("span", { ...part('spoken'), children: [", ", spoken] })] })] }));
19
+ }
@@ -0,0 +1,63 @@
1
+ @layer ui {
2
+ [data-scope=button][data-part=root] {
3
+ position: relative;
4
+ display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
5
+ height: var(--ui-control-height); padding: 0 0.875rem; box-sizing: border-box;
6
+ border: 1px solid var(--ui-border); border-radius: var(--ui-radius);
7
+ background: var(--ui-surface); color: var(--ui-text);
8
+ font-family: var(--ui-font); font-size: 14px; font-weight: 500; line-height: 1; white-space: nowrap; text-decoration: none; cursor: pointer; outline: none;
9
+ transition: background-color 120ms, border-color 120ms;
10
+
11
+ &:hover { background: var(--ui-hover); }
12
+ &:active { translate: 0 1px; }
13
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); border-color: var(--ui-ring); }
14
+ &:disabled { opacity: 0.5; cursor: not-allowed; translate: none; }
15
+
16
+ &[data-variant=primary] { background: var(--ui-accent); border-color: var(--ui-accent); color: var(--ui-accent-text);
17
+ &:hover { filter: brightness(1.1); } }
18
+ &[data-variant=danger] { background: var(--ui-danger); border-color: var(--ui-danger); color: white;
19
+ &:hover { filter: brightness(1.1); } }
20
+ &[data-variant=soft] { background: var(--ui-accent-soft); border-color: transparent; color: var(--ui-accent-soft-text);
21
+ &:hover { filter: brightness(0.97); } }
22
+ &[data-variant=ghost] { background: transparent; border-color: transparent;
23
+ &:hover { background: var(--ui-hover); } }
24
+ /* Looks like a link, behaves as a button. */
25
+ &[data-variant=link] { height: auto; padding: 0; background: transparent; border-color: transparent; color: var(--ui-accent);
26
+ &:hover { background: transparent; text-decoration: underline; }
27
+ &:active { translate: none; } }
28
+
29
+ &[data-size=sm] { height: var(--ui-control-height-sm); padding: 0 0.625rem; font-size: 12px; gap: 0.375rem; }
30
+ &[data-size=lg] { height: var(--ui-control-height-lg); padding: 0 1.25rem; font-size: 16px; }
31
+ &[data-icon-only] { padding: 0; width: var(--ui-control-height);
32
+ &[data-size=sm] { width: var(--ui-control-height-sm); }
33
+ &[data-size=lg] { width: var(--ui-control-height-lg); } }
34
+ &[data-round] { border-radius: 9999px; }
35
+ &[data-block] { display: flex; width: 100%; }
36
+ &[data-loading] { cursor: progress; }
37
+ }
38
+
39
+ [data-scope=button][data-part=icon] { display: block; width: 1em; height: 1em; font-size: 16px; flex-shrink: 0;
40
+ [data-size=sm] > & { font-size: 14px; } }
41
+ [data-scope=button][data-part=spinner] { width: 1em; height: 1em; flex-shrink: 0; box-sizing: border-box;
42
+ border: 2px solid currentColor; border-top-color: transparent; border-radius: 9999px; animation: ui-spin 0.7s linear infinite; }
43
+ /* The count badge, ringed in the page colour so it stands off the button. */
44
+ [data-scope=button][data-part=badge] { position: absolute; top: 0; right: 0; translate: 45% -45%;
45
+ display: grid; place-items: center; min-width: 18px; height: 18px; padding: 0 5px; box-sizing: border-box;
46
+ border-radius: 9999px; isolation: isolate; box-shadow: 0 0 0 2px var(--ui-surface);
47
+ --count-bg: var(--ui-danger); --count-text: white;
48
+ background: var(--ui-button-count-bg, var(--count-bg)); color: var(--ui-button-count-text, var(--count-text));
49
+ &[data-color=accent] { --count-bg: var(--ui-accent); --count-text: var(--ui-accent-text); }
50
+ &[data-color=success] { --count-bg: var(--ui-success); --count-text: white; }
51
+ &[data-color=warning] { --count-bg: #f59e0b; --count-text: #451a03; }
52
+ &[data-color=neutral] { --count-bg: var(--ui-text); --count-text: var(--ui-surface); }
53
+ &[data-dot] { min-width: 0; width: 10px; height: 10px; padding: 0; }
54
+ [data-icon-only] > &[data-dot] { translate: 15% -15%; }
55
+ /* A copy of the badge's colour that grows and fades behind it. */
56
+ &[data-pulse]::after { content: ''; position: absolute; inset: 0; z-index: -1; border-radius: 9999px; background: inherit;
57
+ animation: ui-ping 1.6s cubic-bezier(0, 0, 0.2, 1) infinite; }
58
+ font-family: var(--ui-font); font-size: 11px; font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums; pointer-events: none;
59
+ [data-round][data-icon-only] > & { translate: 25% -25%; } }
60
+ [data-scope=button][data-part=spoken] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
61
+ @keyframes ui-ping { from { scale: 1; opacity: 0.7; } 75%, to { scale: 2.2; opacity: 0; } }
62
+ @media (prefers-reduced-motion: reduce) { [data-scope=button][data-part=badge]::after { animation: none; } }
63
+ }