@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,166 @@
|
|
|
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
|
+
|
|
23
|
+
import { component, html, signal } from "@c9up/aurora";
|
|
24
|
+
import { type Slot, slot } from "../lib/children.js";
|
|
25
|
+
import { cn } from "../lib/cn.js";
|
|
26
|
+
import { GripVerticalIcon } from "../lib/icons.js";
|
|
27
|
+
import { uid } from "../lib/id.js";
|
|
28
|
+
import { type Reactive, read } from "../lib/props.js";
|
|
29
|
+
|
|
30
|
+
export interface ResizableProps {
|
|
31
|
+
first?: Slot;
|
|
32
|
+
second?: Slot;
|
|
33
|
+
direction?: "horizontal" | "vertical";
|
|
34
|
+
/** First pane's share at first render, in percent. Default `50`. */
|
|
35
|
+
defaultSize?: number;
|
|
36
|
+
minSize?: number;
|
|
37
|
+
maxSize?: number;
|
|
38
|
+
/** Show the grip dots on the handle. */
|
|
39
|
+
withHandle?: boolean;
|
|
40
|
+
class?: Reactive<string>;
|
|
41
|
+
onResize?: (percent: number) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const KEYBOARD_STEP = 5;
|
|
45
|
+
|
|
46
|
+
export const Resizable = component<ResizableProps>((props) => {
|
|
47
|
+
const horizontal = props.direction !== "vertical";
|
|
48
|
+
const min = props.minSize ?? 10;
|
|
49
|
+
const max = props.maxSize ?? 90;
|
|
50
|
+
const size = signal(clamp(props.defaultSize ?? 50, min, max));
|
|
51
|
+
const rootId = uid("resizable");
|
|
52
|
+
|
|
53
|
+
function setSize(percent: number): void {
|
|
54
|
+
const next = clamp(percent, min, max);
|
|
55
|
+
size(next);
|
|
56
|
+
props.onResize?.(next);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Convert a pointer position into a percentage of the group.
|
|
61
|
+
*
|
|
62
|
+
* Measured against the group's own rect on every move rather than a rect
|
|
63
|
+
* cached at drag start: the window can be resized mid-drag, and a stale rect
|
|
64
|
+
* makes the divider drift away from the pointer.
|
|
65
|
+
*/
|
|
66
|
+
function percentAt(event: PointerEvent): number | null {
|
|
67
|
+
const root = document.getElementById(rootId);
|
|
68
|
+
if (root === null) return null;
|
|
69
|
+
const rect = root.getBoundingClientRect();
|
|
70
|
+
const span = horizontal ? rect.width : rect.height;
|
|
71
|
+
if (span === 0) return null;
|
|
72
|
+
const offset = horizontal
|
|
73
|
+
? event.clientX - rect.left
|
|
74
|
+
: event.clientY - rect.top;
|
|
75
|
+
return (offset / span) * 100;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function onPointerDown(event: PointerEvent): void {
|
|
79
|
+
const target = event.currentTarget;
|
|
80
|
+
if (!(target instanceof HTMLElement)) return;
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
target.setPointerCapture(event.pointerId);
|
|
83
|
+
|
|
84
|
+
const onMove = (move: PointerEvent): void => {
|
|
85
|
+
const percent = percentAt(move);
|
|
86
|
+
if (percent !== null) setSize(percent);
|
|
87
|
+
};
|
|
88
|
+
const onUp = (): void => {
|
|
89
|
+
target.releasePointerCapture(event.pointerId);
|
|
90
|
+
target.removeEventListener("pointermove", onMove);
|
|
91
|
+
target.removeEventListener("pointerup", onUp);
|
|
92
|
+
target.removeEventListener("pointercancel", onUp);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
target.addEventListener("pointermove", onMove);
|
|
96
|
+
target.addEventListener("pointerup", onUp);
|
|
97
|
+
target.addEventListener("pointercancel", onUp);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function onKeyDown(event: KeyboardEvent): void {
|
|
101
|
+
const back = horizontal ? "ArrowLeft" : "ArrowUp";
|
|
102
|
+
const forward = horizontal ? "ArrowRight" : "ArrowDown";
|
|
103
|
+
|
|
104
|
+
if (event.key === back) {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
setSize(size() - KEYBOARD_STEP);
|
|
107
|
+
} else if (event.key === forward) {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
setSize(size() + KEYBOARD_STEP);
|
|
110
|
+
} else if (event.key === "Home") {
|
|
111
|
+
event.preventDefault();
|
|
112
|
+
setSize(min);
|
|
113
|
+
} else if (event.key === "End") {
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
setSize(max);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return html`<div
|
|
120
|
+
data-slot="resizable-group"
|
|
121
|
+
id="${rootId}"
|
|
122
|
+
data-direction="${horizontal ? "horizontal" : "vertical"}"
|
|
123
|
+
class="${() =>
|
|
124
|
+
cn(
|
|
125
|
+
"flex h-full w-full",
|
|
126
|
+
horizontal ? "flex-row" : "flex-col",
|
|
127
|
+
read(props.class),
|
|
128
|
+
)}"
|
|
129
|
+
>
|
|
130
|
+
<div
|
|
131
|
+
data-slot="resizable-panel"
|
|
132
|
+
class="overflow-hidden"
|
|
133
|
+
style="${() => `flex: 0 0 ${size()}%`}"
|
|
134
|
+
>${slot(props.first)}</div>
|
|
135
|
+
<div
|
|
136
|
+
data-slot="resizable-handle"
|
|
137
|
+
role="separator"
|
|
138
|
+
tabindex="0"
|
|
139
|
+
aria-orientation="${horizontal ? "vertical" : "horizontal"}"
|
|
140
|
+
aria-valuemin="${min}"
|
|
141
|
+
aria-valuemax="${max}"
|
|
142
|
+
aria-valuenow="${() => Math.round(size())}"
|
|
143
|
+
aria-label="Resize panes"
|
|
144
|
+
class="${cn(
|
|
145
|
+
"bg-border relative flex shrink-0 items-center justify-center outline-none",
|
|
146
|
+
"focus-visible:ring-ring focus-visible:ring-1 focus-visible:ring-offset-1",
|
|
147
|
+
horizontal ? "w-px cursor-col-resize" : "h-px cursor-row-resize",
|
|
148
|
+
)}"
|
|
149
|
+
@pointerdown="${onPointerDown}"
|
|
150
|
+
@keydown="${onKeyDown}"
|
|
151
|
+
>
|
|
152
|
+
${
|
|
153
|
+
props.withHandle === true
|
|
154
|
+
? html`<span
|
|
155
|
+
class="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border"
|
|
156
|
+
>${GripVerticalIcon({ class: "size-2.5" })}</span>`
|
|
157
|
+
: null
|
|
158
|
+
}
|
|
159
|
+
</div>
|
|
160
|
+
<div data-slot="resizable-panel" class="flex-1 overflow-hidden">${slot(props.second)}</div>
|
|
161
|
+
</div>`;
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
function clamp(value: number, min: number, max: number): number {
|
|
165
|
+
return Math.min(Math.max(value, min), max);
|
|
166
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
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
|
+
|
|
14
|
+
import { component, html } from "@c9up/aurora";
|
|
15
|
+
import { slot } from "../lib/children.js";
|
|
16
|
+
import { cn } from "../lib/cn.js";
|
|
17
|
+
import { type Reactive, read } from "../lib/props.js";
|
|
18
|
+
import type { StyledProps } from "../lib/styled.js";
|
|
19
|
+
|
|
20
|
+
export type TableProps = StyledProps;
|
|
21
|
+
|
|
22
|
+
export interface TableCellProps extends StyledProps {
|
|
23
|
+
colspan?: number;
|
|
24
|
+
rowspan?: number;
|
|
25
|
+
/** `col` for a column header, `row` for a row header. */
|
|
26
|
+
scope?: "col" | "row";
|
|
27
|
+
/** Reflected as `aria-sort` — set it on the column currently sorted. */
|
|
28
|
+
sort?: Reactive<"ascending" | "descending" | "none" | undefined>;
|
|
29
|
+
onClick?: (event: MouseEvent) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const Table = component<TableProps>((props) => {
|
|
33
|
+
return html`<div
|
|
34
|
+
data-slot="table-container"
|
|
35
|
+
class="relative w-full overflow-x-auto"
|
|
36
|
+
>
|
|
37
|
+
<table
|
|
38
|
+
data-slot="table"
|
|
39
|
+
class="${() => cn("w-full caption-bottom text-sm", read(props.class))}"
|
|
40
|
+
>${slot(props.children)}</table>
|
|
41
|
+
</div>`;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const TableHeader = component<TableProps>((props) => {
|
|
45
|
+
return html`<thead
|
|
46
|
+
data-slot="table-header"
|
|
47
|
+
class="${() => cn("[&_tr]:border-b", read(props.class))}"
|
|
48
|
+
>${slot(props.children)}</thead>`;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const TableBody = component<TableProps>((props) => {
|
|
52
|
+
return html`<tbody
|
|
53
|
+
data-slot="table-body"
|
|
54
|
+
class="${() => cn("[&_tr:last-child]:border-0", read(props.class))}"
|
|
55
|
+
>${slot(props.children)}</tbody>`;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const TableFooter = component<TableProps>((props) => {
|
|
59
|
+
return html`<tfoot
|
|
60
|
+
data-slot="table-footer"
|
|
61
|
+
class="${() => cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", read(props.class))}"
|
|
62
|
+
>${slot(props.children)}</tfoot>`;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export interface TableRowProps extends StyledProps {
|
|
66
|
+
/** Reflected as `data-state="selected"`, which the row styling keys off. */
|
|
67
|
+
selected?: Reactive<boolean>;
|
|
68
|
+
onClick?: (event: MouseEvent) => void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const TableRow = component<TableRowProps>((props) => {
|
|
72
|
+
return html`<tr
|
|
73
|
+
data-slot="table-row"
|
|
74
|
+
data-state="${() => (read(props.selected) === true ? "selected" : undefined)}"
|
|
75
|
+
class="${() =>
|
|
76
|
+
cn(
|
|
77
|
+
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
|
|
78
|
+
read(props.class),
|
|
79
|
+
)}"
|
|
80
|
+
@click="${props.onClick}"
|
|
81
|
+
>${slot(props.children)}</tr>`;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export const TableHead = component<TableCellProps>((props) => {
|
|
85
|
+
return html`<th
|
|
86
|
+
data-slot="table-head"
|
|
87
|
+
scope="${props.scope ?? "col"}"
|
|
88
|
+
colspan="${props.colspan}"
|
|
89
|
+
rowspan="${props.rowspan}"
|
|
90
|
+
aria-sort="${() => read(props.sort)}"
|
|
91
|
+
class="${() =>
|
|
92
|
+
cn(
|
|
93
|
+
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0",
|
|
94
|
+
read(props.class),
|
|
95
|
+
)}"
|
|
96
|
+
@click="${props.onClick}"
|
|
97
|
+
>${slot(props.children)}</th>`;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
export const TableCell = component<TableCellProps>((props) => {
|
|
101
|
+
return html`<td
|
|
102
|
+
data-slot="table-cell"
|
|
103
|
+
colspan="${props.colspan}"
|
|
104
|
+
rowspan="${props.rowspan}"
|
|
105
|
+
class="${() =>
|
|
106
|
+
cn(
|
|
107
|
+
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0",
|
|
108
|
+
read(props.class),
|
|
109
|
+
)}"
|
|
110
|
+
>${slot(props.children)}</td>`;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
export const TableCaption = component<TableProps>((props) => {
|
|
114
|
+
return html`<caption
|
|
115
|
+
data-slot="table-caption"
|
|
116
|
+
class="${() => cn("text-muted-foreground mt-4 text-sm", read(props.class))}"
|
|
117
|
+
>${slot(props.children)}</caption>`;
|
|
118
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
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
|
+
|
|
19
|
+
import { component, html, onMount, onUnmount, signal } from "@c9up/aurora";
|
|
20
|
+
import type { Child, Slot } from "../lib/children.js";
|
|
21
|
+
import { slot } from "../lib/children.js";
|
|
22
|
+
import { cn } from "../lib/cn.js";
|
|
23
|
+
import { uid } from "../lib/id.js";
|
|
24
|
+
import { type Reactive, read } from "../lib/props.js";
|
|
25
|
+
import { type RovingFocus, rovingFocus } from "../primitives/rovingFocus.js";
|
|
26
|
+
|
|
27
|
+
export interface TabItem {
|
|
28
|
+
value: string;
|
|
29
|
+
label: Child;
|
|
30
|
+
content: Slot;
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TabsProps {
|
|
35
|
+
items: readonly TabItem[];
|
|
36
|
+
/** Selected at first render. Defaults to the first enabled tab. */
|
|
37
|
+
defaultValue?: string;
|
|
38
|
+
orientation?: "horizontal" | "vertical";
|
|
39
|
+
/** Selecting follows focus. Default `true`. */
|
|
40
|
+
activateOnFocus?: boolean;
|
|
41
|
+
onValueChange?: (value: string) => void;
|
|
42
|
+
class?: Reactive<string>;
|
|
43
|
+
listClass?: Reactive<string>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const Tabs = component<TabsProps>((props) => {
|
|
47
|
+
const orientation = props.orientation ?? "horizontal";
|
|
48
|
+
const first = props.items.find((item) => item.disabled !== true);
|
|
49
|
+
const active = signal(props.defaultValue ?? first?.value ?? "");
|
|
50
|
+
const listId = uid("tabs-list");
|
|
51
|
+
|
|
52
|
+
// Ids are minted once per tab, up front, because a tab and its panel each
|
|
53
|
+
// need to name the other — one of them has to know both before rendering.
|
|
54
|
+
const ids = new Map<string, { tab: string; panel: string }>();
|
|
55
|
+
for (const item of props.items) {
|
|
56
|
+
ids.set(item.value, { tab: uid("tab"), panel: uid("tab-panel") });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function select(value: string): void {
|
|
60
|
+
if (active() === value) return;
|
|
61
|
+
active(value);
|
|
62
|
+
props.onValueChange?.(value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let group: RovingFocus | undefined;
|
|
66
|
+
onMount(() => {
|
|
67
|
+
group = rovingFocus({
|
|
68
|
+
container: () => document.getElementById(listId),
|
|
69
|
+
itemSelector: "[role='tab']",
|
|
70
|
+
orientation,
|
|
71
|
+
onFocusChange: (item) => {
|
|
72
|
+
if (props.activateOnFocus === false) return;
|
|
73
|
+
const value = item.getAttribute("data-value");
|
|
74
|
+
if (value !== null) select(value);
|
|
75
|
+
},
|
|
76
|
+
onSelect: (item) => {
|
|
77
|
+
const value = item.getAttribute("data-value");
|
|
78
|
+
if (value !== null) select(value);
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
group.sync();
|
|
82
|
+
});
|
|
83
|
+
onUnmount(() => group?.destroy());
|
|
84
|
+
|
|
85
|
+
return html`<div
|
|
86
|
+
data-slot="tabs"
|
|
87
|
+
data-orientation="${orientation}"
|
|
88
|
+
class="${() => cn("flex gap-2", orientation === "vertical" ? "flex-row" : "flex-col", read(props.class))}"
|
|
89
|
+
>
|
|
90
|
+
<div
|
|
91
|
+
role="tablist"
|
|
92
|
+
data-slot="tabs-list"
|
|
93
|
+
id="${listId}"
|
|
94
|
+
aria-orientation="${orientation}"
|
|
95
|
+
class="${() =>
|
|
96
|
+
cn(
|
|
97
|
+
"bg-muted text-muted-foreground inline-flex w-fit items-center justify-center rounded-lg p-[3px]",
|
|
98
|
+
orientation === "vertical" ? "flex-col" : "h-9",
|
|
99
|
+
read(props.listClass),
|
|
100
|
+
)}"
|
|
101
|
+
>
|
|
102
|
+
${props.items.map((item) => renderTab(item, ids, active, select))}
|
|
103
|
+
</div>
|
|
104
|
+
${props.items.map((item) => renderPanel(item, ids, active))}
|
|
105
|
+
</div>`;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
function renderTab(
|
|
109
|
+
item: TabItem,
|
|
110
|
+
ids: ReadonlyMap<string, { tab: string; panel: string }>,
|
|
111
|
+
active: () => string,
|
|
112
|
+
select: (value: string) => void,
|
|
113
|
+
): Child {
|
|
114
|
+
const id = ids.get(item.value);
|
|
115
|
+
const selected = (): boolean => active() === item.value;
|
|
116
|
+
|
|
117
|
+
return html`<button
|
|
118
|
+
type="button"
|
|
119
|
+
role="tab"
|
|
120
|
+
data-slot="tabs-trigger"
|
|
121
|
+
data-value="${item.value}"
|
|
122
|
+
data-state="${() => (selected() ? "active" : "inactive")}"
|
|
123
|
+
data-disabled="${item.disabled === true ? "" : undefined}"
|
|
124
|
+
id="${id?.tab}"
|
|
125
|
+
aria-selected="${() => (selected() ? "true" : "false")}"
|
|
126
|
+
aria-controls="${id?.panel}"
|
|
127
|
+
?disabled="${item.disabled === true}"
|
|
128
|
+
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"
|
|
129
|
+
@click="${() => select(item.value)}"
|
|
130
|
+
>${item.label}</button>`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function renderPanel(
|
|
134
|
+
item: TabItem,
|
|
135
|
+
ids: ReadonlyMap<string, { tab: string; panel: string }>,
|
|
136
|
+
active: () => string,
|
|
137
|
+
): Child {
|
|
138
|
+
const id = ids.get(item.value);
|
|
139
|
+
return html`<div
|
|
140
|
+
role="tabpanel"
|
|
141
|
+
data-slot="tabs-content"
|
|
142
|
+
id="${id?.panel}"
|
|
143
|
+
aria-labelledby="${id?.tab}"
|
|
144
|
+
tabindex="0"
|
|
145
|
+
?hidden="${() => active() !== item.value}"
|
|
146
|
+
class="flex-1 outline-none"
|
|
147
|
+
>${slot(item.content)}</div>`;
|
|
148
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
|
|
18
|
+
import { component, html, signal } from "@c9up/aurora";
|
|
19
|
+
import { type ToggleVariants, toggleVariants } from "../atoms/Toggle.js";
|
|
20
|
+
import type { Child } from "../lib/children.js";
|
|
21
|
+
import { cn } from "../lib/cn.js";
|
|
22
|
+
import { type Reactive, read, readOr } from "../lib/props.js";
|
|
23
|
+
|
|
24
|
+
export interface ToggleGroupItem {
|
|
25
|
+
value: string;
|
|
26
|
+
label: Child;
|
|
27
|
+
/** Required when the label is an icon alone. */
|
|
28
|
+
ariaLabel?: string;
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ToggleGroupProps {
|
|
33
|
+
items: readonly ToggleGroupItem[];
|
|
34
|
+
type?: "single" | "multiple";
|
|
35
|
+
defaultValue?: string | readonly string[];
|
|
36
|
+
variant?: Reactive<ToggleVariants["variant"]>;
|
|
37
|
+
size?: Reactive<ToggleVariants["size"]>;
|
|
38
|
+
disabled?: Reactive<boolean>;
|
|
39
|
+
class?: Reactive<string>;
|
|
40
|
+
onValueChange?: (value: readonly string[]) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const ToggleGroup = component<ToggleGroupProps>((props) => {
|
|
44
|
+
const type = props.type ?? "single";
|
|
45
|
+
const selected = signal<readonly string[]>(
|
|
46
|
+
props.defaultValue === undefined
|
|
47
|
+
? []
|
|
48
|
+
: typeof props.defaultValue === "string"
|
|
49
|
+
? [props.defaultValue]
|
|
50
|
+
: props.defaultValue,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
function toggle(value: string): void {
|
|
54
|
+
const current = selected();
|
|
55
|
+
const next =
|
|
56
|
+
type === "single"
|
|
57
|
+
? current.includes(value)
|
|
58
|
+
? []
|
|
59
|
+
: [value]
|
|
60
|
+
: current.includes(value)
|
|
61
|
+
? current.filter((entry) => entry !== value)
|
|
62
|
+
: [...current, value];
|
|
63
|
+
selected(next);
|
|
64
|
+
props.onValueChange?.(next);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return html`<div
|
|
68
|
+
data-slot="toggle-group"
|
|
69
|
+
role="${type === "single" ? "radiogroup" : "group"}"
|
|
70
|
+
class="${() =>
|
|
71
|
+
cn(
|
|
72
|
+
"group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
|
|
73
|
+
read(props.class),
|
|
74
|
+
)}"
|
|
75
|
+
data-variant="${() => readOr(props.variant, "default")}"
|
|
76
|
+
>
|
|
77
|
+
${props.items.map((item) => renderItem(item, props, type, selected, toggle))}
|
|
78
|
+
</div>`;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
function renderItem(
|
|
82
|
+
item: ToggleGroupItem,
|
|
83
|
+
props: ToggleGroupProps,
|
|
84
|
+
type: "single" | "multiple",
|
|
85
|
+
selected: () => readonly string[],
|
|
86
|
+
toggle: (value: string) => void,
|
|
87
|
+
): Child {
|
|
88
|
+
const on = (): boolean => selected().includes(item.value);
|
|
89
|
+
|
|
90
|
+
return html`<button
|
|
91
|
+
type="button"
|
|
92
|
+
data-slot="toggle-group-item"
|
|
93
|
+
data-state="${() => (on() ? "on" : "off")}"
|
|
94
|
+
data-value="${item.value}"
|
|
95
|
+
role="${type === "single" ? "radio" : undefined}"
|
|
96
|
+
aria-checked="${() => (type === "single" ? (on() ? "true" : "false") : undefined)}"
|
|
97
|
+
aria-pressed="${() => (type === "multiple" ? (on() ? "true" : "false") : undefined)}"
|
|
98
|
+
aria-label="${item.ariaLabel}"
|
|
99
|
+
?disabled="${() => item.disabled === true || readOr(props.disabled, false)}"
|
|
100
|
+
class="${() =>
|
|
101
|
+
toggleVariants({
|
|
102
|
+
variant: read(props.variant),
|
|
103
|
+
size: read(props.size),
|
|
104
|
+
class:
|
|
105
|
+
"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",
|
|
106
|
+
})}"
|
|
107
|
+
@click="${() => toggle(item.value)}"
|
|
108
|
+
>${item.label}</button>`;
|
|
109
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
|
|
14
|
+
import { component, html } from "@c9up/aurora";
|
|
15
|
+
import { slot } from "../lib/children.js";
|
|
16
|
+
import { cn } from "../lib/cn.js";
|
|
17
|
+
import { read } from "../lib/props.js";
|
|
18
|
+
import { type StyledProps, styledDiv } from "../lib/styled.js";
|
|
19
|
+
|
|
20
|
+
export type TypographyProps = StyledProps;
|
|
21
|
+
|
|
22
|
+
export const H1 = component<TypographyProps>((props) => {
|
|
23
|
+
return html`<h1
|
|
24
|
+
id="${props.id}"
|
|
25
|
+
class="${() =>
|
|
26
|
+
cn(
|
|
27
|
+
"scroll-m-20 text-4xl font-extrabold tracking-tight text-balance",
|
|
28
|
+
read(props.class),
|
|
29
|
+
)}"
|
|
30
|
+
>${slot(props.children)}</h1>`;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const H2 = component<TypographyProps>((props) => {
|
|
34
|
+
return html`<h2
|
|
35
|
+
id="${props.id}"
|
|
36
|
+
class="${() =>
|
|
37
|
+
cn(
|
|
38
|
+
"scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0",
|
|
39
|
+
read(props.class),
|
|
40
|
+
)}"
|
|
41
|
+
>${slot(props.children)}</h2>`;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const H3 = component<TypographyProps>((props) => {
|
|
45
|
+
return html`<h3
|
|
46
|
+
id="${props.id}"
|
|
47
|
+
class="${() => cn("scroll-m-20 text-2xl font-semibold tracking-tight", read(props.class))}"
|
|
48
|
+
>${slot(props.children)}</h3>`;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const H4 = component<TypographyProps>((props) => {
|
|
52
|
+
return html`<h4
|
|
53
|
+
id="${props.id}"
|
|
54
|
+
class="${() => cn("scroll-m-20 text-xl font-semibold tracking-tight", read(props.class))}"
|
|
55
|
+
>${slot(props.children)}</h4>`;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const P = component<TypographyProps>((props) => {
|
|
59
|
+
return html`<p
|
|
60
|
+
id="${props.id}"
|
|
61
|
+
class="${() => cn("leading-7 text-pretty [&:not(:first-child)]:mt-6", read(props.class))}"
|
|
62
|
+
>${slot(props.children)}</p>`;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export const Blockquote = component<TypographyProps>((props) => {
|
|
66
|
+
return html`<blockquote
|
|
67
|
+
id="${props.id}"
|
|
68
|
+
class="${() => cn("mt-6 border-l-2 pl-6 italic", read(props.class))}"
|
|
69
|
+
>${slot(props.children)}</blockquote>`;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export const InlineCode = component<TypographyProps>((props) => {
|
|
73
|
+
return html`<code
|
|
74
|
+
class="${() =>
|
|
75
|
+
cn(
|
|
76
|
+
"bg-muted relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold",
|
|
77
|
+
read(props.class),
|
|
78
|
+
)}"
|
|
79
|
+
>${slot(props.children)}</code>`;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export const List = component<TypographyProps>((props) => {
|
|
83
|
+
return html`<ul
|
|
84
|
+
class="${() => cn("my-6 ml-6 list-disc [&>li]:mt-2", read(props.class))}"
|
|
85
|
+
>${slot(props.children)}</ul>`;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export const Lead = styledDiv(
|
|
89
|
+
"lead",
|
|
90
|
+
"text-muted-foreground text-xl text-pretty",
|
|
91
|
+
);
|
|
92
|
+
export const Large = styledDiv("large", "text-lg font-semibold");
|
|
93
|
+
export const Small = styledDiv("small", "text-sm leading-none font-medium");
|
|
94
|
+
export const Muted = styledDiv("muted", "text-muted-foreground text-sm");
|