@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,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagination — page links for a long list.
|
|
3
|
+
*
|
|
4
|
+
* The interesting part is `pageWindow`, which decides which page numbers to
|
|
5
|
+
* show. Rendering all of them breaks at a few hundred pages; the usual fix is
|
|
6
|
+
* a fixed window around the current page, which then jitters in width as you
|
|
7
|
+
* approach either end — the control visibly reflows while you click through
|
|
8
|
+
* it. `pageWindow` keeps the count constant instead, so the row stays the same
|
|
9
|
+
* size from page 1 to page 500.
|
|
10
|
+
*
|
|
11
|
+
* `aria-current="page"` marks the current page. Without it the only signal is
|
|
12
|
+
* the highlight, which a screen-reader user does not get.
|
|
13
|
+
*/
|
|
14
|
+
import { component, html } from "@c9up/aurora";
|
|
15
|
+
import { buttonVariants } from "../atoms/Button.js";
|
|
16
|
+
import { cn } from "../lib/cn.js";
|
|
17
|
+
import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon, } from "../lib/icons.js";
|
|
18
|
+
import { read } from "../lib/props.js";
|
|
19
|
+
/**
|
|
20
|
+
* Which page numbers to show, at a fixed total width.
|
|
21
|
+
*
|
|
22
|
+
* Always the first page, the last page, a run around the current one, and one
|
|
23
|
+
* or two gaps — adding up to exactly `size` slots wherever the current page
|
|
24
|
+
* sits. The constant width is the whole reason this is computed rather than a
|
|
25
|
+
* range being sliced: a window that simply narrows near the ends makes the
|
|
26
|
+
* control reflow as you click through it, and the buttons move under the
|
|
27
|
+
* pointer.
|
|
28
|
+
*
|
|
29
|
+
* Near an end there is only one gap, so the run is one slot wider to make up
|
|
30
|
+
* for it. That is the case a naive implementation gets wrong.
|
|
31
|
+
*/
|
|
32
|
+
export function pageWindow(page, pageCount, size = 7) {
|
|
33
|
+
if (pageCount <= size)
|
|
34
|
+
return range(1, pageCount);
|
|
35
|
+
// Below five slots there is no room for first, last, a gap and a run, so
|
|
36
|
+
// the shape this function promises cannot be built.
|
|
37
|
+
const slots = Math.max(size, 5);
|
|
38
|
+
const current = Math.min(Math.max(page, 1), pageCount);
|
|
39
|
+
// Near an end the run absorbs the first (or last) page and there is a
|
|
40
|
+
// single gap, so it holds `slots - 2` entries. In the middle the first and
|
|
41
|
+
// last pages are separate and there are two gaps, leaving `slots - 4`.
|
|
42
|
+
const runNearEnd = slots - 2;
|
|
43
|
+
const runInMiddle = slots - 4;
|
|
44
|
+
if (current <= runNearEnd - 1) {
|
|
45
|
+
return [...range(1, runNearEnd), null, pageCount];
|
|
46
|
+
}
|
|
47
|
+
if (current >= pageCount - runNearEnd + 2) {
|
|
48
|
+
return [1, null, ...range(pageCount - runNearEnd + 1, pageCount)];
|
|
49
|
+
}
|
|
50
|
+
const before = Math.floor((runInMiddle - 1) / 2);
|
|
51
|
+
const start = current - before;
|
|
52
|
+
return [1, null, ...range(start, start + runInMiddle - 1), null, pageCount];
|
|
53
|
+
}
|
|
54
|
+
function range(from, to) {
|
|
55
|
+
const out = [];
|
|
56
|
+
for (let value = from; value <= to; value += 1)
|
|
57
|
+
out.push(value);
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
export const Pagination = component((props) => {
|
|
61
|
+
const page = () => read(props.page) ?? 1;
|
|
62
|
+
const pageCount = () => Math.max(1, read(props.pageCount) ?? 1);
|
|
63
|
+
const go = (target) => (event) => {
|
|
64
|
+
if (props.onPageChange === undefined)
|
|
65
|
+
return;
|
|
66
|
+
// Only intercept when the caller wants callback-driven paging. With an
|
|
67
|
+
// `href` and no handler the link must navigate normally.
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
props.onPageChange(Math.min(Math.max(target, 1), pageCount()));
|
|
70
|
+
};
|
|
71
|
+
return html `<nav
|
|
72
|
+
data-slot="pagination"
|
|
73
|
+
role="navigation"
|
|
74
|
+
aria-label="pagination"
|
|
75
|
+
class="${() => cn("mx-auto flex w-full justify-center", read(props.class))}"
|
|
76
|
+
>
|
|
77
|
+
<ul data-slot="pagination-content" class="flex flex-row items-center gap-1">
|
|
78
|
+
<li>
|
|
79
|
+
${link({
|
|
80
|
+
label: html `${ChevronLeftIcon({ class: "size-4" })}<span class="hidden sm:block">Previous</span>`,
|
|
81
|
+
ariaLabel: "Go to previous page",
|
|
82
|
+
href: props.href?.(page() - 1),
|
|
83
|
+
disabled: () => page() <= 1,
|
|
84
|
+
onClick: go(page() - 1),
|
|
85
|
+
size: "default",
|
|
86
|
+
extra: "gap-1 px-2.5 sm:pl-2.5",
|
|
87
|
+
})}
|
|
88
|
+
</li>
|
|
89
|
+
${() => pageWindow(page(), pageCount(), props.window ?? 7).map((entry, index) => entry === null
|
|
90
|
+
? html `<li aria-hidden="true">
|
|
91
|
+
<span class="flex size-9 items-center justify-center">
|
|
92
|
+
${MoreHorizontalIcon({ class: "size-4" })}
|
|
93
|
+
<span class="sr-only">More pages</span>
|
|
94
|
+
</span>
|
|
95
|
+
</li>`
|
|
96
|
+
: html `<li>
|
|
97
|
+
${link({
|
|
98
|
+
label: String(entry),
|
|
99
|
+
ariaLabel: `Go to page ${entry}`,
|
|
100
|
+
href: props.href?.(entry),
|
|
101
|
+
current: entry === page(),
|
|
102
|
+
onClick: go(entry),
|
|
103
|
+
size: "icon",
|
|
104
|
+
key: index,
|
|
105
|
+
})}
|
|
106
|
+
</li>`)}
|
|
107
|
+
<li>
|
|
108
|
+
${link({
|
|
109
|
+
label: html `<span class="hidden sm:block">Next</span>${ChevronRightIcon({ class: "size-4" })}`,
|
|
110
|
+
ariaLabel: "Go to next page",
|
|
111
|
+
href: props.href?.(page() + 1),
|
|
112
|
+
disabled: () => page() >= pageCount(),
|
|
113
|
+
onClick: go(page() + 1),
|
|
114
|
+
size: "default",
|
|
115
|
+
extra: "gap-1 px-2.5 sm:pr-2.5",
|
|
116
|
+
})}
|
|
117
|
+
</li>
|
|
118
|
+
</ul>
|
|
119
|
+
</nav>`;
|
|
120
|
+
});
|
|
121
|
+
/**
|
|
122
|
+
* One page link.
|
|
123
|
+
*
|
|
124
|
+
* Always an `<a>`, even without an `href`: pagination is navigation, and the
|
|
125
|
+
* role is what a screen reader uses to offer "list of links" here. The
|
|
126
|
+
* disabled ends carry `aria-disabled` plus `pointer-events-none` rather than
|
|
127
|
+
* being removed, so the control keeps its width at both ends of the range.
|
|
128
|
+
*/
|
|
129
|
+
function link(spec) {
|
|
130
|
+
const disabled = () => spec.disabled?.() === true;
|
|
131
|
+
return html `<a
|
|
132
|
+
data-slot="pagination-link"
|
|
133
|
+
href="${spec.href}"
|
|
134
|
+
aria-label="${spec.ariaLabel}"
|
|
135
|
+
aria-current="${spec.current === true ? "page" : undefined}"
|
|
136
|
+
aria-disabled="${() => (disabled() ? "true" : undefined)}"
|
|
137
|
+
tabindex="${() => (disabled() ? -1 : 0)}"
|
|
138
|
+
class="${() => cn(buttonVariants({
|
|
139
|
+
variant: spec.current === true ? "outline" : "ghost",
|
|
140
|
+
size: spec.size,
|
|
141
|
+
}), spec.extra, disabled() ? "pointer-events-none opacity-50" : "")}"
|
|
142
|
+
@click="${spec.onClick}"
|
|
143
|
+
>${spec.label}</a>`;
|
|
144
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RadioGroup — pick exactly one option.
|
|
3
|
+
*
|
|
4
|
+
* Native `<input type="radio">` throughout. The browser already implements the
|
|
5
|
+
* radio group pattern on a shared `name`: arrows move and select, only one can
|
|
6
|
+
* be checked, and the group is one tab stop. Radix reimplements all of that
|
|
7
|
+
* because it needs a stylable element; `appearance-none` gets nebula the same
|
|
8
|
+
* styling freedom while the behaviour stays the browser's.
|
|
9
|
+
*
|
|
10
|
+
* The indicator dot is a sibling shown by `peer-checked:`, so selecting a
|
|
11
|
+
* radio runs no JavaScript at all.
|
|
12
|
+
*/
|
|
13
|
+
import type { Child } from "../lib/children.js";
|
|
14
|
+
import { type Reactive } from "../lib/props.js";
|
|
15
|
+
export interface RadioOption {
|
|
16
|
+
value: string;
|
|
17
|
+
label: Child;
|
|
18
|
+
description?: Child;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface RadioGroupProps {
|
|
22
|
+
/** Shared `name`, which is what makes the browser treat these as one group. */
|
|
23
|
+
name: string;
|
|
24
|
+
options: readonly RadioOption[];
|
|
25
|
+
value?: Reactive<string | undefined>;
|
|
26
|
+
defaultValue?: string;
|
|
27
|
+
disabled?: Reactive<boolean>;
|
|
28
|
+
orientation?: "vertical" | "horizontal";
|
|
29
|
+
class?: Reactive<string>;
|
|
30
|
+
onValueChange?: (value: string) => void;
|
|
31
|
+
}
|
|
32
|
+
export declare const RadioGroup: (props?: RadioGroupProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RadioGroup — pick exactly one option.
|
|
3
|
+
*
|
|
4
|
+
* Native `<input type="radio">` throughout. The browser already implements the
|
|
5
|
+
* radio group pattern on a shared `name`: arrows move and select, only one can
|
|
6
|
+
* be checked, and the group is one tab stop. Radix reimplements all of that
|
|
7
|
+
* because it needs a stylable element; `appearance-none` gets nebula the same
|
|
8
|
+
* styling freedom while the behaviour stays the browser's.
|
|
9
|
+
*
|
|
10
|
+
* The indicator dot is a sibling shown by `peer-checked:`, so selecting a
|
|
11
|
+
* radio runs no JavaScript at all.
|
|
12
|
+
*/
|
|
13
|
+
import { component, html } from "@c9up/aurora";
|
|
14
|
+
import { cn } from "../lib/cn.js";
|
|
15
|
+
import { uid } from "../lib/id.js";
|
|
16
|
+
import { read, readOr } from "../lib/props.js";
|
|
17
|
+
const radioClasses = "peer size-4 shrink-0 appearance-none rounded-full border border-input bg-transparent shadow-xs outline-none transition-shadow dark:bg-input/30 checked:border-primary focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50";
|
|
18
|
+
export const RadioGroup = component((props) => {
|
|
19
|
+
const selected = () => {
|
|
20
|
+
const controlled = read(props.value);
|
|
21
|
+
return controlled === undefined ? props.defaultValue : controlled;
|
|
22
|
+
};
|
|
23
|
+
return html `<div
|
|
24
|
+
role="radiogroup"
|
|
25
|
+
data-slot="radio-group"
|
|
26
|
+
aria-orientation="${props.orientation ?? "vertical"}"
|
|
27
|
+
class="${() => cn("grid gap-3", props.orientation === "horizontal" ? "grid-flow-col auto-cols-max" : "", read(props.class))}"
|
|
28
|
+
>
|
|
29
|
+
${props.options.map((option) => renderOption(option, props, selected))}
|
|
30
|
+
</div>`;
|
|
31
|
+
});
|
|
32
|
+
function renderOption(option, props, selected) {
|
|
33
|
+
const id = uid("radio");
|
|
34
|
+
const descriptionId = option.description === undefined ? undefined : uid("radio-description");
|
|
35
|
+
return html `<div class="flex items-start gap-3">
|
|
36
|
+
<span class="relative flex size-4 shrink-0 items-center justify-center">
|
|
37
|
+
<input
|
|
38
|
+
type="radio"
|
|
39
|
+
data-slot="radio-group-item"
|
|
40
|
+
id="${id}"
|
|
41
|
+
name="${props.name}"
|
|
42
|
+
value="${option.value}"
|
|
43
|
+
aria-describedby="${descriptionId}"
|
|
44
|
+
?disabled="${() => option.disabled === true || readOr(props.disabled, false)}"
|
|
45
|
+
.checked="${() => selected() === option.value}"
|
|
46
|
+
class="${radioClasses}"
|
|
47
|
+
@change="${() => props.onValueChange?.(option.value)}"
|
|
48
|
+
/>
|
|
49
|
+
<span
|
|
50
|
+
class="bg-primary pointer-events-none absolute size-2 rounded-full opacity-0 transition-opacity peer-checked:opacity-100"
|
|
51
|
+
></span>
|
|
52
|
+
</span>
|
|
53
|
+
<div class="grid gap-1 leading-none">
|
|
54
|
+
<label
|
|
55
|
+
for="${id}"
|
|
56
|
+
class="text-sm leading-none font-medium select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50"
|
|
57
|
+
>${option.label}</label>
|
|
58
|
+
${option.description === undefined
|
|
59
|
+
? null
|
|
60
|
+
: html `<p id="${descriptionId}" class="text-muted-foreground text-sm">
|
|
61
|
+
${option.description}
|
|
62
|
+
</p>`}
|
|
63
|
+
</div>
|
|
64
|
+
</div>`;
|
|
65
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resizable — two panes with a draggable divider.
|
|
3
|
+
*
|
|
4
|
+
* `react-resizable-panels`, which shadcn wraps, supports arbitrarily nested
|
|
5
|
+
* groups with persisted layouts and collapse-to-zero. nebula ships the case
|
|
6
|
+
* that covers almost every use of it: two panes, one handle. Nesting two of
|
|
7
|
+
* these gets a three-pane layout, which is where the demand stops.
|
|
8
|
+
*
|
|
9
|
+
* The split is one number — the first pane's percentage — driving both panes
|
|
10
|
+
* through `flex-basis`. One source of truth means the two can never disagree,
|
|
11
|
+
* which is the failure mode of tracking a size per pane.
|
|
12
|
+
*
|
|
13
|
+
* Pointer capture on the handle is what keeps the drag alive when the pointer
|
|
14
|
+
* outruns it, which it will: the handle is a few pixels wide and the pointer
|
|
15
|
+
* moves faster than layout. Without capture the drag drops the moment the
|
|
16
|
+
* cursor leaves.
|
|
17
|
+
*
|
|
18
|
+
* The handle is also a real `separator` with `aria-valuenow`, and arrow keys
|
|
19
|
+
* move it. A resizable layout reachable only by dragging is unusable by
|
|
20
|
+
* keyboard, and this is the whole of what it takes to fix that.
|
|
21
|
+
*/
|
|
22
|
+
import { type Slot } from "../lib/children.js";
|
|
23
|
+
import { type Reactive } from "../lib/props.js";
|
|
24
|
+
export interface ResizableProps {
|
|
25
|
+
first?: Slot;
|
|
26
|
+
second?: Slot;
|
|
27
|
+
direction?: "horizontal" | "vertical";
|
|
28
|
+
/** First pane's share at first render, in percent. Default `50`. */
|
|
29
|
+
defaultSize?: number;
|
|
30
|
+
minSize?: number;
|
|
31
|
+
maxSize?: number;
|
|
32
|
+
/** Show the grip dots on the handle. */
|
|
33
|
+
withHandle?: boolean;
|
|
34
|
+
class?: Reactive<string>;
|
|
35
|
+
onResize?: (percent: number) => void;
|
|
36
|
+
}
|
|
37
|
+
export declare const Resizable: (props?: ResizableProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resizable — two panes with a draggable divider.
|
|
3
|
+
*
|
|
4
|
+
* `react-resizable-panels`, which shadcn wraps, supports arbitrarily nested
|
|
5
|
+
* groups with persisted layouts and collapse-to-zero. nebula ships the case
|
|
6
|
+
* that covers almost every use of it: two panes, one handle. Nesting two of
|
|
7
|
+
* these gets a three-pane layout, which is where the demand stops.
|
|
8
|
+
*
|
|
9
|
+
* The split is one number — the first pane's percentage — driving both panes
|
|
10
|
+
* through `flex-basis`. One source of truth means the two can never disagree,
|
|
11
|
+
* which is the failure mode of tracking a size per pane.
|
|
12
|
+
*
|
|
13
|
+
* Pointer capture on the handle is what keeps the drag alive when the pointer
|
|
14
|
+
* outruns it, which it will: the handle is a few pixels wide and the pointer
|
|
15
|
+
* moves faster than layout. Without capture the drag drops the moment the
|
|
16
|
+
* cursor leaves.
|
|
17
|
+
*
|
|
18
|
+
* The handle is also a real `separator` with `aria-valuenow`, and arrow keys
|
|
19
|
+
* move it. A resizable layout reachable only by dragging is unusable by
|
|
20
|
+
* keyboard, and this is the whole of what it takes to fix that.
|
|
21
|
+
*/
|
|
22
|
+
import { component, html, signal } from "@c9up/aurora";
|
|
23
|
+
import { slot } from "../lib/children.js";
|
|
24
|
+
import { cn } from "../lib/cn.js";
|
|
25
|
+
import { GripVerticalIcon } from "../lib/icons.js";
|
|
26
|
+
import { uid } from "../lib/id.js";
|
|
27
|
+
import { read } from "../lib/props.js";
|
|
28
|
+
const KEYBOARD_STEP = 5;
|
|
29
|
+
export const Resizable = component((props) => {
|
|
30
|
+
const horizontal = props.direction !== "vertical";
|
|
31
|
+
const min = props.minSize ?? 10;
|
|
32
|
+
const max = props.maxSize ?? 90;
|
|
33
|
+
const size = signal(clamp(props.defaultSize ?? 50, min, max));
|
|
34
|
+
const rootId = uid("resizable");
|
|
35
|
+
function setSize(percent) {
|
|
36
|
+
const next = clamp(percent, min, max);
|
|
37
|
+
size(next);
|
|
38
|
+
props.onResize?.(next);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Convert a pointer position into a percentage of the group.
|
|
42
|
+
*
|
|
43
|
+
* Measured against the group's own rect on every move rather than a rect
|
|
44
|
+
* cached at drag start: the window can be resized mid-drag, and a stale rect
|
|
45
|
+
* makes the divider drift away from the pointer.
|
|
46
|
+
*/
|
|
47
|
+
function percentAt(event) {
|
|
48
|
+
const root = document.getElementById(rootId);
|
|
49
|
+
if (root === null)
|
|
50
|
+
return null;
|
|
51
|
+
const rect = root.getBoundingClientRect();
|
|
52
|
+
const span = horizontal ? rect.width : rect.height;
|
|
53
|
+
if (span === 0)
|
|
54
|
+
return null;
|
|
55
|
+
const offset = horizontal
|
|
56
|
+
? event.clientX - rect.left
|
|
57
|
+
: event.clientY - rect.top;
|
|
58
|
+
return (offset / span) * 100;
|
|
59
|
+
}
|
|
60
|
+
function onPointerDown(event) {
|
|
61
|
+
const target = event.currentTarget;
|
|
62
|
+
if (!(target instanceof HTMLElement))
|
|
63
|
+
return;
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
target.setPointerCapture(event.pointerId);
|
|
66
|
+
const onMove = (move) => {
|
|
67
|
+
const percent = percentAt(move);
|
|
68
|
+
if (percent !== null)
|
|
69
|
+
setSize(percent);
|
|
70
|
+
};
|
|
71
|
+
const onUp = () => {
|
|
72
|
+
target.releasePointerCapture(event.pointerId);
|
|
73
|
+
target.removeEventListener("pointermove", onMove);
|
|
74
|
+
target.removeEventListener("pointerup", onUp);
|
|
75
|
+
target.removeEventListener("pointercancel", onUp);
|
|
76
|
+
};
|
|
77
|
+
target.addEventListener("pointermove", onMove);
|
|
78
|
+
target.addEventListener("pointerup", onUp);
|
|
79
|
+
target.addEventListener("pointercancel", onUp);
|
|
80
|
+
}
|
|
81
|
+
function onKeyDown(event) {
|
|
82
|
+
const back = horizontal ? "ArrowLeft" : "ArrowUp";
|
|
83
|
+
const forward = horizontal ? "ArrowRight" : "ArrowDown";
|
|
84
|
+
if (event.key === back) {
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
setSize(size() - KEYBOARD_STEP);
|
|
87
|
+
}
|
|
88
|
+
else if (event.key === forward) {
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
setSize(size() + KEYBOARD_STEP);
|
|
91
|
+
}
|
|
92
|
+
else if (event.key === "Home") {
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
setSize(min);
|
|
95
|
+
}
|
|
96
|
+
else if (event.key === "End") {
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
setSize(max);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return html `<div
|
|
102
|
+
data-slot="resizable-group"
|
|
103
|
+
id="${rootId}"
|
|
104
|
+
data-direction="${horizontal ? "horizontal" : "vertical"}"
|
|
105
|
+
class="${() => cn("flex h-full w-full", horizontal ? "flex-row" : "flex-col", read(props.class))}"
|
|
106
|
+
>
|
|
107
|
+
<div
|
|
108
|
+
data-slot="resizable-panel"
|
|
109
|
+
class="overflow-hidden"
|
|
110
|
+
style="${() => `flex: 0 0 ${size()}%`}"
|
|
111
|
+
>${slot(props.first)}</div>
|
|
112
|
+
<div
|
|
113
|
+
data-slot="resizable-handle"
|
|
114
|
+
role="separator"
|
|
115
|
+
tabindex="0"
|
|
116
|
+
aria-orientation="${horizontal ? "vertical" : "horizontal"}"
|
|
117
|
+
aria-valuemin="${min}"
|
|
118
|
+
aria-valuemax="${max}"
|
|
119
|
+
aria-valuenow="${() => Math.round(size())}"
|
|
120
|
+
aria-label="Resize panes"
|
|
121
|
+
class="${cn("bg-border relative flex shrink-0 items-center justify-center outline-none", "focus-visible:ring-ring focus-visible:ring-1 focus-visible:ring-offset-1", horizontal ? "w-px cursor-col-resize" : "h-px cursor-row-resize")}"
|
|
122
|
+
@pointerdown="${onPointerDown}"
|
|
123
|
+
@keydown="${onKeyDown}"
|
|
124
|
+
>
|
|
125
|
+
${props.withHandle === true
|
|
126
|
+
? html `<span
|
|
127
|
+
class="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border"
|
|
128
|
+
>${GripVerticalIcon({ class: "size-2.5" })}</span>`
|
|
129
|
+
: null}
|
|
130
|
+
</div>
|
|
131
|
+
<div data-slot="resizable-panel" class="flex-1 overflow-hidden">${slot(props.second)}</div>
|
|
132
|
+
</div>`;
|
|
133
|
+
});
|
|
134
|
+
function clamp(value, min, max) {
|
|
135
|
+
return Math.min(Math.max(value, min), max);
|
|
136
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Table — the styled primitives for tabular data.
|
|
3
|
+
*
|
|
4
|
+
* Real table elements, not a grid of divs. A `<table>` gives screen readers
|
|
5
|
+
* row and column context for free ("row 3, Amount, 42"), and reproducing that
|
|
6
|
+
* over divs means hand-maintaining `role="grid"`, `aria-rowindex` and
|
|
7
|
+
* `aria-colindex` on every cell. The container's `overflow-x-auto` handles
|
|
8
|
+
* narrow viewports without touching the semantics.
|
|
9
|
+
*
|
|
10
|
+
* These are the presentation layer only. `DataTable` in `organisms/` adds
|
|
11
|
+
* sorting, selection and pagination on top of them.
|
|
12
|
+
*/
|
|
13
|
+
import { type Reactive } from "../lib/props.js";
|
|
14
|
+
import type { StyledProps } from "../lib/styled.js";
|
|
15
|
+
export type TableProps = StyledProps;
|
|
16
|
+
export interface TableCellProps extends StyledProps {
|
|
17
|
+
colspan?: number;
|
|
18
|
+
rowspan?: number;
|
|
19
|
+
/** `col` for a column header, `row` for a row header. */
|
|
20
|
+
scope?: "col" | "row";
|
|
21
|
+
/** Reflected as `aria-sort` — set it on the column currently sorted. */
|
|
22
|
+
sort?: Reactive<"ascending" | "descending" | "none" | undefined>;
|
|
23
|
+
onClick?: (event: MouseEvent) => void;
|
|
24
|
+
}
|
|
25
|
+
export declare const Table: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
26
|
+
export declare const TableHeader: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
27
|
+
export declare const TableBody: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
28
|
+
export declare const TableFooter: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
29
|
+
export interface TableRowProps extends StyledProps {
|
|
30
|
+
/** Reflected as `data-state="selected"`, which the row styling keys off. */
|
|
31
|
+
selected?: Reactive<boolean>;
|
|
32
|
+
onClick?: (event: MouseEvent) => void;
|
|
33
|
+
}
|
|
34
|
+
export declare const TableRow: (props?: TableRowProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
35
|
+
export declare const TableHead: (props?: TableCellProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
36
|
+
export declare const TableCell: (props?: TableCellProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
37
|
+
export declare const TableCaption: (props?: StyledProps | undefined) => import("@c9up/aurora").TemplateResult;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Table — the styled primitives for tabular data.
|
|
3
|
+
*
|
|
4
|
+
* Real table elements, not a grid of divs. A `<table>` gives screen readers
|
|
5
|
+
* row and column context for free ("row 3, Amount, 42"), and reproducing that
|
|
6
|
+
* over divs means hand-maintaining `role="grid"`, `aria-rowindex` and
|
|
7
|
+
* `aria-colindex` on every cell. The container's `overflow-x-auto` handles
|
|
8
|
+
* narrow viewports without touching the semantics.
|
|
9
|
+
*
|
|
10
|
+
* These are the presentation layer only. `DataTable` in `organisms/` adds
|
|
11
|
+
* sorting, selection and pagination on top of them.
|
|
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
|
+
export const Table = component((props) => {
|
|
18
|
+
return html `<div
|
|
19
|
+
data-slot="table-container"
|
|
20
|
+
class="relative w-full overflow-x-auto"
|
|
21
|
+
>
|
|
22
|
+
<table
|
|
23
|
+
data-slot="table"
|
|
24
|
+
class="${() => cn("w-full caption-bottom text-sm", read(props.class))}"
|
|
25
|
+
>${slot(props.children)}</table>
|
|
26
|
+
</div>`;
|
|
27
|
+
});
|
|
28
|
+
export const TableHeader = component((props) => {
|
|
29
|
+
return html `<thead
|
|
30
|
+
data-slot="table-header"
|
|
31
|
+
class="${() => cn("[&_tr]:border-b", read(props.class))}"
|
|
32
|
+
>${slot(props.children)}</thead>`;
|
|
33
|
+
});
|
|
34
|
+
export const TableBody = component((props) => {
|
|
35
|
+
return html `<tbody
|
|
36
|
+
data-slot="table-body"
|
|
37
|
+
class="${() => cn("[&_tr:last-child]:border-0", read(props.class))}"
|
|
38
|
+
>${slot(props.children)}</tbody>`;
|
|
39
|
+
});
|
|
40
|
+
export const TableFooter = component((props) => {
|
|
41
|
+
return html `<tfoot
|
|
42
|
+
data-slot="table-footer"
|
|
43
|
+
class="${() => cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", read(props.class))}"
|
|
44
|
+
>${slot(props.children)}</tfoot>`;
|
|
45
|
+
});
|
|
46
|
+
export const TableRow = component((props) => {
|
|
47
|
+
return html `<tr
|
|
48
|
+
data-slot="table-row"
|
|
49
|
+
data-state="${() => (read(props.selected) === true ? "selected" : undefined)}"
|
|
50
|
+
class="${() => cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", read(props.class))}"
|
|
51
|
+
@click="${props.onClick}"
|
|
52
|
+
>${slot(props.children)}</tr>`;
|
|
53
|
+
});
|
|
54
|
+
export const TableHead = component((props) => {
|
|
55
|
+
return html `<th
|
|
56
|
+
data-slot="table-head"
|
|
57
|
+
scope="${props.scope ?? "col"}"
|
|
58
|
+
colspan="${props.colspan}"
|
|
59
|
+
rowspan="${props.rowspan}"
|
|
60
|
+
aria-sort="${() => read(props.sort)}"
|
|
61
|
+
class="${() => cn("text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0", read(props.class))}"
|
|
62
|
+
@click="${props.onClick}"
|
|
63
|
+
>${slot(props.children)}</th>`;
|
|
64
|
+
});
|
|
65
|
+
export const TableCell = component((props) => {
|
|
66
|
+
return html `<td
|
|
67
|
+
data-slot="table-cell"
|
|
68
|
+
colspan="${props.colspan}"
|
|
69
|
+
rowspan="${props.rowspan}"
|
|
70
|
+
class="${() => cn("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0", read(props.class))}"
|
|
71
|
+
>${slot(props.children)}</td>`;
|
|
72
|
+
});
|
|
73
|
+
export const TableCaption = component((props) => {
|
|
74
|
+
return html `<caption
|
|
75
|
+
data-slot="table-caption"
|
|
76
|
+
class="${() => cn("text-muted-foreground mt-4 text-sm", read(props.class))}"
|
|
77
|
+
>${slot(props.children)}</caption>`;
|
|
78
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
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 type { Child, Slot } from "../lib/children.js";
|
|
19
|
+
import { type Reactive } from "../lib/props.js";
|
|
20
|
+
export interface TabItem {
|
|
21
|
+
value: string;
|
|
22
|
+
label: Child;
|
|
23
|
+
content: Slot;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface TabsProps {
|
|
27
|
+
items: readonly TabItem[];
|
|
28
|
+
/** Selected at first render. Defaults to the first enabled tab. */
|
|
29
|
+
defaultValue?: string;
|
|
30
|
+
orientation?: "horizontal" | "vertical";
|
|
31
|
+
/** Selecting follows focus. Default `true`. */
|
|
32
|
+
activateOnFocus?: boolean;
|
|
33
|
+
onValueChange?: (value: string) => void;
|
|
34
|
+
class?: Reactive<string>;
|
|
35
|
+
listClass?: Reactive<string>;
|
|
36
|
+
}
|
|
37
|
+
export declare const Tabs: (props?: TabsProps | undefined) => import("@c9up/aurora").TemplateResult;
|