@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,71 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* A light, dark and system switch, driving `colorScheme`, so every toggle on
|
|
4
|
+
* the page stays in step and the choice is saved.
|
|
5
|
+
*
|
|
6
|
+
* <ThemeToggle />
|
|
7
|
+
* <ThemeToggle variant="menu" />
|
|
8
|
+
* <ThemeToggle mobile="menu" />
|
|
9
|
+
* <ThemeToggle variant="toggle" />
|
|
10
|
+
*
|
|
11
|
+
* - `variant`: 'segmented' (default: icon-only buttons with tooltips), 'menu'
|
|
12
|
+
* (one button showing the current choice, opening a menu of all of them) or
|
|
13
|
+
* 'toggle' (one button flipping between light and dark)
|
|
14
|
+
* - `mobile`: the variant below `breakpoint` (e.g. 'menu' where space is tight)
|
|
15
|
+
* - `breakpoint`: the width in px `mobile` applies under (768)
|
|
16
|
+
* - `options`: which schemes to offer, in order (system, light, dark)
|
|
17
|
+
* - `system`: false leaves out 'system' whatever `options` says
|
|
18
|
+
* - `labels`: the tooltips and accessible names, `{ light, dark, system }`
|
|
19
|
+
* - `label`: names the group for assistive tech
|
|
20
|
+
* - `size`: 'sm', 'md' (default) or 'lg', for every variant
|
|
21
|
+
* - `placement`: where the menu variant opens ('bottom-end', or 'bottom' with `iconOnly`)
|
|
22
|
+
* - `keepOpen`: the menu variant stays open after a choice
|
|
23
|
+
* - `iconOnly`: the menu variant lists just the icons, the current one highlighted
|
|
24
|
+
* - `onChange`: called with 'light', 'dark' or 'system'
|
|
25
|
+
*/
|
|
26
|
+
import { useEffect, useState } from 'react';
|
|
27
|
+
import { Menu } from '../menu/Menu.js';
|
|
28
|
+
import 'iconify-icon';
|
|
29
|
+
import { Segmented } from '../segment-group/Segmented.js';
|
|
30
|
+
import { Tooltip } from '../tooltip/Tooltip.js';
|
|
31
|
+
import { useColorScheme } from '../color-scheme.js';
|
|
32
|
+
import { cx, partsOf } from '../utils.js';
|
|
33
|
+
import { useMessages } from '../format/LocaleProvider.js';
|
|
34
|
+
import '../theme.css';
|
|
35
|
+
import './theme-toggle.css';
|
|
36
|
+
const icons = { light: 'lucide:sun', dark: 'lucide:moon', system: 'lucide:monitor' };
|
|
37
|
+
// Whether the viewport is under `breakpoint` (only watched when `on`).
|
|
38
|
+
function useNarrow(on, breakpoint) {
|
|
39
|
+
const [narrow, setNarrow] = useState(false);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!on)
|
|
42
|
+
return;
|
|
43
|
+
const query = matchMedia(`(max-width: ${breakpoint - 1}px)`);
|
|
44
|
+
const update = () => setNarrow(query.matches);
|
|
45
|
+
update();
|
|
46
|
+
query.addEventListener('change', update);
|
|
47
|
+
return () => query.removeEventListener('change', update);
|
|
48
|
+
}, [on, breakpoint]);
|
|
49
|
+
return narrow;
|
|
50
|
+
}
|
|
51
|
+
export function ThemeToggle({ variant = 'segmented', mobile = null, breakpoint = 768, options = ['system', 'light', 'dark'], system = true, labels: labelsProp, label: labelProp, placement = null, size = 'md', keepOpen = false, iconOnly = false, onChange, className, classes, ...rest }) {
|
|
52
|
+
const t = useMessages();
|
|
53
|
+
const labels = labelsProp ?? t.themeToggle;
|
|
54
|
+
const label = labelProp ?? t.themeToggle.label;
|
|
55
|
+
const part = partsOf('theme-toggle', classes);
|
|
56
|
+
const { scheme, dark, setScheme } = useColorScheme();
|
|
57
|
+
const narrow = useNarrow(!!mobile, breakpoint);
|
|
58
|
+
const mode = mobile && narrow ? mobile : variant;
|
|
59
|
+
const offersSystem = system && options.includes('system');
|
|
60
|
+
const items = options.filter((s) => icons[s] && (s !== 'system' || system)).map((s) => ({ value: s, label: labels[s], icon: icons[s] }));
|
|
61
|
+
// Without the system option, show what the OS currently resolves to.
|
|
62
|
+
const value = offersSystem ? scheme : dark ? 'dark' : 'light';
|
|
63
|
+
const shown = dark ? 'dark' : 'light';
|
|
64
|
+
function pick(next) {
|
|
65
|
+
if (next == null || next === value)
|
|
66
|
+
return;
|
|
67
|
+
setScheme(next);
|
|
68
|
+
onChange?.(next);
|
|
69
|
+
}
|
|
70
|
+
return (_jsx("div", { ...rest, ...part('root'), className: cx(className, classes?.root), role: "group", "aria-label": label, "data-variant": mode, "data-size": size, "data-icon-only": (iconOnly && mode === 'menu') || undefined, children: mode === 'menu' ? (_jsx(Menu, { placement: placement ?? (iconOnly ? 'bottom' : 'bottom-end'), keepOpen: keepOpen, trigger: (_jsx("button", { ...part('trigger'), type: "button", "aria-label": `${label}: ${labels[value]}`, children: _jsx("iconify-icon", { ...part('trigger-icon'), icon: icons[value], "aria-hidden": "true" }) })), children: _jsx(Menu.RadioGroup, { value: value, onValueChange: (next) => pick(next), children: items.map((item) => _jsx(Menu.Radio, { value: item.value, icon: item.icon, children: item.label }, item.value)) }) })) : mode === 'toggle' ? (_jsx(Tooltip, { content: labels[shown], children: _jsx("button", { ...part('trigger'), type: "button", "aria-label": labels.dark, "aria-pressed": dark, onClick: () => pick(dark ? 'light' : 'dark'), children: _jsx("iconify-icon", { ...part('trigger-icon'), icon: icons[shown], "aria-hidden": "true" }) }) })) : (_jsx(Segmented, { iconOnly: true, size: size, items: items, value: value, onValueChange: pick })) }));
|
|
71
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
@layer ui {
|
|
2
|
+
[data-scope=theme-toggle][data-part=root] { display: inline-block;
|
|
3
|
+
--tt-h: var(--ui-control-height); --tt-icon: 16px;
|
|
4
|
+
&[data-size=sm] { --tt-h: var(--ui-control-height-sm); --tt-icon: 15px; }
|
|
5
|
+
&[data-size=lg] { --tt-h: var(--ui-control-height-lg); --tt-icon: 18px; } }
|
|
6
|
+
/* The menu and toggle variants: one square button, like a segmented item. */
|
|
7
|
+
[data-scope=theme-toggle][data-part=trigger] { display: inline-flex; align-items: center; justify-content: center; width: var(--tt-h); height: var(--tt-h);
|
|
8
|
+
padding: 0; box-sizing: border-box; border: 1px solid var(--ui-border); background: var(--ui-surface); color: var(--ui-text);
|
|
9
|
+
border-radius: var(--ui-radius); cursor: pointer;
|
|
10
|
+
&:hover, &[data-state=open] { background: var(--ui-hover); }
|
|
11
|
+
&:focus-visible { outline: 2px solid var(--ui-ring-soft); } }
|
|
12
|
+
[data-scope=theme-toggle][data-part=trigger-icon] { display: block; font-size: var(--tt-icon); }
|
|
13
|
+
/* Icons only: a narrow column, the current scheme highlighted instead of
|
|
14
|
+
dotted, names kept for screen readers. */
|
|
15
|
+
[data-scope=theme-toggle][data-icon-only] {
|
|
16
|
+
& [data-scope=menu][data-part=content] { min-width: 0; padding: 0.25rem; }
|
|
17
|
+
& [data-scope=menu][data-part=item] { justify-content: center; width: var(--tt-h); height: var(--tt-h); padding: 0; box-sizing: border-box;
|
|
18
|
+
&[data-state=checked] { background: var(--ui-hover); }
|
|
19
|
+
&[data-state=checked] [data-part=icon] { color: var(--ui-text); } }
|
|
20
|
+
& [data-scope=menu][data-part=indicator] { display: none; }
|
|
21
|
+
& [data-scope=menu][data-part=icon] { font-size: var(--tt-icon); }
|
|
22
|
+
& [data-scope=menu][data-part=label] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } }
|
|
23
|
+
}
|
package/dist/theme.css
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* Design tokens (imba-ui's), and a few base rules. Override any token in your
|
|
2
|
+
app, on :root for everything or on an element for what's inside it:
|
|
3
|
+
|
|
4
|
+
:root { --ui-accent: #e11d48; }
|
|
5
|
+
<DatePicker style="--ui-radius: 12px" />
|
|
6
|
+
|
|
7
|
+
Dark values apply under html.dark or [data-theme=dark].
|
|
8
|
+
|
|
9
|
+
All the library's CSS sits in @layer ui, so your CSS (and Tailwind's
|
|
10
|
+
utilities) beat it without !important. With Tailwind, declare the order
|
|
11
|
+
first in your app's CSS, so its reset stays below the library:
|
|
12
|
+
|
|
13
|
+
@layer theme, base, ui, components, utilities;
|
|
14
|
+
@import "tailwindcss"; */
|
|
15
|
+
|
|
16
|
+
@layer ui {
|
|
17
|
+
:root {
|
|
18
|
+
--ui-text: #18181b;
|
|
19
|
+
--ui-muted: #71717a;
|
|
20
|
+
--ui-surface: white;
|
|
21
|
+
--ui-canvas: #f2f4f7;
|
|
22
|
+
/* The inset app shell: its sidebar (a lighter shade of the canvas) and the frame round the page. */
|
|
23
|
+
--ui-sidebar-bg: #f8f9fb;
|
|
24
|
+
--ui-frame: white;
|
|
25
|
+
--ui-border: #e4e4e7;
|
|
26
|
+
--ui-hover: #f4f4f5;
|
|
27
|
+
/* Loading placeholders: visible on the canvas and on cards alike. */
|
|
28
|
+
--ui-skeleton: #e4e4e7;
|
|
29
|
+
--ui-accent: #2563eb;
|
|
30
|
+
--ui-accent-text: white;
|
|
31
|
+
--ui-accent-soft: #dbeafe;
|
|
32
|
+
--ui-accent-soft-text: #1e40af;
|
|
33
|
+
/* Selected text, and Highlight's marks. */
|
|
34
|
+
--ui-selection: color-mix(in srgb, var(--ui-accent) 24%, transparent);
|
|
35
|
+
--ui-highlight: color-mix(in srgb, var(--ui-accent) 22%, transparent);
|
|
36
|
+
--ui-ring: #3b82f6;
|
|
37
|
+
--ui-ring-soft: #3b82f633;
|
|
38
|
+
--ui-danger: #dc2626;
|
|
39
|
+
--ui-success: #16a34a;
|
|
40
|
+
/* Chart series, in order (the first is the accent). */
|
|
41
|
+
--ui-chart-1: var(--ui-accent);
|
|
42
|
+
--ui-chart-2: #14b8a6;
|
|
43
|
+
--ui-chart-3: #f59e0b;
|
|
44
|
+
--ui-chart-4: #f43f5e;
|
|
45
|
+
--ui-chart-5: #8b5cf6;
|
|
46
|
+
--ui-radius: 6px;
|
|
47
|
+
--ui-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.25);
|
|
48
|
+
--ui-card-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
|
|
49
|
+
--ui-font: inherit;
|
|
50
|
+
/* One height for buttons and inputs, so they line up in a row. */
|
|
51
|
+
--ui-control-height: 2.25rem;
|
|
52
|
+
--ui-control-height-sm: 2rem;
|
|
53
|
+
--ui-control-height-lg: 2.75rem;
|
|
54
|
+
/* Sidebar and its items. */
|
|
55
|
+
--ui-sidebar-width: 15rem;
|
|
56
|
+
--ui-sidebar-rail-width: 3.5rem;
|
|
57
|
+
--ui-sidebar-item-height: 2.125rem;
|
|
58
|
+
--ui-sidebar-item-padding: 0 0.5rem 0 0.75rem;
|
|
59
|
+
--ui-sidebar-item-radius: var(--ui-radius);
|
|
60
|
+
--ui-sidebar-item-text: var(--ui-muted);
|
|
61
|
+
/* The sidebar itself (outside the inset layout, which uses --ui-sidebar-bg),
|
|
62
|
+
its section headings, its own icon buttons, and hovered items' text.
|
|
63
|
+
With the item tokens, enough for a coloured sidebar. */
|
|
64
|
+
--ui-sidebar-surface: var(--ui-surface);
|
|
65
|
+
--ui-sidebar-heading: var(--ui-muted);
|
|
66
|
+
--ui-sidebar-icon: var(--ui-muted);
|
|
67
|
+
--ui-sidebar-hover-text: var(--ui-text);
|
|
68
|
+
--ui-sidebar-hover: var(--ui-hover);
|
|
69
|
+
--ui-sidebar-active: var(--ui-hover);
|
|
70
|
+
--ui-sidebar-active-text: var(--ui-text);
|
|
71
|
+
--ui-sidebar-active-weight: 500;
|
|
72
|
+
--ui-sidebar-active-shadow: none;
|
|
73
|
+
/* The inset layout's sidebar is grey, so its hovered and current items go white. */
|
|
74
|
+
--ui-sidebar-inset-hover: color-mix(in srgb, var(--ui-surface) 75%, transparent);
|
|
75
|
+
--ui-sidebar-inset-active: var(--ui-surface);
|
|
76
|
+
--ui-sidebar-inset-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
:root.dark, [data-theme=dark] {
|
|
80
|
+
--ui-text: #fafafa;
|
|
81
|
+
--ui-muted: #a1a1aa;
|
|
82
|
+
--ui-surface: #1c1c21;
|
|
83
|
+
--ui-canvas: #141418;
|
|
84
|
+
--ui-sidebar-bg: #0c0c0e;
|
|
85
|
+
--ui-frame: #0c0c0e;
|
|
86
|
+
--ui-border: #2b2b31;
|
|
87
|
+
--ui-hover: #27272d;
|
|
88
|
+
--ui-skeleton: #303036;
|
|
89
|
+
--ui-accent: #3b82f6;
|
|
90
|
+
--ui-accent-soft: #1e3a8a;
|
|
91
|
+
--ui-accent-soft-text: #bfdbfe;
|
|
92
|
+
--ui-selection: color-mix(in srgb, var(--ui-accent) 40%, transparent);
|
|
93
|
+
--ui-highlight: color-mix(in srgb, var(--ui-accent) 35%, transparent);
|
|
94
|
+
--ui-ring: #60a5fa;
|
|
95
|
+
--ui-ring-soft: #60a5fa33;
|
|
96
|
+
--ui-danger: #f87171;
|
|
97
|
+
--ui-success: #4ade80;
|
|
98
|
+
--ui-chart-2: #2dd4bf;
|
|
99
|
+
--ui-chart-3: #fbbf24;
|
|
100
|
+
--ui-chart-4: #fb7185;
|
|
101
|
+
--ui-chart-5: #a78bfa;
|
|
102
|
+
--ui-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.6);
|
|
103
|
+
/* Shadows don't read on dark; borders do the work. */
|
|
104
|
+
--ui-card-shadow: none;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
::selection { background: var(--ui-selection); }
|
|
108
|
+
|
|
109
|
+
/* Zag hides closed parts with `hidden`; that beats any display a component sets. */
|
|
110
|
+
[data-scope][hidden] { display: none !important; }
|
|
111
|
+
[data-scope]:focus:not(:focus-visible) { outline: none; }
|
|
112
|
+
|
|
113
|
+
@media (prefers-reduced-motion: reduce) {
|
|
114
|
+
[data-scope][data-part] { animation: none !important; transition: none !important; }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@keyframes ui-spin { to { rotate: 360deg; } }
|
|
118
|
+
@keyframes ui-skeleton-pulse { 50% { opacity: 0.5; } }
|
|
119
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A time of day, typed or picked with the browser's own time control (a wheel
|
|
3
|
+
* on phones), as 'HH:mm'.
|
|
4
|
+
*
|
|
5
|
+
* <TimeInput label="Starts" value={time} onValueChange={setTime} step={15} min="08:00" max="18:00" />
|
|
6
|
+
*
|
|
7
|
+
* Inside a Field it takes the field's label, hint and error. (For booking
|
|
8
|
+
* from free slots, show the slots instead: a free time picked here can clash.)
|
|
9
|
+
*
|
|
10
|
+
* - `value`, `defaultValue`: 'HH:mm' (or null); `onValueChange`: called with it
|
|
11
|
+
* - `step`: minutes between times (1); the arrow keys move by it
|
|
12
|
+
* - `min`, `max`: 'HH:mm'
|
|
13
|
+
* - `icon`: an Iconify name before it ('lucide:clock'; null for none)
|
|
14
|
+
* - and Input's props: `label`, `size`, `disabled`, `name`…
|
|
15
|
+
*/
|
|
16
|
+
import { type InputProps } from '../input/Input.js';
|
|
17
|
+
export type TimeInputProps = Omit<InputProps, 'type' | 'value' | 'defaultValue' | 'onValueChange' | 'step' | 'min' | 'max'> & {
|
|
18
|
+
value?: string | null;
|
|
19
|
+
defaultValue?: string | null;
|
|
20
|
+
onValueChange?: (value: string | null) => void;
|
|
21
|
+
step?: number;
|
|
22
|
+
min?: string;
|
|
23
|
+
max?: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function TimeInput({ value, defaultValue, onValueChange, step, icon, ...rest }: TimeInputProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* A time of day, typed or picked with the browser's own time control (a wheel
|
|
4
|
+
* on phones), as 'HH:mm'.
|
|
5
|
+
*
|
|
6
|
+
* <TimeInput label="Starts" value={time} onValueChange={setTime} step={15} min="08:00" max="18:00" />
|
|
7
|
+
*
|
|
8
|
+
* Inside a Field it takes the field's label, hint and error. (For booking
|
|
9
|
+
* from free slots, show the slots instead: a free time picked here can clash.)
|
|
10
|
+
*
|
|
11
|
+
* - `value`, `defaultValue`: 'HH:mm' (or null); `onValueChange`: called with it
|
|
12
|
+
* - `step`: minutes between times (1); the arrow keys move by it
|
|
13
|
+
* - `min`, `max`: 'HH:mm'
|
|
14
|
+
* - `icon`: an Iconify name before it ('lucide:clock'; null for none)
|
|
15
|
+
* - and Input's props: `label`, `size`, `disabled`, `name`…
|
|
16
|
+
*/
|
|
17
|
+
import { Input } from '../input/Input.js';
|
|
18
|
+
export function TimeInput({ value, defaultValue, onValueChange, step = 1, icon = 'lucide:clock', ...rest }) {
|
|
19
|
+
return (_jsx(Input, { ...rest, type: "time", icon: icon, step: step * 60, ...(value !== undefined && { value: value ?? '' }), ...(defaultValue != null && { defaultValue }), onValueChange: (next) => onValueChange?.(next || null) }));
|
|
20
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Events down a line, e.g. a student's history.
|
|
3
|
+
*
|
|
4
|
+
* <Timeline items={[
|
|
5
|
+
* { title: 'Lesson booked', time: '2 Oct', icon: 'lucide:calendar-plus' },
|
|
6
|
+
* { title: 'Invoice paid', time: '1 Oct', description: '£120', color: 'success' },
|
|
7
|
+
* ]} />
|
|
8
|
+
*
|
|
9
|
+
* Or as markup, when an event needs more than text:
|
|
10
|
+
*
|
|
11
|
+
* <Timeline>
|
|
12
|
+
* <Timeline.Item title="Note added" time="30 Sep" icon="lucide:notebook-pen"><Card>…</Card></Timeline.Item>
|
|
13
|
+
* </Timeline>
|
|
14
|
+
*
|
|
15
|
+
* - `items`: objects with a Timeline.Item's props (only `title` is needed)
|
|
16
|
+
* - `variant`: 'line' (default) or 'cards' (each item's content in a card)
|
|
17
|
+
* - `size`: 'sm' or 'md' (default)
|
|
18
|
+
* - Timeline.Item (also TimelineItem): `title`; `time` (text, so format it
|
|
19
|
+
* first); `description`, a line under the title; `icon`, an Iconify name in
|
|
20
|
+
* a circle (a dot without one); `color` tints the marker: 'accent',
|
|
21
|
+
* 'success', 'warning' or 'danger' (grey by default). Its content shows
|
|
22
|
+
* under the title
|
|
23
|
+
*/
|
|
24
|
+
import type { HTMLAttributes, LiHTMLAttributes, ReactNode } from 'react';
|
|
25
|
+
import 'iconify-icon';
|
|
26
|
+
import { type Classes } from '../utils.js';
|
|
27
|
+
import '../theme.css';
|
|
28
|
+
import './timeline.css';
|
|
29
|
+
type ItemClasses = Classes<'item' | 'marker' | 'icon' | 'dot' | 'body' | 'header' | 'heading' | 'time' | 'description' | 'content'>;
|
|
30
|
+
export type TimelineItemProps = Omit<LiHTMLAttributes<HTMLLIElement>, 'title' | 'color'> & {
|
|
31
|
+
title?: ReactNode;
|
|
32
|
+
time?: ReactNode;
|
|
33
|
+
description?: ReactNode;
|
|
34
|
+
icon?: string | null;
|
|
35
|
+
color?: 'accent' | 'success' | 'warning' | 'danger' | null;
|
|
36
|
+
classes?: ItemClasses;
|
|
37
|
+
};
|
|
38
|
+
export type TimelineProps = HTMLAttributes<HTMLDivElement> & {
|
|
39
|
+
items?: TimelineItemProps[] | null;
|
|
40
|
+
variant?: 'line' | 'cards';
|
|
41
|
+
size?: 'sm' | 'md';
|
|
42
|
+
classes?: Classes<'root' | 'list'>;
|
|
43
|
+
};
|
|
44
|
+
declare function TimelineShortcut({ items, variant, size, className, classes, children, ...rest }: TimelineProps): import("react").JSX.Element;
|
|
45
|
+
/** One event in a Timeline; its content shows under the title. */
|
|
46
|
+
export declare function TimelineItem({ title, time, description, icon, color, className, classes, children, ...rest }: TimelineItemProps): import("react").JSX.Element;
|
|
47
|
+
export declare const Timeline: typeof TimelineShortcut & {
|
|
48
|
+
Item: typeof TimelineItem;
|
|
49
|
+
};
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import 'iconify-icon';
|
|
3
|
+
import { cx, partsOf, withParts } from '../utils.js';
|
|
4
|
+
import '../theme.css';
|
|
5
|
+
import './timeline.css';
|
|
6
|
+
function TimelineShortcut({ items = null, variant = 'line', size = 'md', className, classes, children, ...rest }) {
|
|
7
|
+
const part = partsOf('timeline', classes);
|
|
8
|
+
return (_jsx("div", { ...rest, ...part('root'), className: cx(className, classes?.root), "data-variant": variant, "data-size": size, children: _jsxs("ol", { ...part('list'), children: [(items ?? []).map((item, i) => _jsx(TimelineItem, { ...item }, i)), children] }) }));
|
|
9
|
+
}
|
|
10
|
+
/** One event in a Timeline; its content shows under the title. */
|
|
11
|
+
export function TimelineItem({ title = null, time = null, description = null, icon = null, color = null, className, classes, children, ...rest }) {
|
|
12
|
+
const part = partsOf('timeline', classes);
|
|
13
|
+
return (_jsxs("li", { ...rest, ...part('item'), className: cx(className, classes?.item), "data-color": color ?? 'neutral', children: [_jsx("div", { ...part('marker'), "aria-hidden": "true", children: icon ? _jsx("span", { ...part('icon'), children: _jsx("iconify-icon", { icon: icon }) }) : _jsx("span", { ...part('dot') }) }), _jsxs("div", { ...part('body'), children: [_jsxs("div", { ...part('header'), children: [title && _jsx("span", { ...part('heading'), children: title }), time && _jsx("time", { ...part('time'), children: time })] }), description && _jsx("div", { ...part('description'), children: description }), children && _jsx("div", { ...part('content'), children: children })] })] }));
|
|
14
|
+
}
|
|
15
|
+
export const Timeline = withParts(TimelineShortcut, { Item: TimelineItem });
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
@layer ui {
|
|
2
|
+
[data-scope=timeline][data-part=root] { display: block; width: 100%; color: var(--ui-text); font-family: var(--ui-font); font-size: 14px; }
|
|
3
|
+
[data-scope=timeline][data-part=list] { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
|
|
4
|
+
[data-scope=timeline][data-part=item] { display: flex; gap: 0.75rem; position: relative; --tint: var(--ui-muted); --tint-bg: var(--ui-hover);
|
|
5
|
+
&[data-color=accent] { --tint: var(--ui-accent); --tint-text: var(--ui-accent-soft-text); --tint-bg: var(--ui-accent-soft); }
|
|
6
|
+
&[data-color=success] { --tint: var(--ui-success); --tint-bg: color-mix(in srgb, var(--ui-success) 15%, var(--ui-surface)); }
|
|
7
|
+
&[data-color=warning] { --tint: #f59e0b; --tint-text: #b45309; --tint-bg: #fef3c7; }
|
|
8
|
+
&[data-color=danger] { --tint: var(--ui-danger); --tint-bg: color-mix(in srgb, var(--ui-danger) 12%, var(--ui-surface)); } }
|
|
9
|
+
/* The connector runs down the marker column to the next item, behind the markers, and stops at the last. */
|
|
10
|
+
[data-scope=timeline][data-part=marker] { position: relative; display: flex; justify-content: center; width: 2rem; flex-shrink: 0;
|
|
11
|
+
&::after { content: ''; position: absolute; top: 0; bottom: 0; left: calc(50% - 0.5px); width: 1px; background: var(--ui-border); }
|
|
12
|
+
[data-part=item]:last-child > &::after { display: none; } }
|
|
13
|
+
[data-scope=timeline][data-part=icon] { position: relative; z-index: 1; display: grid; place-items: center; width: 2rem; height: 2rem; box-sizing: border-box;
|
|
14
|
+
border-radius: 9999px; background: var(--tint-bg); color: var(--tint-text, var(--tint)); font-size: 15px; border: 1px solid var(--ui-border);
|
|
15
|
+
[data-color]:not([data-color=neutral]) > [data-part=marker] > & { border-color: transparent; }
|
|
16
|
+
[data-size=sm] & { width: 1.5rem; height: 1.5rem; font-size: 13px; } }
|
|
17
|
+
/* A dot, ringed in the page colour so the line doesn't touch it. */
|
|
18
|
+
[data-scope=timeline][data-part=dot] { position: relative; z-index: 1; width: 0.625rem; height: 0.625rem; margin-top: 0.625rem;
|
|
19
|
+
border-radius: 9999px; background: var(--tint); border: 3px solid var(--ui-surface); box-sizing: content-box; }
|
|
20
|
+
[data-scope=timeline][data-part=body] { flex-grow: 1; min-width: 0; padding: 0.375rem 0 1.5rem;
|
|
21
|
+
[data-size=sm] & { padding-bottom: 1rem; }
|
|
22
|
+
[data-variant=cards] & { padding: 0.75rem; margin-bottom: 0.75rem; border: 1px solid var(--ui-border);
|
|
23
|
+
border-radius: calc(var(--ui-radius) + 2px); background: var(--ui-surface); } }
|
|
24
|
+
[data-scope=timeline][data-part=header] { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; }
|
|
25
|
+
[data-scope=timeline][data-part=heading] { font-weight: 500; }
|
|
26
|
+
[data-scope=timeline][data-part=time] { font-size: 12px; color: var(--ui-muted); white-space: nowrap; }
|
|
27
|
+
[data-scope=timeline][data-part=description] { margin-top: 0.125rem; color: var(--ui-muted); }
|
|
28
|
+
[data-scope=timeline][data-part=content] { margin-top: 0.5rem; }
|
|
29
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The region toasts stack in, rendered at the end of <body>. Put one in the
|
|
3
|
+
* app layout, then call `toaster` from anywhere:
|
|
4
|
+
*
|
|
5
|
+
* <Toaster />
|
|
6
|
+
* toaster.success({ title: 'Lesson booked', description: 'Thursday at 16:00' })
|
|
7
|
+
* toaster.error({ title: 'Could not save' })
|
|
8
|
+
* const id = toaster.create({ type: 'loading', title: 'Uploading…' })
|
|
9
|
+
* toaster.update(id, { type: 'success', title: 'Uploaded' })
|
|
10
|
+
*
|
|
11
|
+
* Toasts pause while hovered or focused, Escape dismisses the focused one, and
|
|
12
|
+
* Alt+T jumps to them. An `action: { label, onClick }` adds a button.
|
|
13
|
+
*
|
|
14
|
+
* - `store`: a toaster from createToaster (the shared `toaster` by default)
|
|
15
|
+
* - `label`: the region's accessible name ('Notifications')
|
|
16
|
+
* - `render`: draws each toast yourself, given its data (inside Toast.Root,
|
|
17
|
+
* with Toast.Title, Description, ActionTrigger, CloseTrigger)
|
|
18
|
+
*/
|
|
19
|
+
import type { ReactNode } from 'react';
|
|
20
|
+
import { Toast, createToaster, type CreateToasterReturn, type ToastOptions } from '@ark-ui/react/toast';
|
|
21
|
+
import 'iconify-icon';
|
|
22
|
+
import { type Classes } from '../utils.js';
|
|
23
|
+
import '../theme.css';
|
|
24
|
+
import './toast.css';
|
|
25
|
+
declare const make: (options?: Partial<Parameters<typeof createToaster>[0]>) => CreateToasterReturn<ReactNode>;
|
|
26
|
+
export { make as createToaster };
|
|
27
|
+
export declare const toaster: CreateToasterReturn;
|
|
28
|
+
export type ToasterProps = {
|
|
29
|
+
store?: CreateToasterReturn;
|
|
30
|
+
label?: string;
|
|
31
|
+
render?: (toast: ToastOptions) => ReactNode;
|
|
32
|
+
className?: string;
|
|
33
|
+
classes?: Classes<'group' | 'root' | 'icon' | 'spinner' | 'text' | 'title' | 'description' | 'actionTrigger' | 'closeTrigger'>;
|
|
34
|
+
};
|
|
35
|
+
export declare function Toaster({ store, label: labelProp, render, className, classes }: ToasterProps): import("react").JSX.Element;
|
|
36
|
+
export { Toast };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Toaster as ArkToaster, Toast, createToaster } from '@ark-ui/react/toast';
|
|
3
|
+
import { Portal } from '@ark-ui/react/portal';
|
|
4
|
+
import 'iconify-icon';
|
|
5
|
+
import { cx, partsOf } from '../utils.js';
|
|
6
|
+
import { useMessages } from '../format/LocaleProvider.js';
|
|
7
|
+
import '../theme.css';
|
|
8
|
+
import './toast.css';
|
|
9
|
+
// Toasts: brief notifications stacked in a corner. The shared `toaster`
|
|
10
|
+
// stacks at bottom-end; make another with createToaster and pass it to a
|
|
11
|
+
// Toaster as `store`.
|
|
12
|
+
const make = (options = {}) => createToaster({ placement: 'bottom-end', overlap: true, max: 5, ...options });
|
|
13
|
+
export { make as createToaster };
|
|
14
|
+
export const toaster = make();
|
|
15
|
+
const icons = {
|
|
16
|
+
info: 'lucide:info', success: 'lucide:circle-check', warning: 'lucide:triangle-alert', error: 'lucide:circle-alert',
|
|
17
|
+
};
|
|
18
|
+
export function Toaster({ store = toaster, label: labelProp, render, className, classes }) {
|
|
19
|
+
const t = useMessages();
|
|
20
|
+
const label = labelProp ?? t.toaster.label;
|
|
21
|
+
const part = partsOf('toast', classes);
|
|
22
|
+
return (_jsx(Portal, { children: _jsx(ArkToaster, { toaster: store, label: label, ...part('group'), className: cx(className, classes?.group), children: (toast) => render ? _jsx(Toast.Root, { ...part('root'), children: render(toast) }) : (_jsxs(Toast.Root, { ...part('root'), children: [toast.type === 'loading' ? _jsx("span", { ...part('spinner'), "aria-hidden": "true" })
|
|
23
|
+
: toast.type && icons[toast.type] && _jsx("iconify-icon", { ...part('icon'), icon: icons[toast.type], "aria-hidden": "true" }), _jsxs("div", { ...part('text'), children: [toast.title && _jsx(Toast.Title, { ...part('title'), children: toast.title }), toast.description && _jsx(Toast.Description, { ...part('description'), children: toast.description })] }), toast.action && _jsx(Toast.ActionTrigger, { ...part('action-trigger'), children: toast.action.label }), _jsx(Toast.CloseTrigger, { ...part('close-trigger'), "aria-label": t.common.dismiss, children: _jsx("iconify-icon", { icon: "lucide:x" }) })] })) }) }));
|
|
24
|
+
}
|
|
25
|
+
export { Toast };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
@layer ui {
|
|
2
|
+
[data-scope=toast][data-part=root] {
|
|
3
|
+
display: flex; align-items: flex-start; gap: 0.75rem; width: 22rem; max-width: calc(100vw - 2rem); padding: 0.75rem 2.5rem 0.75rem 0.75rem; box-sizing: border-box;
|
|
4
|
+
background: var(--ui-surface); color: var(--ui-text); font-family: var(--ui-font); font-size: 14px;
|
|
5
|
+
border: 1px solid var(--ui-border); border-radius: calc(var(--ui-radius) + 2px); box-shadow: var(--ui-shadow); outline: none;
|
|
6
|
+
/* Zag positions each toast through these variables. Toasts behind the
|
|
7
|
+
front one shrink a little (Zag's own --scale would mirror them). */
|
|
8
|
+
translate: var(--x) var(--y); scale: calc(2 - (var(--base-scale, 1)));
|
|
9
|
+
z-index: var(--z-index); height: var(--height); opacity: var(--opacity);
|
|
10
|
+
transition: translate 400ms, scale 400ms, opacity 400ms, height 400ms;
|
|
11
|
+
transition-timing-function: cubic-bezier(0.21, 1.02, 0.73, 1);
|
|
12
|
+
&[data-state=closed] { transition: translate 400ms, scale 400ms, opacity 200ms; transition-timing-function: cubic-bezier(0.06, 0.71, 0.55, 1); }
|
|
13
|
+
&:focus-visible { outline: 2px solid var(--ui-ring-soft); }
|
|
14
|
+
}
|
|
15
|
+
[data-scope=toast][data-part=icon] { flex-shrink: 0; margin-top: 2px; font-size: 18px; color: var(--ui-muted);
|
|
16
|
+
[data-type=success] > & { color: #16a34a; }
|
|
17
|
+
[data-type=warning] > & { color: #d97706; }
|
|
18
|
+
[data-type=error] > & { color: var(--ui-danger); }
|
|
19
|
+
[data-type=info] > & { color: var(--ui-accent); } }
|
|
20
|
+
[data-scope=toast][data-part=spinner] { display: block; width: 1rem; height: 1rem; margin-top: 2px; box-sizing: border-box; flex-shrink: 0;
|
|
21
|
+
border: 2px solid currentColor; border-top-color: transparent; border-radius: 9999px; animation: ui-spin 0.7s linear infinite; }
|
|
22
|
+
[data-scope=toast][data-part=text] { flex: 1; min-width: 0; }
|
|
23
|
+
[data-scope=toast][data-part=title] { font-weight: 600; }
|
|
24
|
+
[data-scope=toast][data-part=description] { color: var(--ui-muted);
|
|
25
|
+
[data-part=title] + & { margin-top: 0.125rem; } }
|
|
26
|
+
[data-scope=toast][data-part=action-trigger] { align-self: center; flex-shrink: 0; height: 1.75rem; padding: 0 0.625rem;
|
|
27
|
+
border: 1px solid var(--ui-border); background: var(--ui-surface); color: var(--ui-text); border-radius: 6px;
|
|
28
|
+
font: inherit; font-size: 12px; font-weight: 500; cursor: pointer;
|
|
29
|
+
&:hover { background: var(--ui-hover); } }
|
|
30
|
+
[data-scope=toast][data-part=close-trigger] { position: absolute; top: 0.5rem; right: 0.5rem; display: grid; place-items: center; width: 1.75rem; height: 1.75rem;
|
|
31
|
+
border: none; background: transparent; border-radius: 4px; color: var(--ui-muted); cursor: pointer; font-size: 14px;
|
|
32
|
+
&:hover { background: var(--ui-hover); color: var(--ui-text); } }
|
|
33
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tooltip: a short hint on hover and keyboard focus.
|
|
3
|
+
*
|
|
4
|
+
* <Tooltip content="Archive"><Button icon="lucide:archive" aria-label="Archive" /></Tooltip>
|
|
5
|
+
* <Tooltip content="Ada Lovelace">Ada</Tooltip>
|
|
6
|
+
*
|
|
7
|
+
* - children: the element it describes, which gets the trigger's props (a
|
|
8
|
+
* component must pass them and its ref on, as Button does); text, several
|
|
9
|
+
* elements or a disabled one (which fires no events) are wrapped in a span
|
|
10
|
+
* that takes them
|
|
11
|
+
* - `content`: what it says (text or markup)
|
|
12
|
+
* - `placement`: 'top' (default), 'bottom-start', …
|
|
13
|
+
* - `openDelay` (0), `closeDelay` in ms; `interactive`: stays open while the
|
|
14
|
+
* pointer is over it; `disabled`
|
|
15
|
+
* - `open`, `defaultOpen`; `onOpenChange`: called with the new open state
|
|
16
|
+
*
|
|
17
|
+
* It closes on Escape, click or scroll. With `interactive` it stays open while
|
|
18
|
+
* the pointer is over it, so its text can be selected.
|
|
19
|
+
*
|
|
20
|
+
* By hand, with Ark's parts:
|
|
21
|
+
*
|
|
22
|
+
* <Tooltip.Root>
|
|
23
|
+
* <Tooltip.Trigger asChild><Button>Save</Button></Tooltip.Trigger>
|
|
24
|
+
* <Tooltip.Positioner><Tooltip.Content><Tooltip.Arrow><Tooltip.ArrowTip /></Tooltip.Arrow>Saves a draft</Tooltip.Content></Tooltip.Positioner>
|
|
25
|
+
* </Tooltip.Root>
|
|
26
|
+
*/
|
|
27
|
+
import { type ReactNode } from 'react';
|
|
28
|
+
import { Tooltip as Ark, type TooltipOpenChangeDetails } from '@ark-ui/react/tooltip';
|
|
29
|
+
import { type Classes } from '../utils.js';
|
|
30
|
+
import '../theme.css';
|
|
31
|
+
import './tooltip.css';
|
|
32
|
+
export type TooltipProps = Omit<Ark.ContentProps, 'content' | 'children'> & {
|
|
33
|
+
content?: ReactNode;
|
|
34
|
+
placement?: NonNullable<Ark.RootProps['positioning']>['placement'];
|
|
35
|
+
openDelay?: number;
|
|
36
|
+
closeDelay?: number;
|
|
37
|
+
interactive?: boolean;
|
|
38
|
+
disabled?: boolean;
|
|
39
|
+
open?: boolean;
|
|
40
|
+
defaultOpen?: boolean;
|
|
41
|
+
onOpenChange?: (open: boolean, details: TooltipOpenChangeDetails) => void;
|
|
42
|
+
children?: ReactNode;
|
|
43
|
+
classes?: Classes<'trigger' | 'positioner' | 'content' | 'arrow' | 'arrowTip'>;
|
|
44
|
+
};
|
|
45
|
+
declare function TooltipShortcut({ content, placement, openDelay, closeDelay, interactive, disabled, open, defaultOpen, onOpenChange, className, classes, children, ...rest }: TooltipProps): import("react").JSX.Element;
|
|
46
|
+
export declare const Tooltip: typeof TooltipShortcut & {
|
|
47
|
+
Root: (props: Ark.RootProps) => import("react").JSX.Element;
|
|
48
|
+
Trigger: import("react").ForwardRefExoticComponent<Ark.TriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
49
|
+
Positioner: import("react").ForwardRefExoticComponent<Ark.PositionerProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
50
|
+
Content: import("react").ForwardRefExoticComponent<Ark.ContentProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
51
|
+
Arrow: import("react").ForwardRefExoticComponent<Ark.ArrowProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
52
|
+
ArrowTip: import("react").ForwardRefExoticComponent<Ark.ArrowTipProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
53
|
+
Context: (props: Ark.ContextProps) => ReactNode;
|
|
54
|
+
};
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* A tooltip: a short hint on hover and keyboard focus.
|
|
4
|
+
*
|
|
5
|
+
* <Tooltip content="Archive"><Button icon="lucide:archive" aria-label="Archive" /></Tooltip>
|
|
6
|
+
* <Tooltip content="Ada Lovelace">Ada</Tooltip>
|
|
7
|
+
*
|
|
8
|
+
* - children: the element it describes, which gets the trigger's props (a
|
|
9
|
+
* component must pass them and its ref on, as Button does); text, several
|
|
10
|
+
* elements or a disabled one (which fires no events) are wrapped in a span
|
|
11
|
+
* that takes them
|
|
12
|
+
* - `content`: what it says (text or markup)
|
|
13
|
+
* - `placement`: 'top' (default), 'bottom-start', …
|
|
14
|
+
* - `openDelay` (0), `closeDelay` in ms; `interactive`: stays open while the
|
|
15
|
+
* pointer is over it; `disabled`
|
|
16
|
+
* - `open`, `defaultOpen`; `onOpenChange`: called with the new open state
|
|
17
|
+
*
|
|
18
|
+
* It closes on Escape, click or scroll. With `interactive` it stays open while
|
|
19
|
+
* the pointer is over it, so its text can be selected.
|
|
20
|
+
*
|
|
21
|
+
* By hand, with Ark's parts:
|
|
22
|
+
*
|
|
23
|
+
* <Tooltip.Root>
|
|
24
|
+
* <Tooltip.Trigger asChild><Button>Save</Button></Tooltip.Trigger>
|
|
25
|
+
* <Tooltip.Positioner><Tooltip.Content><Tooltip.Arrow><Tooltip.ArrowTip /></Tooltip.Arrow>Saves a draft</Tooltip.Content></Tooltip.Positioner>
|
|
26
|
+
* </Tooltip.Root>
|
|
27
|
+
*/
|
|
28
|
+
import { isValidElement } from 'react';
|
|
29
|
+
import { Tooltip as Ark } from '@ark-ui/react/tooltip';
|
|
30
|
+
import { Portal } from '@ark-ui/react/portal';
|
|
31
|
+
import { cx, on, partsOf, withParts } from '../utils.js';
|
|
32
|
+
import '../theme.css';
|
|
33
|
+
import './tooltip.css';
|
|
34
|
+
function TooltipShortcut({ content = null, placement = 'top', openDelay = 0, closeDelay, interactive = false, disabled = false, open, defaultOpen, onOpenChange, className, classes, children, ...rest }) {
|
|
35
|
+
const part = partsOf('tooltip', classes);
|
|
36
|
+
return (_jsxs(Ark.Root, { openDelay: openDelay, interactive: interactive, disabled: disabled, lazyMount: true, unmountOnExit: true, positioning: { placement, strategy: 'fixed', gutter: 8 }, ...(closeDelay !== undefined && { closeDelay }), ...(open !== undefined && { open }), ...(defaultOpen !== undefined && { defaultOpen }), ...on('onOpenChange', onOpenChange, (details) => details.open), children: [isValidElement(children) && !children.props.disabled
|
|
37
|
+
? _jsx(Ark.Trigger, { asChild: true, children: children })
|
|
38
|
+
: _jsx(Ark.Trigger, { asChild: true, children: _jsx("span", { ...part('trigger'), children: children }) }), _jsx(Portal, { children: _jsx(Ark.Positioner, { ...part('positioner'), children: _jsxs(Ark.Content, { ...rest, ...part('content'), className: cx(className, classes?.content), children: [_jsx(Ark.Arrow, { ...part('arrow'), children: _jsx(Ark.ArrowTip, { ...part('arrow-tip') }) }), content] }) }) })] }));
|
|
39
|
+
}
|
|
40
|
+
export const Tooltip = withParts(TooltipShortcut, {
|
|
41
|
+
Root: Ark.Root,
|
|
42
|
+
Trigger: Ark.Trigger,
|
|
43
|
+
Positioner: Ark.Positioner,
|
|
44
|
+
Content: Ark.Content,
|
|
45
|
+
Arrow: Ark.Arrow,
|
|
46
|
+
ArrowTip: Ark.ArrowTip,
|
|
47
|
+
Context: Ark.Context,
|
|
48
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
@layer ui {
|
|
2
|
+
[data-scope=tooltip][data-part=trigger] { display: inline-flex; }
|
|
3
|
+
/* Zag copies the content's z-index onto its positioner. */
|
|
4
|
+
[data-scope=tooltip][data-part=content] {
|
|
5
|
+
z-index: 60; max-width: 16rem; padding: 0.25rem 0.5rem; white-space: normal; font-weight: 400;
|
|
6
|
+
background: var(--ui-text); color: var(--ui-surface); font-family: var(--ui-font); font-size: 12px; line-height: 1.4;
|
|
7
|
+
border-radius: calc(var(--ui-radius) - 2px); box-shadow: var(--ui-shadow);
|
|
8
|
+
--arrow-size: 8px; --arrow-background: var(--ui-text);
|
|
9
|
+
/* Slides in from the trigger's side. */
|
|
10
|
+
--dx: 0px; --dy: 0px;
|
|
11
|
+
&[data-side=top] { --dy: 4px; }
|
|
12
|
+
&[data-side=bottom] { --dy: -4px; }
|
|
13
|
+
&[data-side=left] { --dx: 4px; }
|
|
14
|
+
&[data-side=right] { --dx: -4px; }
|
|
15
|
+
&[data-state=open] { animation: ui-tooltip-in 140ms ease-out; }
|
|
16
|
+
}
|
|
17
|
+
@keyframes ui-tooltip-in { from { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(0.96); } }
|
|
18
|
+
}
|