@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,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkbox.
|
|
3
|
+
*
|
|
4
|
+
* Radix renders a `<button role="checkbox">` plus a hidden input, because it
|
|
5
|
+
* needs a element it can put arbitrary children in. nebula keeps the native
|
|
6
|
+
* `<input type="checkbox">` and styles it with `appearance-none`.
|
|
7
|
+
*
|
|
8
|
+
* That is not just simpler, it is better behaved: the control participates in
|
|
9
|
+
* form submission and reset on its own, `:checked` and `:indeterminate` drive
|
|
10
|
+
* the visuals with no state to synchronise, and the label association through
|
|
11
|
+
* `for` works without a click forwarder. The tick and dash are siblings shown
|
|
12
|
+
* by `peer-checked:` / `peer-indeterminate:`, so the whole component is
|
|
13
|
+
* declarative — no JavaScript runs when the box is toggled.
|
|
14
|
+
*
|
|
15
|
+
* `indeterminate` is bound as a property because there is no attribute for it;
|
|
16
|
+
* it exists only in the DOM.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { component, html } from "@c9up/aurora";
|
|
20
|
+
import { cn } from "../lib/cn.js";
|
|
21
|
+
import { CheckIcon, MinusIcon } from "../lib/icons.js";
|
|
22
|
+
import { accessor, type Reactive, read } from "../lib/props.js";
|
|
23
|
+
|
|
24
|
+
export const checkboxClasses =
|
|
25
|
+
"peer size-4 shrink-0 appearance-none rounded-[4px] border border-input bg-transparent shadow-xs outline-none transition-shadow dark:bg-input/30 checked:border-primary checked:bg-primary indeterminate:border-primary indeterminate:bg-primary focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20";
|
|
26
|
+
|
|
27
|
+
export interface CheckboxProps {
|
|
28
|
+
id?: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
value?: string;
|
|
31
|
+
checked?: Reactive<boolean>;
|
|
32
|
+
/** The mixed state — a parent box over partially selected children. */
|
|
33
|
+
indeterminate?: Reactive<boolean>;
|
|
34
|
+
disabled?: Reactive<boolean>;
|
|
35
|
+
required?: Reactive<boolean>;
|
|
36
|
+
invalid?: Reactive<boolean>;
|
|
37
|
+
/**
|
|
38
|
+
* Accessible name, for a checkbox with no visible `<label>`.
|
|
39
|
+
*
|
|
40
|
+
* A checkbox in a table row or a toolbar has its meaning from what is beside
|
|
41
|
+
* it, which a screen reader does not see — it announces "checkbox, unchecked"
|
|
42
|
+
* and nothing else. Every such use needs this.
|
|
43
|
+
*/
|
|
44
|
+
label?: Reactive<string | undefined>;
|
|
45
|
+
class?: Reactive<string>;
|
|
46
|
+
onCheckedChange?: (checked: boolean, event: Event) => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function inputChecked(event: Event): boolean {
|
|
50
|
+
const target = event.target;
|
|
51
|
+
return target instanceof HTMLInputElement ? target.checked : false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const Checkbox = component<CheckboxProps>((props) => {
|
|
55
|
+
const marker =
|
|
56
|
+
"pointer-events-none absolute inset-0 size-4 p-px text-primary-foreground opacity-0 transition-opacity";
|
|
57
|
+
|
|
58
|
+
return html`<span data-slot="checkbox" class="relative inline-flex size-4 shrink-0">
|
|
59
|
+
<input
|
|
60
|
+
type="checkbox"
|
|
61
|
+
id="${props.id}"
|
|
62
|
+
name="${props.name}"
|
|
63
|
+
value="${props.value}"
|
|
64
|
+
aria-invalid="${() => (read(props.invalid) === true ? "true" : undefined)}"
|
|
65
|
+
aria-label="${accessor(props.label, undefined)}"
|
|
66
|
+
?disabled="${accessor(props.disabled, false)}"
|
|
67
|
+
?required="${accessor(props.required, false)}"
|
|
68
|
+
.checked="${accessor(props.checked, false)}"
|
|
69
|
+
.indeterminate="${accessor(props.indeterminate, false)}"
|
|
70
|
+
class="${() => cn(checkboxClasses, read(props.class))}"
|
|
71
|
+
@change="${(event: Event) => props.onCheckedChange?.(inputChecked(event), event)}"
|
|
72
|
+
/>
|
|
73
|
+
${CheckIcon({ class: cn(marker, "peer-checked:opacity-100 peer-indeterminate:opacity-0") })}
|
|
74
|
+
${MinusIcon({ class: cn(marker, "peer-indeterminate:opacity-100") })}
|
|
75
|
+
</span>`;
|
|
76
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input — a single-line text field.
|
|
3
|
+
*
|
|
4
|
+
* The value binding is the part that differs from React and is worth calling
|
|
5
|
+
* out. `.value` is a *property* slot, not an attribute: setting the `value`
|
|
6
|
+
* attribute only seeds the initial value, and after the user types once the
|
|
7
|
+
* attribute and the property diverge. Binding the property is what keeps a
|
|
8
|
+
* signal and the field in step in both directions.
|
|
9
|
+
*
|
|
10
|
+
* There is no controlled/uncontrolled split. Pass a signal and the field is
|
|
11
|
+
* controlled; pass nothing and the DOM owns the value, which is the right
|
|
12
|
+
* default for a form posted the classic way — the shape aurora apps use.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { component, html } from "@c9up/aurora";
|
|
16
|
+
import { cn } from "../lib/cn.js";
|
|
17
|
+
import { accessor, type Reactive, read } from "../lib/props.js";
|
|
18
|
+
|
|
19
|
+
export const inputClasses =
|
|
20
|
+
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive";
|
|
21
|
+
|
|
22
|
+
export interface InputProps {
|
|
23
|
+
id?: string;
|
|
24
|
+
name?: string;
|
|
25
|
+
type?: Reactive<string>;
|
|
26
|
+
value?: Reactive<string>;
|
|
27
|
+
placeholder?: Reactive<string>;
|
|
28
|
+
autocomplete?: string;
|
|
29
|
+
disabled?: Reactive<boolean>;
|
|
30
|
+
readonly?: Reactive<boolean>;
|
|
31
|
+
required?: Reactive<boolean>;
|
|
32
|
+
/** Renders `aria-invalid`, which the focus ring and border both key off. */
|
|
33
|
+
invalid?: Reactive<boolean>;
|
|
34
|
+
/** Names the element describing the error, for screen readers. */
|
|
35
|
+
describedBy?: Reactive<string | undefined>;
|
|
36
|
+
class?: Reactive<string>;
|
|
37
|
+
onInput?: (value: string, event: Event) => void;
|
|
38
|
+
onChange?: (value: string, event: Event) => void;
|
|
39
|
+
onBlur?: (event: FocusEvent) => void;
|
|
40
|
+
onKeyDown?: (event: KeyboardEvent) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Pull the current text out of an input event.
|
|
45
|
+
*
|
|
46
|
+
* Reads the target rather than trusting the caller to, so a handler receives a
|
|
47
|
+
* plain string. The guard is not defensive padding: `event.target` is typed as
|
|
48
|
+
* the general `EventTarget`, and narrowing it here is what keeps the callback
|
|
49
|
+
* signature honest without a cast at every call site.
|
|
50
|
+
*/
|
|
51
|
+
function inputValue(event: Event): string {
|
|
52
|
+
const target = event.target;
|
|
53
|
+
return target instanceof HTMLInputElement ? target.value : "";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const Input = component<InputProps>((props) => {
|
|
57
|
+
return html`<input
|
|
58
|
+
data-slot="input"
|
|
59
|
+
id="${props.id}"
|
|
60
|
+
name="${props.name}"
|
|
61
|
+
type="${accessor(props.type, "text")}"
|
|
62
|
+
placeholder="${accessor(props.placeholder, undefined)}"
|
|
63
|
+
autocomplete="${props.autocomplete}"
|
|
64
|
+
aria-invalid="${() => (read(props.invalid) === true ? "true" : undefined)}"
|
|
65
|
+
aria-describedby="${accessor(props.describedBy, undefined)}"
|
|
66
|
+
?disabled="${accessor(props.disabled, false)}"
|
|
67
|
+
?readonly="${accessor(props.readonly, false)}"
|
|
68
|
+
?required="${accessor(props.required, false)}"
|
|
69
|
+
.value="${accessor(props.value, "")}"
|
|
70
|
+
class="${() => cn(inputClasses, read(props.class))}"
|
|
71
|
+
@input="${(event: Event) => props.onInput?.(inputValue(event), event)}"
|
|
72
|
+
@change="${(event: Event) => props.onChange?.(inputValue(event), event)}"
|
|
73
|
+
@blur="${props.onBlur}"
|
|
74
|
+
@keydown="${props.onKeyDown}"
|
|
75
|
+
/>`;
|
|
76
|
+
});
|
package/src/atoms/Kbd.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kbd — a keyboard key rendered inline.
|
|
3
|
+
*
|
|
4
|
+
* Used by Command and by menu items showing their shortcut. Kept as an atom
|
|
5
|
+
* rather than baked into those two, because shortcut hints show up in tooltips
|
|
6
|
+
* and documentation prose as well.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { component, html } from "@c9up/aurora";
|
|
10
|
+
import { type Slot, slot } from "../lib/children.js";
|
|
11
|
+
import { cn } from "../lib/cn.js";
|
|
12
|
+
import { type Reactive, read } from "../lib/props.js";
|
|
13
|
+
|
|
14
|
+
export interface KbdProps {
|
|
15
|
+
children?: Slot;
|
|
16
|
+
class?: Reactive<string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const Kbd = component<KbdProps>((props) => {
|
|
20
|
+
return html`<kbd
|
|
21
|
+
data-slot="kbd"
|
|
22
|
+
class="${() =>
|
|
23
|
+
cn(
|
|
24
|
+
"bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 select-none items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium",
|
|
25
|
+
read(props.class),
|
|
26
|
+
)}"
|
|
27
|
+
>${slot(props.children)}</kbd>`;
|
|
28
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Label — the caption for a form control.
|
|
3
|
+
*
|
|
4
|
+
* Radix's Label exists mostly to stop a click on the label from selecting its
|
|
5
|
+
* text on double-click, and to forward clicks to the control. The browser
|
|
6
|
+
* already does the forwarding through `for`, so nebula keeps the native
|
|
7
|
+
* element and only adds `select-none`.
|
|
8
|
+
*
|
|
9
|
+
* The disabled styling is driven from outside: `peer-disabled:` picks up a
|
|
10
|
+
* sibling control marked disabled, `group-data-[disabled=true]:` picks up a
|
|
11
|
+
* wrapping `Field`. Both are why the label dims without being told anything
|
|
12
|
+
* about the control it captions.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { component, html } from "@c9up/aurora";
|
|
16
|
+
import { type Slot, slot } from "../lib/children.js";
|
|
17
|
+
import { cn } from "../lib/cn.js";
|
|
18
|
+
import { type Reactive, read } from "../lib/props.js";
|
|
19
|
+
|
|
20
|
+
export const labelClasses =
|
|
21
|
+
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50";
|
|
22
|
+
|
|
23
|
+
export interface LabelProps {
|
|
24
|
+
children?: Slot;
|
|
25
|
+
/** The `id` of the control this labels. */
|
|
26
|
+
for?: string;
|
|
27
|
+
id?: string;
|
|
28
|
+
class?: Reactive<string>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const Label = component<LabelProps>((props) => {
|
|
32
|
+
return html`<label
|
|
33
|
+
data-slot="label"
|
|
34
|
+
id="${props.id}"
|
|
35
|
+
for="${props.for}"
|
|
36
|
+
class="${() => cn(labelClasses, read(props.class))}"
|
|
37
|
+
>${slot(props.children)}</label>`;
|
|
38
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marker — an inline annotation in a stream of content.
|
|
3
|
+
*
|
|
4
|
+
* "Yesterday", "3 unread", "Claude joined the conversation", "context
|
|
5
|
+
* truncated". A line that is *about* the conversation rather than part of it.
|
|
6
|
+
*
|
|
7
|
+
* `role="separator"` when it carries no text, `role="status"` when it does.
|
|
8
|
+
* The distinction matters: a bare rule between two days is structure and
|
|
9
|
+
* should be announced as a boundary, while "context truncated" is information
|
|
10
|
+
* the reader needs and would be lost as a separator.
|
|
11
|
+
*
|
|
12
|
+
* The rules on either side are `<span>`s with a border, not a `<hr>` — a
|
|
13
|
+
* horizontal rule cannot sit either side of a label without absolute
|
|
14
|
+
* positioning and a background colour matched to whatever is behind it, which
|
|
15
|
+
* breaks the moment the surface changes.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { component, html } from "@c9up/aurora";
|
|
19
|
+
import type { Child } from "../lib/children.js";
|
|
20
|
+
import { cva, type VariantProps } from "../lib/cva.js";
|
|
21
|
+
import { type Reactive, read } from "../lib/props.js";
|
|
22
|
+
|
|
23
|
+
export const markerVariants = cva(
|
|
24
|
+
"flex w-full items-center gap-2 text-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:shrink-0",
|
|
25
|
+
{
|
|
26
|
+
variants: {
|
|
27
|
+
variant: {
|
|
28
|
+
default: "text-muted-foreground",
|
|
29
|
+
muted: "text-muted-foreground/70",
|
|
30
|
+
destructive: "text-destructive",
|
|
31
|
+
},
|
|
32
|
+
align: {
|
|
33
|
+
center: "justify-center",
|
|
34
|
+
start: "justify-start",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
defaultVariants: { variant: "default", align: "center" },
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export type MarkerVariants = VariantProps<typeof markerVariants>;
|
|
42
|
+
|
|
43
|
+
export interface MarkerProps {
|
|
44
|
+
/** The annotation. Omit for a plain rule. */
|
|
45
|
+
label?: Child;
|
|
46
|
+
icon?: Child;
|
|
47
|
+
variant?: Reactive<MarkerVariants["variant"]>;
|
|
48
|
+
align?: Reactive<MarkerVariants["align"]>;
|
|
49
|
+
/** Draw rules either side of the label. Default `true`. */
|
|
50
|
+
rules?: boolean;
|
|
51
|
+
class?: Reactive<string>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const Marker = component<MarkerProps>((props) => {
|
|
55
|
+
const hasLabel = props.label !== undefined;
|
|
56
|
+
const withRules = props.rules !== false;
|
|
57
|
+
const rule = html`<span aria-hidden="true" class="bg-border h-px flex-1"></span>`;
|
|
58
|
+
|
|
59
|
+
return html`<div
|
|
60
|
+
data-slot="marker"
|
|
61
|
+
role="${hasLabel ? "status" : "separator"}"
|
|
62
|
+
class="${() =>
|
|
63
|
+
markerVariants({
|
|
64
|
+
variant: read(props.variant),
|
|
65
|
+
align: read(props.align),
|
|
66
|
+
class: read(props.class),
|
|
67
|
+
})}"
|
|
68
|
+
>
|
|
69
|
+
${withRules ? rule : null}
|
|
70
|
+
${
|
|
71
|
+
hasLabel
|
|
72
|
+
? html`<span class="flex shrink-0 items-center gap-1.5 whitespace-nowrap"
|
|
73
|
+
>${props.icon}${props.label}</span
|
|
74
|
+
>`
|
|
75
|
+
: null
|
|
76
|
+
}
|
|
77
|
+
${withRules ? rule : null}
|
|
78
|
+
</div>`;
|
|
79
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NativeSelect — a real `<select>`, styled.
|
|
3
|
+
*
|
|
4
|
+
* The counterpart to `Select`, and the one to reach for first. `Select` builds
|
|
5
|
+
* a listbox out of divs because the operating system draws a native popup that
|
|
6
|
+
* cannot be styled at all — options take no markup, no colours, no checkmarks.
|
|
7
|
+
* That freedom costs everything the native control does for free, and `Select`
|
|
8
|
+
* has to put it all back by hand: roles, `aria-activedescendant`, type-ahead,
|
|
9
|
+
* arrows, a hidden input.
|
|
10
|
+
*
|
|
11
|
+
* When the options are plain text, none of that is worth paying for. A native
|
|
12
|
+
* select gives you the platform's own picker — the wheel on iOS, the
|
|
13
|
+
* search-as-you-type list on desktop — form participation, and zero JavaScript.
|
|
14
|
+
* `appearance-none` plus a background chevron gets it to match the design
|
|
15
|
+
* system everywhere except the open popup itself.
|
|
16
|
+
*
|
|
17
|
+
* Use `Select` when an option needs an icon, a description, or a swatch. Use
|
|
18
|
+
* this one otherwise.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { component, html } from "@c9up/aurora";
|
|
22
|
+
import { cn } from "../lib/cn.js";
|
|
23
|
+
import { accessor, type Reactive, read } from "../lib/props.js";
|
|
24
|
+
|
|
25
|
+
export interface NativeSelectOption {
|
|
26
|
+
value: string;
|
|
27
|
+
label: string;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
/** Groups consecutive options under an `<optgroup>`. */
|
|
30
|
+
group?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const nativeSelectClasses =
|
|
34
|
+
"border-input dark:bg-input/30 flex h-9 w-full min-w-0 appearance-none rounded-md border bg-transparent py-1 pr-8 pl-3 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm aria-invalid:border-destructive aria-invalid:ring-destructive/20";
|
|
35
|
+
|
|
36
|
+
export interface NativeSelectProps {
|
|
37
|
+
options: readonly NativeSelectOption[];
|
|
38
|
+
id?: string;
|
|
39
|
+
name?: string;
|
|
40
|
+
value?: Reactive<string | undefined>;
|
|
41
|
+
/** Shown as a disabled first entry while nothing is chosen. */
|
|
42
|
+
placeholder?: string;
|
|
43
|
+
disabled?: Reactive<boolean>;
|
|
44
|
+
required?: Reactive<boolean>;
|
|
45
|
+
invalid?: Reactive<boolean>;
|
|
46
|
+
describedBy?: Reactive<string | undefined>;
|
|
47
|
+
class?: Reactive<string>;
|
|
48
|
+
onValueChange?: (value: string, event: Event) => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function selectedValue(event: Event): string {
|
|
52
|
+
const target = event.target;
|
|
53
|
+
return target instanceof HTMLSelectElement ? target.value : "";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const NativeSelect = component<NativeSelectProps>((props) => {
|
|
57
|
+
/**
|
|
58
|
+
* Options grouped in source order.
|
|
59
|
+
*
|
|
60
|
+
* Consecutive entries sharing a `group` become one `<optgroup>`; a run that
|
|
61
|
+
* starts again later becomes a second one. Sorting by group instead would
|
|
62
|
+
* quietly reorder a list the caller arranged deliberately.
|
|
63
|
+
*/
|
|
64
|
+
function runs(): Array<{ group?: string; items: NativeSelectOption[] }> {
|
|
65
|
+
const out: Array<{ group?: string; items: NativeSelectOption[] }> = [];
|
|
66
|
+
for (const option of props.options) {
|
|
67
|
+
const last = out[out.length - 1];
|
|
68
|
+
if (last !== undefined && last.group === option.group)
|
|
69
|
+
last.items.push(option);
|
|
70
|
+
else out.push({ group: option.group, items: [option] });
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const renderOption = (option: NativeSelectOption) =>
|
|
76
|
+
html`<option value="${option.value}" ?disabled="${option.disabled === true}">
|
|
77
|
+
${option.label}
|
|
78
|
+
</option>`;
|
|
79
|
+
|
|
80
|
+
return html`<div data-slot="native-select" class="relative inline-flex w-full items-center">
|
|
81
|
+
<select
|
|
82
|
+
id="${props.id}"
|
|
83
|
+
name="${props.name}"
|
|
84
|
+
aria-invalid="${() => (read(props.invalid) === true ? "true" : undefined)}"
|
|
85
|
+
aria-describedby="${accessor(props.describedBy, undefined)}"
|
|
86
|
+
?disabled="${accessor(props.disabled, false)}"
|
|
87
|
+
?required="${accessor(props.required, false)}"
|
|
88
|
+
.value="${accessor(props.value, "")}"
|
|
89
|
+
class="${() => cn(nativeSelectClasses, read(props.class))}"
|
|
90
|
+
@change="${(event: Event) => props.onValueChange?.(selectedValue(event), event)}"
|
|
91
|
+
>
|
|
92
|
+
${
|
|
93
|
+
props.placeholder === undefined
|
|
94
|
+
? null
|
|
95
|
+
: html`<option value="" disabled selected>${props.placeholder}</option>`
|
|
96
|
+
}
|
|
97
|
+
${runs().map((run) =>
|
|
98
|
+
run.group === undefined
|
|
99
|
+
? run.items.map(renderOption)
|
|
100
|
+
: html`<optgroup label="${run.group}">${run.items.map(renderOption)}</optgroup>`,
|
|
101
|
+
)}
|
|
102
|
+
</select>
|
|
103
|
+
<svg
|
|
104
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
105
|
+
viewBox="0 0 24 24"
|
|
106
|
+
fill="none"
|
|
107
|
+
stroke="currentColor"
|
|
108
|
+
stroke-width="2"
|
|
109
|
+
stroke-linecap="round"
|
|
110
|
+
stroke-linejoin="round"
|
|
111
|
+
aria-hidden="true"
|
|
112
|
+
class="pointer-events-none absolute end-3 size-4 opacity-50"
|
|
113
|
+
><path d="m6 9 6 6 6-6" /></svg>
|
|
114
|
+
</div>`;
|
|
115
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress — a determinate progress bar.
|
|
3
|
+
*
|
|
4
|
+
* The fill is a full-width child translated left by the remaining percentage,
|
|
5
|
+
* rather than a width animation. Transforms are composited off the main
|
|
6
|
+
* thread; animating `width` relayouts the bar on every frame, which is visible
|
|
7
|
+
* on a long upload.
|
|
8
|
+
*
|
|
9
|
+
* A `value` of `null` means indeterminate, and drops `aria-valuenow` — the
|
|
10
|
+
* ARIA spec is explicit that an absent value is how "unknown progress" is
|
|
11
|
+
* expressed, and reporting `0` instead tells the user it has not started.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { component, html } from "@c9up/aurora";
|
|
15
|
+
import { cn } from "../lib/cn.js";
|
|
16
|
+
import { type Reactive, read, readOr } from "../lib/props.js";
|
|
17
|
+
|
|
18
|
+
export interface ProgressProps {
|
|
19
|
+
/** `0`–`max`, or `null` for indeterminate. */
|
|
20
|
+
value?: Reactive<number | null>;
|
|
21
|
+
max?: Reactive<number>;
|
|
22
|
+
label?: Reactive<string | undefined>;
|
|
23
|
+
class?: Reactive<string>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const Progress = component<ProgressProps>((props) => {
|
|
27
|
+
const max = (): number => {
|
|
28
|
+
const given = readOr(props.max, 100);
|
|
29
|
+
// A zero or negative maximum would make the percentage infinite or
|
|
30
|
+
// negative and push the fill off the track in one direction or the other.
|
|
31
|
+
return given > 0 ? given : 100;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const value = (): number | null => {
|
|
35
|
+
const given = readOr(props.value, 0);
|
|
36
|
+
if (given === null) return null;
|
|
37
|
+
return Math.min(Math.max(given, 0), max());
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const percent = (): number => {
|
|
41
|
+
const current = value();
|
|
42
|
+
return current === null ? 0 : (current / max()) * 100;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return html`<div
|
|
46
|
+
data-slot="progress"
|
|
47
|
+
role="progressbar"
|
|
48
|
+
aria-valuemin="0"
|
|
49
|
+
aria-valuemax="${max}"
|
|
50
|
+
aria-valuenow="${() => value() ?? undefined}"
|
|
51
|
+
aria-label="${() => read(props.label)}"
|
|
52
|
+
class="${() =>
|
|
53
|
+
cn(
|
|
54
|
+
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
|
|
55
|
+
read(props.class),
|
|
56
|
+
)}"
|
|
57
|
+
>
|
|
58
|
+
<div
|
|
59
|
+
data-slot="progress-indicator"
|
|
60
|
+
class="bg-primary h-full w-full flex-1 transition-transform"
|
|
61
|
+
style="${() => `transform: translateX(-${100 - percent()}%)`}"
|
|
62
|
+
></div>
|
|
63
|
+
</div>`;
|
|
64
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScrollArea — a scroll container with a restrained scrollbar.
|
|
3
|
+
*
|
|
4
|
+
* A deliberate departure from Radix, which hides the native scrollbar and
|
|
5
|
+
* draws its own out of divs, driven by scroll and resize listeners. That buys
|
|
6
|
+
* pixel-identical scrollbars across platforms; it costs a virtualised
|
|
7
|
+
* scrollbar that has to re-derive thumb size and position on every frame, and
|
|
8
|
+
* that drops momentum scrolling on touch.
|
|
9
|
+
*
|
|
10
|
+
* The trade is not worth it here. `scrollbar-width` and `scrollbar-color` are
|
|
11
|
+
* supported everywhere nebula targets, the WebKit pseudo-elements cover the
|
|
12
|
+
* rest, and the result keeps native scrolling — including overscroll, momentum
|
|
13
|
+
* and the platform's own accessibility behaviour — for a handful of classes
|
|
14
|
+
* and no JavaScript.
|
|
15
|
+
*
|
|
16
|
+
* Apps needing a fully custom scrollbar should reach for a dedicated library;
|
|
17
|
+
* this component would be the wrong place to grow one.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { component, html } from "@c9up/aurora";
|
|
21
|
+
import { type Slot, slot } from "../lib/children.js";
|
|
22
|
+
import { cn } from "../lib/cn.js";
|
|
23
|
+
import { type Reactive, read, readOr } from "../lib/props.js";
|
|
24
|
+
|
|
25
|
+
export const scrollAreaClasses = cn(
|
|
26
|
+
"relative overflow-auto [scrollbar-width:thin] [scrollbar-color:var(--border)_transparent]",
|
|
27
|
+
"[&::-webkit-scrollbar]:w-2.5 [&::-webkit-scrollbar]:h-2.5",
|
|
28
|
+
"[&::-webkit-scrollbar-track]:bg-transparent",
|
|
29
|
+
"[&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:border-2 [&::-webkit-scrollbar-thumb]:border-solid [&::-webkit-scrollbar-thumb]:border-transparent [&::-webkit-scrollbar-thumb]:bg-border [&::-webkit-scrollbar-thumb]:bg-clip-content",
|
|
30
|
+
"hover:[&::-webkit-scrollbar-thumb]:bg-muted-foreground/40",
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export interface ScrollAreaProps {
|
|
34
|
+
children?: Slot;
|
|
35
|
+
/** Which axis may scroll. Default `"vertical"`. */
|
|
36
|
+
orientation?: Reactive<"vertical" | "horizontal" | "both">;
|
|
37
|
+
class?: Reactive<string>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const ScrollArea = component<ScrollAreaProps>((props) => {
|
|
41
|
+
const axisClass = (): string => {
|
|
42
|
+
const orientation = readOr(props.orientation, "vertical");
|
|
43
|
+
if (orientation === "horizontal")
|
|
44
|
+
return "overflow-x-auto overflow-y-hidden";
|
|
45
|
+
if (orientation === "both") return "overflow-auto";
|
|
46
|
+
return "overflow-y-auto overflow-x-hidden";
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return html`<div
|
|
50
|
+
data-slot="scroll-area"
|
|
51
|
+
class="${() => cn(scrollAreaClasses, axisClass(), read(props.class))}"
|
|
52
|
+
>${slot(props.children)}</div>`;
|
|
53
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Separator — a rule between sections.
|
|
3
|
+
*
|
|
4
|
+
* `role="separator"` with an orientation, or `role="none"` when decorative.
|
|
5
|
+
* The distinction matters to screen readers: a separator is announced as a
|
|
6
|
+
* structural boundary, and a purely visual line between two buttons in a
|
|
7
|
+
* toolbar should not be. Decorative is the default, matching Radix.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { component, html } from "@c9up/aurora";
|
|
11
|
+
import { cn } from "../lib/cn.js";
|
|
12
|
+
import { type Reactive, read, readOr } from "../lib/props.js";
|
|
13
|
+
|
|
14
|
+
export interface SeparatorProps {
|
|
15
|
+
orientation?: Reactive<"horizontal" | "vertical">;
|
|
16
|
+
/** `false` exposes it to assistive technology as a real boundary. */
|
|
17
|
+
decorative?: Reactive<boolean>;
|
|
18
|
+
class?: Reactive<string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const Separator = component<SeparatorProps>((props) => {
|
|
22
|
+
const orientation = (): "horizontal" | "vertical" =>
|
|
23
|
+
readOr(props.orientation, "horizontal");
|
|
24
|
+
|
|
25
|
+
return html`<div
|
|
26
|
+
data-slot="separator"
|
|
27
|
+
data-orientation="${orientation}"
|
|
28
|
+
role="${() => (readOr(props.decorative, true) ? "none" : "separator")}"
|
|
29
|
+
aria-orientation="${() => (readOr(props.decorative, true) ? undefined : orientation())}"
|
|
30
|
+
class="${() =>
|
|
31
|
+
cn(
|
|
32
|
+
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
|
33
|
+
read(props.class),
|
|
34
|
+
)}"
|
|
35
|
+
></div>`;
|
|
36
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skeleton — a placeholder block while content loads.
|
|
3
|
+
*
|
|
4
|
+
* `aria-hidden` and no live region on purpose. A pulsing grey box carries no
|
|
5
|
+
* information, and announcing one per row of a loading table is worse than
|
|
6
|
+
* silence. The surrounding region should carry `aria-busy` instead, which says
|
|
7
|
+
* the same thing once.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { component, html } from "@c9up/aurora";
|
|
11
|
+
import { cn } from "../lib/cn.js";
|
|
12
|
+
import { type Reactive, read } from "../lib/props.js";
|
|
13
|
+
|
|
14
|
+
export interface SkeletonProps {
|
|
15
|
+
class?: Reactive<string>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const Skeleton = component<SkeletonProps>((props) => {
|
|
19
|
+
return html`<div
|
|
20
|
+
data-slot="skeleton"
|
|
21
|
+
aria-hidden="true"
|
|
22
|
+
class="${() => cn("bg-accent animate-pulse rounded-md", read(props.class))}"
|
|
23
|
+
></div>`;
|
|
24
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slider — pick a number from a range.
|
|
3
|
+
*
|
|
4
|
+
* A native `<input type="range">`, restyled. Radix builds its own from a div
|
|
5
|
+
* and pointer maths because it supports multiple thumbs; nebula does not, and
|
|
6
|
+
* for the single-thumb case the native control already brings keyboard
|
|
7
|
+
* support (arrows, Page Up/Down, Home/End), the correct ARIA role and value
|
|
8
|
+
* announcements, and touch behaviour that no reimplementation gets right for
|
|
9
|
+
* free.
|
|
10
|
+
*
|
|
11
|
+
* The cost is the thumb, which can only be reached through the two vendor
|
|
12
|
+
* pseudo-elements. Both are written out; they cannot be combined into one
|
|
13
|
+
* selector, since a browser drops an entire rule containing a pseudo-element
|
|
14
|
+
* it does not recognise.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { component, html } from "@c9up/aurora";
|
|
18
|
+
import { cn } from "../lib/cn.js";
|
|
19
|
+
import { accessor, type Reactive, read } from "../lib/props.js";
|
|
20
|
+
|
|
21
|
+
const THUMB =
|
|
22
|
+
"size-4 appearance-none rounded-full border border-primary bg-background shadow-sm transition-[color,box-shadow]";
|
|
23
|
+
|
|
24
|
+
export const sliderClasses = cn(
|
|
25
|
+
"h-1.5 w-full cursor-pointer appearance-none rounded-full bg-primary/20 outline-none disabled:pointer-events-none disabled:opacity-50",
|
|
26
|
+
`[&::-webkit-slider-thumb]:${THUMB.split(" ").join(" [&::-webkit-slider-thumb]:")}`,
|
|
27
|
+
`[&::-moz-range-thumb]:${THUMB.split(" ").join(" [&::-moz-range-thumb]:")}`,
|
|
28
|
+
"focus-visible:[&::-webkit-slider-thumb]:ring-ring/50 focus-visible:[&::-webkit-slider-thumb]:ring-[3px]",
|
|
29
|
+
"focus-visible:[&::-moz-range-thumb]:ring-ring/50 focus-visible:[&::-moz-range-thumb]:ring-[3px]",
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export interface SliderProps {
|
|
33
|
+
id?: string;
|
|
34
|
+
name?: string;
|
|
35
|
+
value?: Reactive<number>;
|
|
36
|
+
min?: Reactive<number>;
|
|
37
|
+
max?: Reactive<number>;
|
|
38
|
+
step?: Reactive<number>;
|
|
39
|
+
disabled?: Reactive<boolean>;
|
|
40
|
+
label?: Reactive<string | undefined>;
|
|
41
|
+
class?: Reactive<string>;
|
|
42
|
+
onValueChange?: (value: number, event: Event) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function inputValue(event: Event): number {
|
|
46
|
+
const target = event.target;
|
|
47
|
+
return target instanceof HTMLInputElement ? Number(target.value) : 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const Slider = component<SliderProps>((props) => {
|
|
51
|
+
return html`<input
|
|
52
|
+
type="range"
|
|
53
|
+
data-slot="slider"
|
|
54
|
+
id="${props.id}"
|
|
55
|
+
name="${props.name}"
|
|
56
|
+
min="${accessor(props.min, 0)}"
|
|
57
|
+
max="${accessor(props.max, 100)}"
|
|
58
|
+
step="${accessor(props.step, 1)}"
|
|
59
|
+
aria-label="${accessor(props.label, undefined)}"
|
|
60
|
+
?disabled="${accessor(props.disabled, false)}"
|
|
61
|
+
.value="${accessor(props.value, 0)}"
|
|
62
|
+
class="${() => cn(sliderClasses, read(props.class))}"
|
|
63
|
+
@input="${(event: Event) => props.onValueChange?.(inputValue(event), event)}"
|
|
64
|
+
/>`;
|
|
65
|
+
});
|