@milktop/react-ui 0.0.0-stage → 0.1.1
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.
- package/README.md +173 -2
- package/dist/accordion/Accordion.d.ts +65 -0
- package/dist/accordion/Accordion.js +61 -0
- package/dist/accordion/accordion.css +17 -0
- package/dist/action-bar/ActionBar.d.ts +33 -0
- package/dist/action-bar/ActionBar.js +48 -0
- package/dist/action-bar/action-bar.css +22 -0
- package/dist/alert/Alert.d.ts +28 -0
- package/dist/alert/Alert.js +12 -0
- package/dist/alert/alert.css +26 -0
- package/dist/app-shell/AppShell.d.ts +38 -0
- package/dist/app-shell/AppShell.js +92 -0
- package/dist/app-shell/Topbar.d.ts +20 -0
- package/dist/app-shell/Topbar.js +13 -0
- package/dist/app-shell/app-shell.css +41 -0
- package/dist/app-shell/shell.d.ts +9 -0
- package/dist/app-shell/shell.js +3 -0
- package/dist/attachments/Attachments.d.ts +84 -0
- package/dist/attachments/Attachments.js +235 -0
- package/dist/attachments/attachments.css +86 -0
- package/dist/avatar/Avatar.d.ts +31 -0
- package/dist/avatar/Avatar.js +41 -0
- package/dist/avatar/AvatarGroup.d.ts +41 -0
- package/dist/avatar/AvatarGroup.js +17 -0
- package/dist/avatar/avatar.css +73 -0
- package/dist/badge/Badge.d.ts +26 -0
- package/dist/badge/Badge.js +10 -0
- package/dist/badge/badge.css +24 -0
- package/dist/banner/Banner.d.ts +33 -0
- package/dist/banner/Banner.js +14 -0
- package/dist/banner/banner.css +25 -0
- package/dist/breadcrumbs/Breadcrumbs.d.ts +39 -0
- package/dist/breadcrumbs/Breadcrumbs.js +28 -0
- package/dist/breadcrumbs/breadcrumbs.css +18 -0
- package/dist/button/Button.d.ts +40 -0
- package/dist/button/Button.js +19 -0
- package/dist/button/button.css +63 -0
- package/dist/card/Card.d.ts +355 -0
- package/dist/card/Card.js +56 -0
- package/dist/card/card.css +43 -0
- package/dist/chart/AreaChart.d.ts +19 -0
- package/dist/chart/AreaChart.js +61 -0
- package/dist/chart/BarChart.d.ts +3 -0
- package/dist/chart/BarChart.js +60 -0
- package/dist/chart/ChartFrame.d.ts +25 -0
- package/dist/chart/ChartFrame.js +22 -0
- package/dist/chart/DonutChart.d.ts +20 -0
- package/dist/chart/DonutChart.js +41 -0
- package/dist/chart/Sparkline.d.ts +26 -0
- package/dist/chart/Sparkline.js +36 -0
- package/dist/chart/chart.css +51 -0
- package/dist/chart/chart.d.ts +23 -0
- package/dist/chart/chart.js +19 -0
- package/dist/checkbox/Checkbox.d.ts +50 -0
- package/dist/checkbox/Checkbox.js +19 -0
- package/dist/checkbox/CheckboxGroup.d.ts +44 -0
- package/dist/checkbox/CheckboxGroup.js +80 -0
- package/dist/checkbox/checkbox.css +49 -0
- package/dist/collapsible/Collapsible.d.ts +41 -0
- package/dist/collapsible/Collapsible.js +15 -0
- package/dist/collapsible/collapsible.css +20 -0
- package/dist/color-scheme.d.ts +18 -0
- package/dist/color-scheme.js +84 -0
- package/dist/command/Command.d.ts +64 -0
- package/dist/command/Command.js +162 -0
- package/dist/command/command.css +47 -0
- package/dist/confirm/confirm.css +5 -0
- package/dist/confirm/confirm.d.ts +42 -0
- package/dist/confirm/confirm.js +115 -0
- package/dist/copy-button/CopyButton.d.ts +30 -0
- package/dist/copy-button/CopyButton.js +23 -0
- package/dist/copy-button/copy-button.css +12 -0
- package/dist/css.d.ts +2 -0
- package/dist/data-list/DataList.d.ts +55 -0
- package/dist/data-list/DataList.js +43 -0
- package/dist/data-list/data-list.css +19 -0
- package/dist/date-picker/DatePicker.d.ts +95 -0
- package/dist/date-picker/DatePicker.js +237 -0
- package/dist/date-picker/date-picker.css +53 -0
- package/dist/dialog/Dialog.d.ts +98 -0
- package/dist/dialog/Dialog.js +92 -0
- package/dist/dialog/dialog.css +31 -0
- package/dist/divider/Divider.d.ts +27 -0
- package/dist/divider/Divider.js +8 -0
- package/dist/divider/divider.css +12 -0
- package/dist/editable/Editable.d.ts +47 -0
- package/dist/editable/Editable.js +51 -0
- package/dist/editable/editable.css +25 -0
- package/dist/editor/Editor.d.ts +59 -0
- package/dist/editor/Editor.js +220 -0
- package/dist/editor/editor.css +42 -0
- package/dist/editor/image.d.ts +1 -0
- package/dist/editor/image.js +23 -0
- package/dist/empty-state/EmptyState.d.ts +22 -0
- package/dist/empty-state/EmptyState.js +9 -0
- package/dist/empty-state/empty-state.css +14 -0
- package/dist/event-calendar/EventCalendar.d.ts +213 -0
- package/dist/event-calendar/EventCalendar.js +686 -0
- package/dist/event-calendar/dates.d.ts +8 -0
- package/dist/event-calendar/dates.js +21 -0
- package/dist/event-calendar/event-calendar.css +157 -0
- package/dist/field/Field.d.ts +66 -0
- package/dist/field/Field.js +23 -0
- package/dist/field/field.css +26 -0
- package/dist/file-upload/FileUpload.d.ts +60 -0
- package/dist/file-upload/FileUpload.js +102 -0
- package/dist/file-upload/file-upload.css +32 -0
- package/dist/format/FormatNumber.d.ts +42 -0
- package/dist/format/FormatNumber.js +59 -0
- package/dist/format/LocaleProvider.d.ts +33 -0
- package/dist/format/LocaleProvider.js +48 -0
- package/dist/format/format.d.ts +37 -0
- package/dist/format/format.js +74 -0
- package/dist/format/messages.d.ts +211 -0
- package/dist/format/messages.js +202 -0
- package/dist/highlight/Highlight.d.ts +11 -0
- package/dist/highlight/Highlight.js +14 -0
- package/dist/highlight/highlight.css +17 -0
- package/dist/hover-card/HoverCard.d.ts +47 -0
- package/dist/hover-card/HoverCard.js +18 -0
- package/dist/hover-card/hover-card.css +19 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +85 -0
- package/dist/input/Input.d.ts +41 -0
- package/dist/input/Input.js +48 -0
- package/dist/input/input.css +25 -0
- package/dist/kbd/Kbd.d.ts +19 -0
- package/dist/kbd/Kbd.js +20 -0
- package/dist/kbd/kbd.css +8 -0
- package/dist/menu/Menu.d.ts +223 -0
- package/dist/menu/Menu.js +275 -0
- package/dist/menu/menu.css +38 -0
- package/dist/native-select/NativeSelect.d.ts +36 -0
- package/dist/native-select/NativeSelect.js +51 -0
- package/dist/native-select/native-select.css +16 -0
- package/dist/number-input/NumberInput.d.ts +56 -0
- package/dist/number-input/NumberInput.js +67 -0
- package/dist/number-input/number-input.css +21 -0
- package/dist/page/Page.d.ts +30 -0
- package/dist/page/Page.js +9 -0
- package/dist/page/page.css +17 -0
- package/dist/pagination/Pagination.d.ts +31 -0
- package/dist/pagination/Pagination.js +58 -0
- package/dist/pagination/pagination.css +15 -0
- package/dist/password-input/PasswordInput.d.ts +47 -0
- package/dist/password-input/PasswordInput.js +35 -0
- package/dist/password-input/password-input.css +18 -0
- package/dist/pin-input/PinInput.d.ts +45 -0
- package/dist/pin-input/PinInput.js +22 -0
- package/dist/pin-input/pin-input.css +13 -0
- package/dist/popover/Popover.d.ts +65 -0
- package/dist/popover/Popover.js +26 -0
- package/dist/popover/popover.css +16 -0
- package/dist/progress/Progress.d.ts +56 -0
- package/dist/progress/Progress.js +28 -0
- package/dist/progress/progress.css +24 -0
- package/dist/prose/Prose.d.ts +23 -0
- package/dist/prose/Prose.js +10 -0
- package/dist/prose/prose.css +29 -0
- package/dist/radio-group/RadioGroup.d.ts +59 -0
- package/dist/radio-group/RadioGroup.js +37 -0
- package/dist/radio-group/radio-group.css +30 -0
- package/dist/rolling-number/RollingNumber.d.ts +27 -0
- package/dist/rolling-number/RollingNumber.js +18 -0
- package/dist/rolling-number/rolling-number.css +10 -0
- package/dist/segment-group/Segmented.d.ts +68 -0
- package/dist/segment-group/Segmented.js +79 -0
- package/dist/segment-group/segment-group.css +25 -0
- package/dist/select/ListSelect.d.ts +5 -0
- package/dist/select/ListSelect.js +82 -0
- package/dist/select/SearchSelect.d.ts +43 -0
- package/dist/select/SearchSelect.js +219 -0
- package/dist/select/Select.d.ts +147 -0
- package/dist/select/Select.js +32 -0
- package/dist/select/items.d.ts +17 -0
- package/dist/select/items.js +14 -0
- package/dist/select/search-select.css +45 -0
- package/dist/select/select.css +52 -0
- package/dist/sheet/Sheet.d.ts +88 -0
- package/dist/sheet/Sheet.js +66 -0
- package/dist/sheet/sheet.css +44 -0
- package/dist/sidebar/NavGroup.d.ts +29 -0
- package/dist/sidebar/NavGroup.js +78 -0
- package/dist/sidebar/NavItem.d.ts +33 -0
- package/dist/sidebar/NavItem.js +50 -0
- package/dist/sidebar/NavSection.d.ts +20 -0
- package/dist/sidebar/NavSection.js +44 -0
- package/dist/sidebar/Sidebar.d.ts +65 -0
- package/dist/sidebar/Sidebar.js +59 -0
- package/dist/sidebar/SidebarUser.d.ts +31 -0
- package/dist/sidebar/SidebarUser.js +14 -0
- package/dist/sidebar/context.d.ts +13 -0
- package/dist/sidebar/context.js +6 -0
- package/dist/sidebar/sidebar.css +109 -0
- package/dist/skeleton/Skeleton.d.ts +25 -0
- package/dist/skeleton/Skeleton.js +8 -0
- package/dist/skeleton/skeleton.css +13 -0
- package/dist/slider/Slider.d.ts +70 -0
- package/dist/slider/Slider.js +65 -0
- package/dist/slider/slider.css +17 -0
- package/dist/sortable-list/SortableList.d.ts +46 -0
- package/dist/sortable-list/SortableList.js +85 -0
- package/dist/sortable-list/sortable-list.css +25 -0
- package/dist/spinner/Spinner.d.ts +18 -0
- package/dist/spinner/Spinner.js +11 -0
- package/dist/spinner/spinner.css +9 -0
- package/dist/stat/Stat.d.ts +42 -0
- package/dist/stat/Stat.js +21 -0
- package/dist/stat/Stats.d.ts +23 -0
- package/dist/stat/Stats.js +8 -0
- package/dist/stat/stat.css +34 -0
- package/dist/swatch-picker/SwatchPicker.d.ts +45 -0
- package/dist/swatch-picker/SwatchPicker.js +54 -0
- package/dist/swatch-picker/swatch-picker.css +38 -0
- package/dist/switch/Switch.d.ts +39 -0
- package/dist/switch/Switch.js +17 -0
- package/dist/switch/switch.css +13 -0
- package/dist/table/Table.d.ts +90 -0
- package/dist/table/Table.js +145 -0
- package/dist/table/table.css +44 -0
- package/dist/tabs/Tabs.d.ts +87 -0
- package/dist/tabs/Tabs.js +90 -0
- package/dist/tabs/tabs.css +53 -0
- package/dist/tags-input/TagsInput.d.ts +69 -0
- package/dist/tags-input/TagsInput.js +95 -0
- package/dist/tags-input/tags-input.css +37 -0
- package/dist/textarea/Textarea.d.ts +24 -0
- package/dist/textarea/Textarea.js +51 -0
- package/dist/textarea/textarea.css +14 -0
- package/dist/theme-toggle/ThemeToggle.d.ts +49 -0
- package/dist/theme-toggle/ThemeToggle.js +71 -0
- package/dist/theme-toggle/theme-toggle.css +23 -0
- package/dist/theme.css +119 -0
- package/dist/time-input/TimeInput.d.ts +25 -0
- package/dist/time-input/TimeInput.js +20 -0
- package/dist/timeline/Timeline.d.ts +50 -0
- package/dist/timeline/Timeline.js +15 -0
- package/dist/timeline/timeline.css +29 -0
- package/dist/toast/Toaster.d.ts +36 -0
- package/dist/toast/Toaster.js +25 -0
- package/dist/toast/toast.css +33 -0
- package/dist/tooltip/Tooltip.d.ts +55 -0
- package/dist/tooltip/Tooltip.js +48 -0
- package/dist/tooltip/tooltip.css +18 -0
- package/dist/tree-view/TreeView.d.ts +86 -0
- package/dist/tree-view/TreeView.js +74 -0
- package/dist/tree-view/tree-view.css +29 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +59 -0
- 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
|
+
}
|