@c9up/nebula 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +256 -0
- package/dist/adapters/css.d.ts +33 -0
- package/dist/adapters/css.js +67 -0
- package/dist/adapters/index.d.ts +16 -0
- package/dist/adapters/index.js +27 -0
- package/dist/adapters/tailwind.d.ts +25 -0
- package/dist/adapters/tailwind.js +122 -0
- package/dist/adapters/types.d.ts +49 -0
- package/dist/adapters/types.js +21 -0
- package/dist/adapters/unocss.d.ts +17 -0
- package/dist/adapters/unocss.js +107 -0
- package/dist/atoms/AspectRatio.d.ts +17 -0
- package/dist/atoms/AspectRatio.js +19 -0
- package/dist/atoms/Avatar.d.ts +23 -0
- package/dist/atoms/Avatar.js +46 -0
- package/dist/atoms/Badge.d.ts +25 -0
- package/dist/atoms/Badge.js +28 -0
- package/dist/atoms/Button.d.ts +58 -0
- package/dist/atoms/Button.js +67 -0
- package/dist/atoms/Checkbox.d.ts +41 -0
- package/dist/atoms/Checkbox.js +47 -0
- package/dist/atoms/Input.d.ts +36 -0
- package/dist/atoms/Input.js +50 -0
- package/dist/atoms/Kbd.d.ts +14 -0
- package/dist/atoms/Kbd.js +17 -0
- package/dist/atoms/Label.d.ts +24 -0
- package/dist/atoms/Label.js +26 -0
- package/dist/atoms/Marker.d.ts +42 -0
- package/dist/atoms/Marker.js +55 -0
- package/dist/atoms/NativeSelect.d.ts +43 -0
- package/dist/atoms/NativeSelect.js +81 -0
- package/dist/atoms/Progress.d.ts +21 -0
- package/dist/atoms/Progress.js +48 -0
- package/dist/atoms/ScrollArea.d.ts +28 -0
- package/dist/atoms/ScrollArea.js +37 -0
- package/dist/atoms/Separator.d.ts +16 -0
- package/dist/atoms/Separator.js +21 -0
- package/dist/atoms/Skeleton.d.ts +13 -0
- package/dist/atoms/Skeleton.js +18 -0
- package/dist/atoms/Slider.d.ts +30 -0
- package/dist/atoms/Slider.js +40 -0
- package/dist/atoms/Spinner.d.ts +17 -0
- package/dist/atoms/Spinner.js +20 -0
- package/dist/atoms/Switch.d.ts +24 -0
- package/dist/atoms/Switch.js +41 -0
- package/dist/atoms/Textarea.d.ts +28 -0
- package/dist/atoms/Textarea.js +36 -0
- package/dist/atoms/Toggle.d.ts +38 -0
- package/dist/atoms/Toggle.js +45 -0
- package/dist/atoms/index.d.ts +32 -0
- package/dist/atoms/index.js +32 -0
- package/dist/cli/add.d.ts +44 -0
- package/dist/cli/add.js +99 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/index.js +10 -0
- package/dist/cli/registry.d.ts +31 -0
- package/dist/cli/registry.js +83 -0
- package/dist/config.d.ts +81 -0
- package/dist/config.js +43 -0
- package/dist/configure.d.ts +34 -0
- package/dist/configure.js +85 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +41 -0
- package/dist/lib/children.d.ts +28 -0
- package/dist/lib/children.js +27 -0
- package/dist/lib/cn.d.ts +13 -0
- package/dist/lib/cn.js +13 -0
- package/dist/lib/cva.d.ts +83 -0
- package/dist/lib/cva.js +178 -0
- package/dist/lib/icons.d.ts +44 -0
- package/dist/lib/icons.js +79 -0
- package/dist/lib/id.d.ts +31 -0
- package/dist/lib/id.js +41 -0
- package/dist/lib/index.d.ts +12 -0
- package/dist/lib/index.js +12 -0
- package/dist/lib/motion.d.ts +31 -0
- package/dist/lib/motion.js +39 -0
- package/dist/lib/props.d.ts +48 -0
- package/dist/lib/props.js +59 -0
- package/dist/lib/styled.d.ts +31 -0
- package/dist/lib/styled.js +39 -0
- package/dist/molecules/Accordion.d.ts +35 -0
- package/dist/molecules/Accordion.js +117 -0
- package/dist/molecules/Alert.d.ts +30 -0
- package/dist/molecules/Alert.js +35 -0
- package/dist/molecules/Attachment.d.ts +39 -0
- package/dist/molecules/Attachment.js +103 -0
- package/dist/molecules/Breadcrumb.d.ts +29 -0
- package/dist/molecules/Breadcrumb.js +73 -0
- package/dist/molecules/Bubble.d.ts +56 -0
- package/dist/molecules/Bubble.js +92 -0
- package/dist/molecules/ButtonGroup.d.ts +23 -0
- package/dist/molecules/ButtonGroup.js +30 -0
- package/dist/molecules/Card.d.ts +17 -0
- package/dist/molecules/Card.js +17 -0
- package/dist/molecules/Collapsible.d.ts +40 -0
- package/dist/molecules/Collapsible.js +71 -0
- package/dist/molecules/Empty.d.ts +16 -0
- package/dist/molecules/Empty.js +16 -0
- package/dist/molecules/Field.d.ts +54 -0
- package/dist/molecules/Field.js +82 -0
- package/dist/molecules/InputGroup.d.ts +33 -0
- package/dist/molecules/InputGroup.js +35 -0
- package/dist/molecules/InputOTP.d.ts +33 -0
- package/dist/molecules/InputOTP.js +140 -0
- package/dist/molecules/Item.d.ts +41 -0
- package/dist/molecules/Item.js +48 -0
- package/dist/molecules/Message.d.ts +43 -0
- package/dist/molecules/Message.js +82 -0
- package/dist/molecules/Pagination.d.ts +41 -0
- package/dist/molecules/Pagination.js +144 -0
- package/dist/molecules/RadioGroup.d.ts +32 -0
- package/dist/molecules/RadioGroup.js +65 -0
- package/dist/molecules/Resizable.d.ts +37 -0
- package/dist/molecules/Resizable.js +136 -0
- package/dist/molecules/Table.d.ts +37 -0
- package/dist/molecules/Table.js +78 -0
- package/dist/molecules/Tabs.d.ts +37 -0
- package/dist/molecules/Tabs.js +109 -0
- package/dist/molecules/ToggleGroup.d.ts +37 -0
- package/dist/molecules/ToggleGroup.js +68 -0
- package/dist/molecules/Typography.d.ts +26 -0
- package/dist/molecules/Typography.js +67 -0
- package/dist/molecules/index.d.ts +37 -0
- package/dist/molecules/index.js +37 -0
- package/dist/organisms/AlertDialog.d.ts +40 -0
- package/dist/organisms/AlertDialog.js +104 -0
- package/dist/organisms/Calendar.d.ts +96 -0
- package/dist/organisms/Calendar.js +353 -0
- package/dist/organisms/Carousel.d.ts +29 -0
- package/dist/organisms/Carousel.js +128 -0
- package/dist/organisms/Chart.d.ts +55 -0
- package/dist/organisms/Chart.js +189 -0
- package/dist/organisms/Combobox.d.ts +41 -0
- package/dist/organisms/Combobox.js +102 -0
- package/dist/organisms/Command.d.ts +46 -0
- package/dist/organisms/Command.js +171 -0
- package/dist/organisms/CommandDialog.d.ts +42 -0
- package/dist/organisms/CommandDialog.js +116 -0
- package/dist/organisms/ContextMenu.d.ts +29 -0
- package/dist/organisms/ContextMenu.js +102 -0
- package/dist/organisms/DataTable.d.ts +50 -0
- package/dist/organisms/DataTable.js +257 -0
- package/dist/organisms/DatePicker.d.ts +34 -0
- package/dist/organisms/DatePicker.js +115 -0
- package/dist/organisms/DateRangePicker.d.ts +34 -0
- package/dist/organisms/DateRangePicker.js +122 -0
- package/dist/organisms/Dialog.d.ts +50 -0
- package/dist/organisms/Dialog.js +116 -0
- package/dist/organisms/Drawer.d.ts +40 -0
- package/dist/organisms/Drawer.js +131 -0
- package/dist/organisms/DropdownMenu.d.ts +30 -0
- package/dist/organisms/DropdownMenu.js +91 -0
- package/dist/organisms/Form.d.ts +114 -0
- package/dist/organisms/Form.js +151 -0
- package/dist/organisms/HoverCard.d.ts +30 -0
- package/dist/organisms/HoverCard.js +68 -0
- package/dist/organisms/Menubar.d.ts +28 -0
- package/dist/organisms/Menubar.js +147 -0
- package/dist/organisms/MessageScroller.d.ts +36 -0
- package/dist/organisms/MessageScroller.js +124 -0
- package/dist/organisms/NavigationMenu.d.ts +41 -0
- package/dist/organisms/NavigationMenu.js +139 -0
- package/dist/organisms/Popover.d.ts +33 -0
- package/dist/organisms/Popover.js +58 -0
- package/dist/organisms/Questionnaire.d.ts +74 -0
- package/dist/organisms/Questionnaire.js +235 -0
- package/dist/organisms/Select.d.ts +47 -0
- package/dist/organisms/Select.js +250 -0
- package/dist/organisms/Sheet.d.ts +34 -0
- package/dist/organisms/Sheet.js +107 -0
- package/dist/organisms/Sidebar.d.ts +100 -0
- package/dist/organisms/Sidebar.js +228 -0
- package/dist/organisms/Toaster.d.ts +67 -0
- package/dist/organisms/Toaster.js +189 -0
- package/dist/organisms/Tooltip.d.ts +39 -0
- package/dist/organisms/Tooltip.js +94 -0
- package/dist/organisms/index.d.ts +40 -0
- package/dist/organisms/index.js +40 -0
- package/dist/primitives/controllable.d.ts +38 -0
- package/dist/primitives/controllable.js +43 -0
- package/dist/primitives/dismissable.d.ts +53 -0
- package/dist/primitives/dismissable.js +158 -0
- package/dist/primitives/floating.d.ts +116 -0
- package/dist/primitives/floating.js +289 -0
- package/dist/primitives/floatingSurface.d.ts +56 -0
- package/dist/primitives/floatingSurface.js +142 -0
- package/dist/primitives/focusTrap.d.ts +47 -0
- package/dist/primitives/focusTrap.js +101 -0
- package/dist/primitives/focusable.d.ts +53 -0
- package/dist/primitives/focusable.js +109 -0
- package/dist/primitives/index.d.ts +27 -0
- package/dist/primitives/index.js +27 -0
- package/dist/primitives/menu.d.ts +102 -0
- package/dist/primitives/menu.js +356 -0
- package/dist/primitives/modalSurface.d.ts +54 -0
- package/dist/primitives/modalSurface.js +146 -0
- package/dist/primitives/portal.d.ts +34 -0
- package/dist/primitives/portal.js +42 -0
- package/dist/primitives/presence.d.ts +45 -0
- package/dist/primitives/presence.js +152 -0
- package/dist/primitives/rovingFocus.d.ts +47 -0
- package/dist/primitives/rovingFocus.js +176 -0
- package/dist/primitives/scrollLock.d.ts +21 -0
- package/dist/primitives/scrollLock.js +75 -0
- package/dist/primitives/typeahead.d.ts +39 -0
- package/dist/primitives/typeahead.js +123 -0
- package/dist/templates/AppShell.d.ts +30 -0
- package/dist/templates/AppShell.js +42 -0
- package/dist/templates/AuthLayout.d.ts +23 -0
- package/dist/templates/AuthLayout.js +42 -0
- package/dist/templates/SettingsLayout.d.ts +27 -0
- package/dist/templates/SettingsLayout.js +42 -0
- package/dist/templates/index.d.ts +16 -0
- package/dist/templates/index.js +16 -0
- package/nebula.css +2 -0
- package/package.json +92 -0
- package/registry.json +1107 -0
- package/src/adapters/css.ts +75 -0
- package/src/adapters/index.ts +40 -0
- package/src/adapters/tailwind.ts +134 -0
- package/src/adapters/types.ts +53 -0
- package/src/adapters/unocss.ts +116 -0
- package/src/atoms/AspectRatio.ts +28 -0
- package/src/atoms/Avatar.ts +67 -0
- package/src/atoms/Badge.ts +46 -0
- package/src/atoms/Button.ts +96 -0
- package/src/atoms/Checkbox.ts +76 -0
- package/src/atoms/Input.ts +76 -0
- package/src/atoms/Kbd.ts +28 -0
- package/src/atoms/Label.ts +38 -0
- package/src/atoms/Marker.ts +79 -0
- package/src/atoms/NativeSelect.ts +115 -0
- package/src/atoms/Progress.ts +64 -0
- package/src/atoms/ScrollArea.ts +53 -0
- package/src/atoms/Separator.ts +36 -0
- package/src/atoms/Skeleton.ts +24 -0
- package/src/atoms/Slider.ts +65 -0
- package/src/atoms/Spinner.ts +28 -0
- package/src/atoms/Switch.ts +56 -0
- package/src/atoms/Textarea.ts +58 -0
- package/src/atoms/Toggle.ts +67 -0
- package/src/atoms/index.ts +62 -0
- package/src/cli/add.ts +126 -0
- package/src/cli/index.ts +22 -0
- package/src/cli/registry.ts +116 -0
- package/src/config.ts +102 -0
- package/src/configure.ts +127 -0
- package/src/index.ts +53 -0
- package/src/lib/children.ts +43 -0
- package/src/lib/cn.ts +14 -0
- package/src/lib/cva.ts +249 -0
- package/src/lib/icons.ts +138 -0
- package/src/lib/id.ts +44 -0
- package/src/lib/index.ts +21 -0
- package/src/lib/motion.ts +48 -0
- package/src/lib/props.ts +83 -0
- package/src/lib/styled.ts +60 -0
- package/src/molecules/Accordion.ts +161 -0
- package/src/molecules/Alert.ts +58 -0
- package/src/molecules/Attachment.ts +132 -0
- package/src/molecules/Breadcrumb.ts +107 -0
- package/src/molecules/Bubble.ts +134 -0
- package/src/molecules/ButtonGroup.ts +55 -0
- package/src/molecules/Card.ts +42 -0
- package/src/molecules/Collapsible.ts +89 -0
- package/src/molecules/Empty.ts +43 -0
- package/src/molecules/Field.ts +131 -0
- package/src/molecules/InputGroup.ts +62 -0
- package/src/molecules/InputOTP.ts +166 -0
- package/src/molecules/Item.ts +89 -0
- package/src/molecules/Message.ts +127 -0
- package/src/molecules/Pagination.ts +197 -0
- package/src/molecules/RadioGroup.ts +104 -0
- package/src/molecules/Resizable.ts +166 -0
- package/src/molecules/Table.ts +118 -0
- package/src/molecules/Tabs.ts +148 -0
- package/src/molecules/ToggleGroup.ts +109 -0
- package/src/molecules/Typography.ts +94 -0
- package/src/molecules/index.ts +144 -0
- package/src/organisms/AlertDialog.ts +133 -0
- package/src/organisms/Calendar.ts +470 -0
- package/src/organisms/Carousel.ts +161 -0
- package/src/organisms/Chart.ts +257 -0
- package/src/organisms/Combobox.ts +140 -0
- package/src/organisms/Command.ts +211 -0
- package/src/organisms/CommandDialog.ts +149 -0
- package/src/organisms/ContextMenu.ts +125 -0
- package/src/organisms/DataTable.ts +328 -0
- package/src/organisms/DatePicker.ts +141 -0
- package/src/organisms/DateRangePicker.ts +149 -0
- package/src/organisms/Dialog.ts +162 -0
- package/src/organisms/Drawer.ts +167 -0
- package/src/organisms/DropdownMenu.ts +112 -0
- package/src/organisms/Form.ts +217 -0
- package/src/organisms/HoverCard.ts +92 -0
- package/src/organisms/Menubar.ts +167 -0
- package/src/organisms/MessageScroller.ts +145 -0
- package/src/organisms/NavigationMenu.ts +184 -0
- package/src/organisms/Popover.ts +84 -0
- package/src/organisms/Questionnaire.ts +322 -0
- package/src/organisms/Select.ts +286 -0
- package/src/organisms/Sheet.ts +144 -0
- package/src/organisms/Sidebar.ts +336 -0
- package/src/organisms/Toaster.ts +246 -0
- package/src/organisms/Tooltip.ts +124 -0
- package/src/organisms/index.ts +134 -0
- package/src/primitives/controllable.ts +64 -0
- package/src/primitives/dismissable.ts +195 -0
- package/src/primitives/floating.ts +453 -0
- package/src/primitives/floatingSurface.ts +209 -0
- package/src/primitives/focusTrap.ts +132 -0
- package/src/primitives/focusable.ts +108 -0
- package/src/primitives/index.ts +88 -0
- package/src/primitives/menu.ts +491 -0
- package/src/primitives/modalSurface.ts +199 -0
- package/src/primitives/portal.ts +61 -0
- package/src/primitives/presence.ts +185 -0
- package/src/primitives/rovingFocus.ts +212 -0
- package/src/primitives/scrollLock.ts +79 -0
- package/src/primitives/typeahead.ts +156 -0
- package/src/templates/AppShell.ts +56 -0
- package/src/templates/AuthLayout.ts +66 -0
- package/src/templates/SettingsLayout.ts +63 -0
- package/src/templates/index.ts +21 -0
- package/theme.css +151 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tabs — one panel visible at a time.
|
|
3
|
+
*
|
|
4
|
+
* The WAI-ARIA tabs pattern in full: the tab list is a single tab stop with
|
|
5
|
+
* arrows moving between tabs, each tab points at its panel with
|
|
6
|
+
* `aria-controls`, and each panel points back with `aria-labelledby`.
|
|
7
|
+
*
|
|
8
|
+
* Activation is automatic — moving to a tab selects it — which is the right
|
|
9
|
+
* default when panels are already rendered and switching costs nothing. The
|
|
10
|
+
* `activateOnFocus: false` escape hatch exists for panels that fetch on
|
|
11
|
+
* display, where arrowing past three tabs would fire three requests.
|
|
12
|
+
*
|
|
13
|
+
* Hidden panels stay mounted so a form or a scroll position inside one
|
|
14
|
+
* survives a trip to another tab. `hidden` keeps them out of the layout and
|
|
15
|
+
* the accessibility tree, which is the behaviour `display: none` would give
|
|
16
|
+
* without also removing them from the DOM.
|
|
17
|
+
*/
|
|
18
|
+
import { component, html, onMount, onUnmount, signal } from "@c9up/aurora";
|
|
19
|
+
import { slot } from "../lib/children.js";
|
|
20
|
+
import { cn } from "../lib/cn.js";
|
|
21
|
+
import { uid } from "../lib/id.js";
|
|
22
|
+
import { read } from "../lib/props.js";
|
|
23
|
+
import { rovingFocus } from "../primitives/rovingFocus.js";
|
|
24
|
+
export const Tabs = component((props) => {
|
|
25
|
+
const orientation = props.orientation ?? "horizontal";
|
|
26
|
+
const first = props.items.find((item) => item.disabled !== true);
|
|
27
|
+
const active = signal(props.defaultValue ?? first?.value ?? "");
|
|
28
|
+
const listId = uid("tabs-list");
|
|
29
|
+
// Ids are minted once per tab, up front, because a tab and its panel each
|
|
30
|
+
// need to name the other — one of them has to know both before rendering.
|
|
31
|
+
const ids = new Map();
|
|
32
|
+
for (const item of props.items) {
|
|
33
|
+
ids.set(item.value, { tab: uid("tab"), panel: uid("tab-panel") });
|
|
34
|
+
}
|
|
35
|
+
function select(value) {
|
|
36
|
+
if (active() === value)
|
|
37
|
+
return;
|
|
38
|
+
active(value);
|
|
39
|
+
props.onValueChange?.(value);
|
|
40
|
+
}
|
|
41
|
+
let group;
|
|
42
|
+
onMount(() => {
|
|
43
|
+
group = rovingFocus({
|
|
44
|
+
container: () => document.getElementById(listId),
|
|
45
|
+
itemSelector: "[role='tab']",
|
|
46
|
+
orientation,
|
|
47
|
+
onFocusChange: (item) => {
|
|
48
|
+
if (props.activateOnFocus === false)
|
|
49
|
+
return;
|
|
50
|
+
const value = item.getAttribute("data-value");
|
|
51
|
+
if (value !== null)
|
|
52
|
+
select(value);
|
|
53
|
+
},
|
|
54
|
+
onSelect: (item) => {
|
|
55
|
+
const value = item.getAttribute("data-value");
|
|
56
|
+
if (value !== null)
|
|
57
|
+
select(value);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
group.sync();
|
|
61
|
+
});
|
|
62
|
+
onUnmount(() => group?.destroy());
|
|
63
|
+
return html `<div
|
|
64
|
+
data-slot="tabs"
|
|
65
|
+
data-orientation="${orientation}"
|
|
66
|
+
class="${() => cn("flex gap-2", orientation === "vertical" ? "flex-row" : "flex-col", read(props.class))}"
|
|
67
|
+
>
|
|
68
|
+
<div
|
|
69
|
+
role="tablist"
|
|
70
|
+
data-slot="tabs-list"
|
|
71
|
+
id="${listId}"
|
|
72
|
+
aria-orientation="${orientation}"
|
|
73
|
+
class="${() => cn("bg-muted text-muted-foreground inline-flex w-fit items-center justify-center rounded-lg p-[3px]", orientation === "vertical" ? "flex-col" : "h-9", read(props.listClass))}"
|
|
74
|
+
>
|
|
75
|
+
${props.items.map((item) => renderTab(item, ids, active, select))}
|
|
76
|
+
</div>
|
|
77
|
+
${props.items.map((item) => renderPanel(item, ids, active))}
|
|
78
|
+
</div>`;
|
|
79
|
+
});
|
|
80
|
+
function renderTab(item, ids, active, select) {
|
|
81
|
+
const id = ids.get(item.value);
|
|
82
|
+
const selected = () => active() === item.value;
|
|
83
|
+
return html `<button
|
|
84
|
+
type="button"
|
|
85
|
+
role="tab"
|
|
86
|
+
data-slot="tabs-trigger"
|
|
87
|
+
data-value="${item.value}"
|
|
88
|
+
data-state="${() => (selected() ? "active" : "inactive")}"
|
|
89
|
+
data-disabled="${item.disabled === true ? "" : undefined}"
|
|
90
|
+
id="${id?.tab}"
|
|
91
|
+
aria-selected="${() => (selected() ? "true" : "false")}"
|
|
92
|
+
aria-controls="${id?.panel}"
|
|
93
|
+
?disabled="${item.disabled === true}"
|
|
94
|
+
class="data-[state=active]:bg-background data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0"
|
|
95
|
+
@click="${() => select(item.value)}"
|
|
96
|
+
>${item.label}</button>`;
|
|
97
|
+
}
|
|
98
|
+
function renderPanel(item, ids, active) {
|
|
99
|
+
const id = ids.get(item.value);
|
|
100
|
+
return html `<div
|
|
101
|
+
role="tabpanel"
|
|
102
|
+
data-slot="tabs-content"
|
|
103
|
+
id="${id?.panel}"
|
|
104
|
+
aria-labelledby="${id?.tab}"
|
|
105
|
+
tabindex="0"
|
|
106
|
+
?hidden="${() => active() !== item.value}"
|
|
107
|
+
class="flex-1 outline-none"
|
|
108
|
+
>${slot(item.content)}</div>`;
|
|
109
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToggleGroup — a row of toggles acting as one control.
|
|
3
|
+
*
|
|
4
|
+
* `type` decides both the behaviour and the semantics, and they have to move
|
|
5
|
+
* together. `"single"` is a radio group: one value, `role="radio"` on each
|
|
6
|
+
* button, and picking one clears the rest. `"multiple"` is a set of
|
|
7
|
+
* independent toggle buttons, each with `aria-pressed`.
|
|
8
|
+
*
|
|
9
|
+
* Getting that pairing wrong is the usual bug here — a single-select group
|
|
10
|
+
* announcing each option as "pressed" tells a screen-reader user they can turn
|
|
11
|
+
* several on at once.
|
|
12
|
+
*
|
|
13
|
+
* The classes come from `Toggle`'s own `cva`, so a grouped button and a
|
|
14
|
+
* standalone one cannot drift apart. Only the corner rounding is overridden,
|
|
15
|
+
* to weld the row into one shape.
|
|
16
|
+
*/
|
|
17
|
+
import { type ToggleVariants } from "../atoms/Toggle.js";
|
|
18
|
+
import type { Child } from "../lib/children.js";
|
|
19
|
+
import { type Reactive } from "../lib/props.js";
|
|
20
|
+
export interface ToggleGroupItem {
|
|
21
|
+
value: string;
|
|
22
|
+
label: Child;
|
|
23
|
+
/** Required when the label is an icon alone. */
|
|
24
|
+
ariaLabel?: string;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface ToggleGroupProps {
|
|
28
|
+
items: readonly ToggleGroupItem[];
|
|
29
|
+
type?: "single" | "multiple";
|
|
30
|
+
defaultValue?: string | readonly string[];
|
|
31
|
+
variant?: Reactive<ToggleVariants["variant"]>;
|
|
32
|
+
size?: Reactive<ToggleVariants["size"]>;
|
|
33
|
+
disabled?: Reactive<boolean>;
|
|
34
|
+
class?: Reactive<string>;
|
|
35
|
+
onValueChange?: (value: readonly string[]) => void;
|
|
36
|
+
}
|
|
37
|
+
export declare const ToggleGroup: (props?: ToggleGroupProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToggleGroup — a row of toggles acting as one control.
|
|
3
|
+
*
|
|
4
|
+
* `type` decides both the behaviour and the semantics, and they have to move
|
|
5
|
+
* together. `"single"` is a radio group: one value, `role="radio"` on each
|
|
6
|
+
* button, and picking one clears the rest. `"multiple"` is a set of
|
|
7
|
+
* independent toggle buttons, each with `aria-pressed`.
|
|
8
|
+
*
|
|
9
|
+
* Getting that pairing wrong is the usual bug here — a single-select group
|
|
10
|
+
* announcing each option as "pressed" tells a screen-reader user they can turn
|
|
11
|
+
* several on at once.
|
|
12
|
+
*
|
|
13
|
+
* The classes come from `Toggle`'s own `cva`, so a grouped button and a
|
|
14
|
+
* standalone one cannot drift apart. Only the corner rounding is overridden,
|
|
15
|
+
* to weld the row into one shape.
|
|
16
|
+
*/
|
|
17
|
+
import { component, html, signal } from "@c9up/aurora";
|
|
18
|
+
import { toggleVariants } from "../atoms/Toggle.js";
|
|
19
|
+
import { cn } from "../lib/cn.js";
|
|
20
|
+
import { read, readOr } from "../lib/props.js";
|
|
21
|
+
export const ToggleGroup = component((props) => {
|
|
22
|
+
const type = props.type ?? "single";
|
|
23
|
+
const selected = signal(props.defaultValue === undefined
|
|
24
|
+
? []
|
|
25
|
+
: typeof props.defaultValue === "string"
|
|
26
|
+
? [props.defaultValue]
|
|
27
|
+
: props.defaultValue);
|
|
28
|
+
function toggle(value) {
|
|
29
|
+
const current = selected();
|
|
30
|
+
const next = type === "single"
|
|
31
|
+
? current.includes(value)
|
|
32
|
+
? []
|
|
33
|
+
: [value]
|
|
34
|
+
: current.includes(value)
|
|
35
|
+
? current.filter((entry) => entry !== value)
|
|
36
|
+
: [...current, value];
|
|
37
|
+
selected(next);
|
|
38
|
+
props.onValueChange?.(next);
|
|
39
|
+
}
|
|
40
|
+
return html `<div
|
|
41
|
+
data-slot="toggle-group"
|
|
42
|
+
role="${type === "single" ? "radiogroup" : "group"}"
|
|
43
|
+
class="${() => cn("group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs", read(props.class))}"
|
|
44
|
+
data-variant="${() => readOr(props.variant, "default")}"
|
|
45
|
+
>
|
|
46
|
+
${props.items.map((item) => renderItem(item, props, type, selected, toggle))}
|
|
47
|
+
</div>`;
|
|
48
|
+
});
|
|
49
|
+
function renderItem(item, props, type, selected, toggle) {
|
|
50
|
+
const on = () => selected().includes(item.value);
|
|
51
|
+
return html `<button
|
|
52
|
+
type="button"
|
|
53
|
+
data-slot="toggle-group-item"
|
|
54
|
+
data-state="${() => (on() ? "on" : "off")}"
|
|
55
|
+
data-value="${item.value}"
|
|
56
|
+
role="${type === "single" ? "radio" : undefined}"
|
|
57
|
+
aria-checked="${() => (type === "single" ? (on() ? "true" : "false") : undefined)}"
|
|
58
|
+
aria-pressed="${() => (type === "multiple" ? (on() ? "true" : "false") : undefined)}"
|
|
59
|
+
aria-label="${item.ariaLabel}"
|
|
60
|
+
?disabled="${() => item.disabled === true || readOr(props.disabled, false)}"
|
|
61
|
+
class="${() => toggleVariants({
|
|
62
|
+
variant: read(props.variant),
|
|
63
|
+
size: read(props.size),
|
|
64
|
+
class: "min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l aria-checked:bg-accent aria-checked:text-accent-foreground",
|
|
65
|
+
})}"
|
|
66
|
+
@click="${() => toggle(item.value)}"
|
|
67
|
+
>${item.label}</button>`;
|
|
68
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typography — prose styles for content nebula does not otherwise own.
|
|
3
|
+
*
|
|
4
|
+
* shadcn documents these as copy-paste snippets rather than shipping them as
|
|
5
|
+
* components, which leaves every app to re-derive the same scale. They are
|
|
6
|
+
* cheap to ship and they are what keeps a marketing page, a changelog and an
|
|
7
|
+
* empty state reading as one product.
|
|
8
|
+
*
|
|
9
|
+
* `text-balance` on headings and `text-pretty` on body copy do the work here:
|
|
10
|
+
* they stop a two-word orphan on the last line of a heading and a single word
|
|
11
|
+
* ending a paragraph, which is most of what separates typeset text from text.
|
|
12
|
+
*/
|
|
13
|
+
import { type StyledProps } from "../lib/styled.js";
|
|
14
|
+
export type TypographyProps = StyledProps;
|
|
15
|
+
export declare const H1: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
16
|
+
export declare const H2: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
17
|
+
export declare const H3: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
18
|
+
export declare const H4: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
19
|
+
export declare const P: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
20
|
+
export declare const Blockquote: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
21
|
+
export declare const InlineCode: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
22
|
+
export declare const List: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
23
|
+
export declare const Lead: (props?: StyledProps) => import("@c9up/aurora").TemplateResult;
|
|
24
|
+
export declare const Large: (props?: StyledProps) => import("@c9up/aurora").TemplateResult;
|
|
25
|
+
export declare const Small: (props?: StyledProps) => import("@c9up/aurora").TemplateResult;
|
|
26
|
+
export declare const Muted: (props?: StyledProps) => import("@c9up/aurora").TemplateResult;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typography — prose styles for content nebula does not otherwise own.
|
|
3
|
+
*
|
|
4
|
+
* shadcn documents these as copy-paste snippets rather than shipping them as
|
|
5
|
+
* components, which leaves every app to re-derive the same scale. They are
|
|
6
|
+
* cheap to ship and they are what keeps a marketing page, a changelog and an
|
|
7
|
+
* empty state reading as one product.
|
|
8
|
+
*
|
|
9
|
+
* `text-balance` on headings and `text-pretty` on body copy do the work here:
|
|
10
|
+
* they stop a two-word orphan on the last line of a heading and a single word
|
|
11
|
+
* ending a paragraph, which is most of what separates typeset text from text.
|
|
12
|
+
*/
|
|
13
|
+
import { component, html } from "@c9up/aurora";
|
|
14
|
+
import { slot } from "../lib/children.js";
|
|
15
|
+
import { cn } from "../lib/cn.js";
|
|
16
|
+
import { read } from "../lib/props.js";
|
|
17
|
+
import { styledDiv } from "../lib/styled.js";
|
|
18
|
+
export const H1 = component((props) => {
|
|
19
|
+
return html `<h1
|
|
20
|
+
id="${props.id}"
|
|
21
|
+
class="${() => cn("scroll-m-20 text-4xl font-extrabold tracking-tight text-balance", read(props.class))}"
|
|
22
|
+
>${slot(props.children)}</h1>`;
|
|
23
|
+
});
|
|
24
|
+
export const H2 = component((props) => {
|
|
25
|
+
return html `<h2
|
|
26
|
+
id="${props.id}"
|
|
27
|
+
class="${() => cn("scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0", read(props.class))}"
|
|
28
|
+
>${slot(props.children)}</h2>`;
|
|
29
|
+
});
|
|
30
|
+
export const H3 = component((props) => {
|
|
31
|
+
return html `<h3
|
|
32
|
+
id="${props.id}"
|
|
33
|
+
class="${() => cn("scroll-m-20 text-2xl font-semibold tracking-tight", read(props.class))}"
|
|
34
|
+
>${slot(props.children)}</h3>`;
|
|
35
|
+
});
|
|
36
|
+
export const H4 = component((props) => {
|
|
37
|
+
return html `<h4
|
|
38
|
+
id="${props.id}"
|
|
39
|
+
class="${() => cn("scroll-m-20 text-xl font-semibold tracking-tight", read(props.class))}"
|
|
40
|
+
>${slot(props.children)}</h4>`;
|
|
41
|
+
});
|
|
42
|
+
export const P = component((props) => {
|
|
43
|
+
return html `<p
|
|
44
|
+
id="${props.id}"
|
|
45
|
+
class="${() => cn("leading-7 text-pretty [&:not(:first-child)]:mt-6", read(props.class))}"
|
|
46
|
+
>${slot(props.children)}</p>`;
|
|
47
|
+
});
|
|
48
|
+
export const Blockquote = component((props) => {
|
|
49
|
+
return html `<blockquote
|
|
50
|
+
id="${props.id}"
|
|
51
|
+
class="${() => cn("mt-6 border-l-2 pl-6 italic", read(props.class))}"
|
|
52
|
+
>${slot(props.children)}</blockquote>`;
|
|
53
|
+
});
|
|
54
|
+
export const InlineCode = component((props) => {
|
|
55
|
+
return html `<code
|
|
56
|
+
class="${() => cn("bg-muted relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold", read(props.class))}"
|
|
57
|
+
>${slot(props.children)}</code>`;
|
|
58
|
+
});
|
|
59
|
+
export const List = component((props) => {
|
|
60
|
+
return html `<ul
|
|
61
|
+
class="${() => cn("my-6 ml-6 list-disc [&>li]:mt-2", read(props.class))}"
|
|
62
|
+
>${slot(props.children)}</ul>`;
|
|
63
|
+
});
|
|
64
|
+
export const Lead = styledDiv("lead", "text-muted-foreground text-xl text-pretty");
|
|
65
|
+
export const Large = styledDiv("large", "text-lg font-semibold");
|
|
66
|
+
export const Small = styledDiv("small", "text-sm leading-none font-medium");
|
|
67
|
+
export const Muted = styledDiv("muted", "text-muted-foreground text-sm");
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Molecules — assemblies of atoms, and small state machines.
|
|
3
|
+
*
|
|
4
|
+
* A component lands here when it composes other nebula components, or owns
|
|
5
|
+
* state that spans more than one element (Accordion's open set, Tabs'
|
|
6
|
+
* selection, InputOTP's characters). What it does *not* do is escape its own
|
|
7
|
+
* DOM position: nothing here portals, traps focus, or floats. Those are
|
|
8
|
+
* organisms.
|
|
9
|
+
*
|
|
10
|
+
* Compound components are data-driven — `Accordion({ items: [...] })`, not
|
|
11
|
+
* `<Accordion><AccordionItem>`. shadcn's compound API is a React-context
|
|
12
|
+
* artifact; Aurora has no context, and the alternatives (a factory returning
|
|
13
|
+
* bound parts, a handle threaded through props) are more machinery for less
|
|
14
|
+
* clarity. The rendered markup is unchanged, so shadcn's CSS and its examples
|
|
15
|
+
* still read across.
|
|
16
|
+
*/
|
|
17
|
+
export { Accordion, type AccordionItem, type AccordionProps, } from "./Accordion.js";
|
|
18
|
+
export { Alert, AlertDescription, type AlertProps, AlertTitle, type AlertVariants, alertVariants, } from "./Alert.js";
|
|
19
|
+
export { Attachment, type AttachmentProps, formatBytes, } from "./Attachment.js";
|
|
20
|
+
export { Breadcrumb, type BreadcrumbProps, type Crumb } from "./Breadcrumb.js";
|
|
21
|
+
export { Bubble, type BubbleProps, type BubbleReaction, type BubbleVariants, bubbleVariants, } from "./Bubble.js";
|
|
22
|
+
export { ButtonGroup, type ButtonGroupProps, ButtonGroupSeparator, ButtonGroupText, } from "./ButtonGroup.js";
|
|
23
|
+
export { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, type CardProps, CardTitle, } from "./Card.js";
|
|
24
|
+
export { Collapsible, type CollapsibleProps } from "./Collapsible.js";
|
|
25
|
+
export { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, type EmptyProps, EmptyTitle, } from "./Empty.js";
|
|
26
|
+
export { Field, FieldGroup, type FieldIds, type FieldProps, FieldSeparator, fieldIds, } from "./Field.js";
|
|
27
|
+
export { InputGroup, InputGroupAddon, type InputGroupProps, inputGroupControlClasses, } from "./InputGroup.js";
|
|
28
|
+
export { InputOTP, type InputOTPProps } from "./InputOTP.js";
|
|
29
|
+
export { Item, ItemActions, ItemContent, ItemDescription, ItemGroup, ItemMedia, type ItemProps, ItemSeparator, ItemTitle, type ItemVariants, itemVariants, } from "./Item.js";
|
|
30
|
+
export { Message, type MessageProps } from "./Message.js";
|
|
31
|
+
export { type PageSlot, Pagination, type PaginationProps, pageWindow, } from "./Pagination.js";
|
|
32
|
+
export { RadioGroup, type RadioGroupProps, type RadioOption, } from "./RadioGroup.js";
|
|
33
|
+
export { Resizable, type ResizableProps } from "./Resizable.js";
|
|
34
|
+
export { Table, TableBody, TableCaption, TableCell, type TableCellProps, TableFooter, TableHead, TableHeader, type TableProps, TableRow, type TableRowProps, } from "./Table.js";
|
|
35
|
+
export { type TabItem, Tabs, type TabsProps } from "./Tabs.js";
|
|
36
|
+
export { ToggleGroup, type ToggleGroupItem, type ToggleGroupProps, } from "./ToggleGroup.js";
|
|
37
|
+
export { Blockquote, H1, H2, H3, H4, InlineCode, Large, Lead, List, Muted, P, Small, type TypographyProps, } from "./Typography.js";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Molecules — assemblies of atoms, and small state machines.
|
|
3
|
+
*
|
|
4
|
+
* A component lands here when it composes other nebula components, or owns
|
|
5
|
+
* state that spans more than one element (Accordion's open set, Tabs'
|
|
6
|
+
* selection, InputOTP's characters). What it does *not* do is escape its own
|
|
7
|
+
* DOM position: nothing here portals, traps focus, or floats. Those are
|
|
8
|
+
* organisms.
|
|
9
|
+
*
|
|
10
|
+
* Compound components are data-driven — `Accordion({ items: [...] })`, not
|
|
11
|
+
* `<Accordion><AccordionItem>`. shadcn's compound API is a React-context
|
|
12
|
+
* artifact; Aurora has no context, and the alternatives (a factory returning
|
|
13
|
+
* bound parts, a handle threaded through props) are more machinery for less
|
|
14
|
+
* clarity. The rendered markup is unchanged, so shadcn's CSS and its examples
|
|
15
|
+
* still read across.
|
|
16
|
+
*/
|
|
17
|
+
export { Accordion, } from "./Accordion.js";
|
|
18
|
+
export { Alert, AlertDescription, AlertTitle, alertVariants, } from "./Alert.js";
|
|
19
|
+
export { Attachment, formatBytes, } from "./Attachment.js";
|
|
20
|
+
export { Breadcrumb } from "./Breadcrumb.js";
|
|
21
|
+
export { Bubble, bubbleVariants, } from "./Bubble.js";
|
|
22
|
+
export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, } from "./ButtonGroup.js";
|
|
23
|
+
export { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "./Card.js";
|
|
24
|
+
export { Collapsible } from "./Collapsible.js";
|
|
25
|
+
export { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, } from "./Empty.js";
|
|
26
|
+
export { Field, FieldGroup, FieldSeparator, fieldIds, } from "./Field.js";
|
|
27
|
+
export { InputGroup, InputGroupAddon, inputGroupControlClasses, } from "./InputGroup.js";
|
|
28
|
+
export { InputOTP } from "./InputOTP.js";
|
|
29
|
+
export { Item, ItemActions, ItemContent, ItemDescription, ItemGroup, ItemMedia, ItemSeparator, ItemTitle, itemVariants, } from "./Item.js";
|
|
30
|
+
export { Message } from "./Message.js";
|
|
31
|
+
export { Pagination, pageWindow, } from "./Pagination.js";
|
|
32
|
+
export { RadioGroup, } from "./RadioGroup.js";
|
|
33
|
+
export { Resizable } from "./Resizable.js";
|
|
34
|
+
export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, } from "./Table.js";
|
|
35
|
+
export { Tabs } from "./Tabs.js";
|
|
36
|
+
export { ToggleGroup, } from "./ToggleGroup.js";
|
|
37
|
+
export { Blockquote, H1, H2, H3, H4, InlineCode, Large, Lead, List, Muted, P, Small, } from "./Typography.js";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AlertDialog — a modal that interrupts to ask something.
|
|
3
|
+
*
|
|
4
|
+
* The differences from Dialog are all about refusing to be dismissed by
|
|
5
|
+
* accident, because the user is being asked to confirm something they cannot
|
|
6
|
+
* undo:
|
|
7
|
+
*
|
|
8
|
+
* - `role="alertdialog"`, which tells assistive technology this is not merely
|
|
9
|
+
* a window but a message requiring a response.
|
|
10
|
+
* - No close button and no backdrop dismissal. Both are the "click somewhere
|
|
11
|
+
* else and it goes away" gesture, and an accidental dismissal of "delete
|
|
12
|
+
* this?" reads as a cancel — the safe answer — but leaves the user unsure
|
|
13
|
+
* which one they gave.
|
|
14
|
+
* - Escape still closes. It is unambiguous and deliberate, and taking it away
|
|
15
|
+
* would trap a keyboard user in the dialog.
|
|
16
|
+
* - Focus starts on **Cancel**, not the action. The destructive button is the
|
|
17
|
+
* one the user must reach for on purpose.
|
|
18
|
+
*/
|
|
19
|
+
import type { Child } from "../lib/children.js";
|
|
20
|
+
import { type Slot } from "../lib/children.js";
|
|
21
|
+
import { type Reactive } from "../lib/props.js";
|
|
22
|
+
export interface AlertDialogProps {
|
|
23
|
+
trigger?: Slot;
|
|
24
|
+
title: Child;
|
|
25
|
+
description: Child;
|
|
26
|
+
/** Label for the confirming action. Default `"Continue"`. */
|
|
27
|
+
actionLabel?: Child;
|
|
28
|
+
/** Label for the way out. Default `"Cancel"`. */
|
|
29
|
+
cancelLabel?: Child;
|
|
30
|
+
/** Styles the action as destructive. */
|
|
31
|
+
destructive?: boolean;
|
|
32
|
+
open?: Reactive<boolean>;
|
|
33
|
+
defaultOpen?: boolean;
|
|
34
|
+
onOpenChange?: (open: boolean) => void;
|
|
35
|
+
onConfirm?: () => void;
|
|
36
|
+
onCancel?: () => void;
|
|
37
|
+
triggerClass?: Reactive<string>;
|
|
38
|
+
contentClass?: Reactive<string>;
|
|
39
|
+
}
|
|
40
|
+
export declare const AlertDialog: (props?: AlertDialogProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AlertDialog — a modal that interrupts to ask something.
|
|
3
|
+
*
|
|
4
|
+
* The differences from Dialog are all about refusing to be dismissed by
|
|
5
|
+
* accident, because the user is being asked to confirm something they cannot
|
|
6
|
+
* undo:
|
|
7
|
+
*
|
|
8
|
+
* - `role="alertdialog"`, which tells assistive technology this is not merely
|
|
9
|
+
* a window but a message requiring a response.
|
|
10
|
+
* - No close button and no backdrop dismissal. Both are the "click somewhere
|
|
11
|
+
* else and it goes away" gesture, and an accidental dismissal of "delete
|
|
12
|
+
* this?" reads as a cancel — the safe answer — but leaves the user unsure
|
|
13
|
+
* which one they gave.
|
|
14
|
+
* - Escape still closes. It is unambiguous and deliberate, and taking it away
|
|
15
|
+
* would trap a keyboard user in the dialog.
|
|
16
|
+
* - Focus starts on **Cancel**, not the action. The destructive button is the
|
|
17
|
+
* one the user must reach for on purpose.
|
|
18
|
+
*/
|
|
19
|
+
import { component, html } from "@c9up/aurora";
|
|
20
|
+
import { buttonVariants } from "../atoms/Button.js";
|
|
21
|
+
import { slot } from "../lib/children.js";
|
|
22
|
+
import { cn } from "../lib/cn.js";
|
|
23
|
+
import { uid } from "../lib/id.js";
|
|
24
|
+
import { fadeInOut, zoomInOut } from "../lib/motion.js";
|
|
25
|
+
import { read } from "../lib/props.js";
|
|
26
|
+
import { controllable } from "../primitives/controllable.js";
|
|
27
|
+
import { modalSurface } from "../primitives/modalSurface.js";
|
|
28
|
+
import { dialogBackdropClasses, dialogPanelClasses } from "./Dialog.js";
|
|
29
|
+
export const AlertDialog = component((props) => {
|
|
30
|
+
const triggerId = uid("alert-dialog-trigger");
|
|
31
|
+
const panelId = uid("alert-dialog-panel");
|
|
32
|
+
const titleId = uid("alert-dialog-title");
|
|
33
|
+
const descriptionId = uid("alert-dialog-description");
|
|
34
|
+
const cancelId = uid("alert-dialog-cancel");
|
|
35
|
+
const state = controllable({
|
|
36
|
+
value: props.open,
|
|
37
|
+
initial: props.defaultOpen ?? false,
|
|
38
|
+
onChange: props.onOpenChange,
|
|
39
|
+
});
|
|
40
|
+
function cancel() {
|
|
41
|
+
props.onCancel?.();
|
|
42
|
+
state.set(false);
|
|
43
|
+
}
|
|
44
|
+
function confirm() {
|
|
45
|
+
props.onConfirm?.();
|
|
46
|
+
state.set(false);
|
|
47
|
+
}
|
|
48
|
+
modalSurface({
|
|
49
|
+
open: () => state.current(),
|
|
50
|
+
onClose: cancel,
|
|
51
|
+
panel: (root) => root.querySelector(`#${CSS.escape(panelId)}`),
|
|
52
|
+
initialFocus: (panel) => panel.querySelector(`#${CSS.escape(cancelId)}`),
|
|
53
|
+
returnFocus: () => document.getElementById(triggerId),
|
|
54
|
+
dismissOnOutside: false,
|
|
55
|
+
content: () => html `<div data-slot="alert-dialog-overlay" class="${cn("fixed inset-0 z-50", fadeInOut)}">
|
|
56
|
+
<div class="${dialogBackdropClasses}"></div>
|
|
57
|
+
<div
|
|
58
|
+
data-slot="alert-dialog-content"
|
|
59
|
+
id="${panelId}"
|
|
60
|
+
role="alertdialog"
|
|
61
|
+
aria-modal="true"
|
|
62
|
+
aria-labelledby="${titleId}"
|
|
63
|
+
aria-describedby="${descriptionId}"
|
|
64
|
+
tabindex="-1"
|
|
65
|
+
class="${cn(dialogPanelClasses, zoomInOut, read(props.contentClass))}"
|
|
66
|
+
>
|
|
67
|
+
<div class="flex flex-col gap-2 text-center sm:text-left">
|
|
68
|
+
<h2 id="${titleId}" class="text-lg font-semibold">${props.title}</h2>
|
|
69
|
+
<p id="${descriptionId}" class="text-muted-foreground text-sm">
|
|
70
|
+
${props.description}
|
|
71
|
+
</p>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
id="${cancelId}"
|
|
77
|
+
data-slot="alert-dialog-cancel"
|
|
78
|
+
class="${buttonVariants({ variant: "outline" })}"
|
|
79
|
+
@click="${cancel}"
|
|
80
|
+
>${props.cancelLabel ?? "Cancel"}</button>
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
data-slot="alert-dialog-action"
|
|
84
|
+
class="${buttonVariants({
|
|
85
|
+
variant: props.destructive === true ? "destructive" : "default",
|
|
86
|
+
})}"
|
|
87
|
+
@click="${confirm}"
|
|
88
|
+
>${props.actionLabel ?? "Continue"}</button>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>`,
|
|
92
|
+
});
|
|
93
|
+
if (props.trigger === undefined) {
|
|
94
|
+
return html `<span data-slot="alert-dialog" hidden></span>`;
|
|
95
|
+
}
|
|
96
|
+
return html `<button
|
|
97
|
+
type="button"
|
|
98
|
+
data-slot="alert-dialog-trigger"
|
|
99
|
+
id="${triggerId}"
|
|
100
|
+
aria-haspopup="dialog"
|
|
101
|
+
class="${() => cn(read(props.triggerClass))}"
|
|
102
|
+
@click="${() => state.set(true)}"
|
|
103
|
+
>${slot(props.trigger)}</button>`;
|
|
104
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar — a month grid for picking a date.
|
|
3
|
+
*
|
|
4
|
+
* shadcn wraps `react-day-picker`, which brings `date-fns` with it. nebula
|
|
5
|
+
* uses `Date` and `Intl`, which cover everything this needs: month lengths and
|
|
6
|
+
* leap years come from `Date`, and weekday and month names come from
|
|
7
|
+
* `Intl.DateTimeFormat` in the user's locale for free — which is more than a
|
|
8
|
+
* hardcoded English array would give.
|
|
9
|
+
*
|
|
10
|
+
* Dates are handled at local midnight throughout. A calendar cell means "this
|
|
11
|
+
* day", not "this instant", and mixing the two is how a date picker ends up
|
|
12
|
+
* off by one for users west of UTC: `new Date("2026-03-14")` parses as UTC
|
|
13
|
+
* midnight, which is the 13th in New York.
|
|
14
|
+
*
|
|
15
|
+
* The keyboard model is the WAI-ARIA grid pattern, and it is what makes the
|
|
16
|
+
* control usable at all without a pointer: arrows move a day, PageUp/PageDown
|
|
17
|
+
* move a month, Home and End jump to the ends of the week. Only one cell is
|
|
18
|
+
* ever tabbable — the focused day — so Tab leaves the grid instead of walking
|
|
19
|
+
* through thirty-one buttons.
|
|
20
|
+
*/
|
|
21
|
+
import { type Reactive } from "../lib/props.js";
|
|
22
|
+
/**
|
|
23
|
+
* A span of days. `to` is absent while the second end is still being picked —
|
|
24
|
+
* that half-open state is what the hover preview renders against, so it is
|
|
25
|
+
* part of the type rather than something the component hides internally.
|
|
26
|
+
*/
|
|
27
|
+
export interface DateRange {
|
|
28
|
+
readonly from: Date;
|
|
29
|
+
readonly to?: Date;
|
|
30
|
+
}
|
|
31
|
+
export interface CalendarProps {
|
|
32
|
+
/**
|
|
33
|
+
* `"range"` picks a span in two clicks. Default `"single"`.
|
|
34
|
+
*
|
|
35
|
+
* The two modes carry their value in separate props rather than one union.
|
|
36
|
+
* A union would make every read of `value` a narrowing exercise for the
|
|
37
|
+
* caller as much as for this file, and the two `onChange` shapes genuinely
|
|
38
|
+
* differ — a range handler that receives a bare `Date` has no way to say
|
|
39
|
+
* which end moved.
|
|
40
|
+
*/
|
|
41
|
+
mode?: "single" | "range";
|
|
42
|
+
/** Selected day, in `"single"` mode. Local midnight. */
|
|
43
|
+
value?: Reactive<Date | undefined>;
|
|
44
|
+
defaultValue?: Date;
|
|
45
|
+
/** Selected span, in `"range"` mode. */
|
|
46
|
+
range?: Reactive<DateRange | undefined>;
|
|
47
|
+
defaultRange?: DateRange;
|
|
48
|
+
/** Month shown at first render. Defaults to the selection, then today. */
|
|
49
|
+
defaultMonth?: Date;
|
|
50
|
+
min?: Date;
|
|
51
|
+
max?: Date;
|
|
52
|
+
/** Rule out individual days — weekends, holidays, taken slots. */
|
|
53
|
+
disabled?: (date: Date) => boolean;
|
|
54
|
+
/** `0` Sunday, `1` Monday. Defaults to the locale's own first day. */
|
|
55
|
+
weekStartsOn?: number;
|
|
56
|
+
locale?: string;
|
|
57
|
+
class?: Reactive<string>;
|
|
58
|
+
/** `"single"` mode. */
|
|
59
|
+
onValueChange?: (date: Date) => void;
|
|
60
|
+
/** `"range"` mode. Fires on both clicks — `to` is absent after the first. */
|
|
61
|
+
onRangeChange?: (range: DateRange) => void;
|
|
62
|
+
}
|
|
63
|
+
/** Local midnight of the given day — the canonical form for a calendar date. */
|
|
64
|
+
export declare function startOfDay(date: Date): Date;
|
|
65
|
+
export declare function isSameDay(a: Date, b: Date): boolean;
|
|
66
|
+
export declare function addDays(date: Date, days: number): Date;
|
|
67
|
+
export declare function addMonths(date: Date, months: number): Date;
|
|
68
|
+
/**
|
|
69
|
+
* The six-week grid covering a month.
|
|
70
|
+
*
|
|
71
|
+
* Always six weeks, always 42 cells, even when five would do. A grid that
|
|
72
|
+
* changes height between months makes the whole popover jump as you page
|
|
73
|
+
* through it.
|
|
74
|
+
*/
|
|
75
|
+
export declare function monthGrid(month: Date, weekStartsOn: number): Date[];
|
|
76
|
+
/** Where a day sits within a span. `"single"` is a span of one day. */
|
|
77
|
+
export type RangePosition = "single" | "start" | "end" | "middle" | "none";
|
|
78
|
+
/**
|
|
79
|
+
* Place a day relative to a span.
|
|
80
|
+
*
|
|
81
|
+
* `end` is passed in rather than read off the range because it may be the
|
|
82
|
+
* *preview* end — wherever the pointer is, before the second click. Keeping
|
|
83
|
+
* that decision at the call site is what lets this stay a pure function over
|
|
84
|
+
* three dates, which is the part worth testing exhaustively.
|
|
85
|
+
*/
|
|
86
|
+
export declare function rangePosition(date: Date, range: DateRange | undefined, end: Date | undefined): RangePosition;
|
|
87
|
+
/**
|
|
88
|
+
* The span after clicking `date`.
|
|
89
|
+
*
|
|
90
|
+
* A click before the fixed start restarts the span rather than producing an
|
|
91
|
+
* inverted one. Silently swapping the ends would be the other option, and it
|
|
92
|
+
* is worse: the user who clicked the 3rd after the 10th almost always meant to
|
|
93
|
+
* start again, not to select the week between them.
|
|
94
|
+
*/
|
|
95
|
+
export declare function nextRange(current: DateRange | undefined, date: Date): DateRange;
|
|
96
|
+
export declare const Calendar: (props?: CalendarProps | undefined) => import("@c9up/aurora").TemplateResult;
|