@nibapp/ui 0.0.0-stage → 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/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.md +124 -2
- package/TRADEMARKS.md +22 -0
- package/dist/_bundled/money/index.d.ts +98 -0
- package/dist/_bundled/money/index.js +152 -0
- package/dist/_bundled/tokens/index.d.ts +2 -0
- package/dist/_bundled/tokens/index.js +2 -0
- package/dist/_bundled/tokens/palette.gen.d.ts +51 -0
- package/dist/_bundled/tokens/palette.gen.js +206 -0
- package/dist/_bundled/tokens/spec.d.ts +291 -0
- package/dist/_bundled/tokens/spec.js +221 -0
- package/dist/components/accordion.d.ts +27 -0
- package/dist/components/accordion.js +66 -0
- package/dist/components/alert-dialog.d.ts +59 -0
- package/dist/components/alert-dialog.js +82 -0
- package/dist/components/alert.d.ts +141 -0
- package/dist/components/alert.js +55 -0
- package/dist/components/appear.d.ts +21 -0
- package/dist/components/appear.js +42 -0
- package/dist/components/avatar.d.ts +196 -0
- package/dist/components/avatar.js +135 -0
- package/dist/components/badge.d.ts +183 -0
- package/dist/components/badge.js +68 -0
- package/dist/components/block-stack.d.ts +33 -0
- package/dist/components/block-stack.js +197 -0
- package/dist/components/button.d.ts +327 -0
- package/dist/components/button.js +101 -0
- package/dist/components/card.d.ts +131 -0
- package/dist/components/card.js +100 -0
- package/dist/components/checkbox.d.ts +34 -0
- package/dist/components/checkbox.js +37 -0
- package/dist/components/choice-row.d.ts +104 -0
- package/dist/components/choice-row.js +56 -0
- package/dist/components/clamped-text.d.ts +17 -0
- package/dist/components/clamped-text.js +47 -0
- package/dist/components/code-block.d.ts +48 -0
- package/dist/components/code-block.js +124 -0
- package/dist/components/code-input.d.ts +49 -0
- package/dist/components/code-input.js +59 -0
- package/dist/components/combobox.d.ts +194 -0
- package/dist/components/combobox.js +176 -0
- package/dist/components/command-palette.d.ts +57 -0
- package/dist/components/command-palette.js +83 -0
- package/dist/components/context-menu.d.ts +24 -0
- package/dist/components/context-menu.js +30 -0
- package/dist/components/copy-button.d.ts +9 -0
- package/dist/components/copy-button.js +20 -0
- package/dist/components/copy-command.d.ts +18 -0
- package/dist/components/copy-command.js +19 -0
- package/dist/components/data-table.d.ts +240 -0
- package/dist/components/data-table.js +63 -0
- package/dist/components/dialog.d.ts +80 -0
- package/dist/components/dialog.js +85 -0
- package/dist/components/diff-lines.d.ts +20 -0
- package/dist/components/diff-lines.js +10 -0
- package/dist/components/diff-view.d.ts +128 -0
- package/dist/components/diff-view.js +675 -0
- package/dist/components/disclosure.d.ts +33 -0
- package/dist/components/disclosure.js +51 -0
- package/dist/components/drawn-check.d.ts +8 -0
- package/dist/components/drawn-check.js +16 -0
- package/dist/components/edge-resizer.d.ts +26 -0
- package/dist/components/edge-resizer.js +53 -0
- package/dist/components/empty-state.d.ts +112 -0
- package/dist/components/empty-state.js +56 -0
- package/dist/components/expand-toggle.d.ts +19 -0
- package/dist/components/expand-toggle.js +10 -0
- package/dist/components/field.d.ts +48 -0
- package/dist/components/field.js +60 -0
- package/dist/components/filter-chip.d.ts +47 -0
- package/dist/components/filter-chip.js +33 -0
- package/dist/components/group.d.ts +10 -0
- package/dist/components/group.js +9 -0
- package/dist/components/icon-text.d.ts +11 -0
- package/dist/components/icon-text.js +10 -0
- package/dist/components/image-sources.d.ts +57 -0
- package/dist/components/image-sources.js +58 -0
- package/dist/components/image-thumbnail.d.ts +34 -0
- package/dist/components/image-thumbnail.js +58 -0
- package/dist/components/image-viewer.d.ts +60 -0
- package/dist/components/image-viewer.js +411 -0
- package/dist/components/inline-edit.d.ts +22 -0
- package/dist/components/inline-edit.js +51 -0
- package/dist/components/input.d.ts +155 -0
- package/dist/components/input.js +109 -0
- package/dist/components/item-label.d.ts +11 -0
- package/dist/components/item-label.js +5 -0
- package/dist/components/item-row.d.ts +23 -0
- package/dist/components/item-row.js +19 -0
- package/dist/components/kbd.d.ts +55 -0
- package/dist/components/kbd.js +86 -0
- package/dist/components/link-policy.d.ts +24 -0
- package/dist/components/link-policy.js +32 -0
- package/dist/components/linked-text.d.ts +15 -0
- package/dist/components/linked-text.js +48 -0
- package/dist/components/loading-region.d.ts +18 -0
- package/dist/components/loading-region.js +17 -0
- package/dist/components/markdown.d.ts +34 -0
- package/dist/components/markdown.js +307 -0
- package/dist/components/menu.d.ts +132 -0
- package/dist/components/menu.js +102 -0
- package/dist/components/message-bubble.d.ts +57 -0
- package/dist/components/message-bubble.js +28 -0
- package/dist/components/meta-line.d.ts +28 -0
- package/dist/components/meta-line.js +36 -0
- package/dist/components/money-input.d.ts +23 -0
- package/dist/components/money-input.js +34 -0
- package/dist/components/native-select.d.ts +120 -0
- package/dist/components/native-select.js +85 -0
- package/dist/components/page-trail.d.ts +30 -0
- package/dist/components/page-trail.js +43 -0
- package/dist/components/page.d.ts +80 -0
- package/dist/components/page.js +60 -0
- package/dist/components/path-text.d.ts +8 -0
- package/dist/components/path-text.js +11 -0
- package/dist/components/percent-input.d.ts +20 -0
- package/dist/components/percent-input.js +32 -0
- package/dist/components/popover.d.ts +52 -0
- package/dist/components/popover.js +36 -0
- package/dist/components/progress.d.ts +389 -0
- package/dist/components/progress.js +142 -0
- package/dist/components/radio.d.ts +65 -0
- package/dist/components/radio.js +54 -0
- package/dist/components/remote-image-gate.d.ts +34 -0
- package/dist/components/remote-image-gate.js +60 -0
- package/dist/components/resizable.d.ts +23 -0
- package/dist/components/resizable.js +30 -0
- package/dist/components/rolling-number.d.ts +13 -0
- package/dist/components/rolling-number.js +69 -0
- package/dist/components/scroll-area.d.ts +17 -0
- package/dist/components/scroll-area.js +25 -0
- package/dist/components/section-label.d.ts +10 -0
- package/dist/components/section-label.js +21 -0
- package/dist/components/section-nav.d.ts +22 -0
- package/dist/components/section-nav.js +80 -0
- package/dist/components/segmented-control.d.ts +125 -0
- package/dist/components/segmented-control.js +101 -0
- package/dist/components/select.d.ts +120 -0
- package/dist/components/select.js +89 -0
- package/dist/components/separator.d.ts +10 -0
- package/dist/components/separator.js +12 -0
- package/dist/components/sheet.d.ts +101 -0
- package/dist/components/sheet.js +129 -0
- package/dist/components/shortcut-recorder.d.ts +31 -0
- package/dist/components/shortcut-recorder.js +67 -0
- package/dist/components/skeleton.d.ts +31 -0
- package/dist/components/skeleton.js +33 -0
- package/dist/components/slider.d.ts +138 -0
- package/dist/components/slider.js +98 -0
- package/dist/components/spinner.d.ts +37 -0
- package/dist/components/spinner.js +16 -0
- package/dist/components/switch.d.ts +59 -0
- package/dist/components/switch.js +56 -0
- package/dist/components/tab-strip.d.ts +29 -0
- package/dist/components/tab-strip.js +42 -0
- package/dist/components/tabs.d.ts +13 -0
- package/dist/components/tabs.js +68 -0
- package/dist/components/text-link.d.ts +54 -0
- package/dist/components/text-link.js +38 -0
- package/dist/components/textarea.d.ts +75 -0
- package/dist/components/textarea.js +90 -0
- package/dist/components/toast.d.ts +74 -0
- package/dist/components/toast.js +128 -0
- package/dist/components/tooltip.d.ts +50 -0
- package/dist/components/tooltip.js +46 -0
- package/dist/components/truncated-text.d.ts +15 -0
- package/dist/components/truncated-text.js +19 -0
- package/dist/i18n/defaults.d.ts +7 -0
- package/dist/i18n/defaults.js +7 -0
- package/dist/i18n/format.d.ts +84 -0
- package/dist/i18n/format.js +169 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.js +8 -0
- package/dist/i18n/locales/ru-formatters.d.ts +3 -0
- package/dist/i18n/locales/ru-formatters.js +22 -0
- package/dist/i18n/locales/ru.d.ts +5 -0
- package/dist/i18n/locales/ru.js +185 -0
- package/dist/i18n/plural.d.ts +20 -0
- package/dist/i18n/plural.js +26 -0
- package/dist/i18n/provider.d.ts +36 -0
- package/dist/i18n/provider.js +39 -0
- package/dist/i18n/rich.d.ts +14 -0
- package/dist/i18n/rich.js +44 -0
- package/dist/i18n/ru-plural.d.ts +3 -0
- package/dist/i18n/ru-plural.js +4 -0
- package/dist/i18n/strings/alert-dialog.d.ts +5 -0
- package/dist/i18n/strings/alert-dialog.js +5 -0
- package/dist/i18n/strings/block-stack.d.ts +17 -0
- package/dist/i18n/strings/block-stack.js +11 -0
- package/dist/i18n/strings/code-block.d.ts +18 -0
- package/dist/i18n/strings/code-block.js +13 -0
- package/dist/i18n/strings/combobox.d.ts +12 -0
- package/dist/i18n/strings/combobox.js +10 -0
- package/dist/i18n/strings/command-palette.d.ts +17 -0
- package/dist/i18n/strings/command-palette.js +11 -0
- package/dist/i18n/strings/diff-view.d.ts +92 -0
- package/dist/i18n/strings/diff-view.js +68 -0
- package/dist/i18n/strings/image-viewer.d.ts +43 -0
- package/dist/i18n/strings/image-viewer.js +32 -0
- package/dist/i18n/strings/index.d.ts +314 -0
- package/dist/i18n/strings/index.js +43 -0
- package/dist/i18n/strings/kbd.d.ts +42 -0
- package/dist/i18n/strings/kbd.js +39 -0
- package/dist/i18n/strings/markdown.d.ts +23 -0
- package/dist/i18n/strings/markdown.js +18 -0
- package/dist/i18n/strings/money.d.ts +7 -0
- package/dist/i18n/strings/money.js +5 -0
- package/dist/i18n/strings/primitives.d.ts +25 -0
- package/dist/i18n/strings/primitives.js +18 -0
- package/dist/i18n/strings/progress.d.ts +4 -0
- package/dist/i18n/strings/progress.js +4 -0
- package/dist/i18n/strings/shortcut-recorder.d.ts +15 -0
- package/dist/i18n/strings/shortcut-recorder.js +11 -0
- package/dist/i18n/strings/time.d.ts +41 -0
- package/dist/i18n/strings/time.js +20 -0
- package/dist/i18n/strings/toast.d.ts +6 -0
- package/dist/i18n/strings/toast.js +6 -0
- package/dist/i18n/time-zones.d.ts +20 -0
- package/dist/i18n/time-zones.js +64 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +88 -0
- package/dist/lib/ansi.d.ts +55 -0
- package/dist/lib/ansi.js +438 -0
- package/dist/lib/clock.d.ts +32 -0
- package/dist/lib/clock.js +106 -0
- package/dist/lib/cn.d.ts +12 -0
- package/dist/lib/cn.js +28 -0
- package/dist/lib/control-props.d.ts +21 -0
- package/dist/lib/control-props.js +14 -0
- package/dist/lib/direction.d.ts +13 -0
- package/dist/lib/direction.js +25 -0
- package/dist/lib/image-size.d.ts +12 -0
- package/dist/lib/image-size.js +66 -0
- package/dist/lib/keycaps.d.ts +21 -0
- package/dist/lib/keycaps.js +62 -0
- package/dist/lib/keyed.d.ts +9 -0
- package/dist/lib/keyed.js +14 -0
- package/dist/lib/keys.d.ts +49 -0
- package/dist/lib/keys.js +159 -0
- package/dist/lib/kit-motion.d.ts +9 -0
- package/dist/lib/kit-motion.js +12 -0
- package/dist/lib/link-check-dialog.d.ts +11 -0
- package/dist/lib/link-check-dialog.js +18 -0
- package/dist/lib/motion-features.d.ts +1 -0
- package/dist/lib/motion-features.js +4 -0
- package/dist/lib/motion-provider.d.ts +5 -0
- package/dist/lib/motion-provider.js +8 -0
- package/dist/lib/motion.d.ts +39 -0
- package/dist/lib/motion.js +49 -0
- package/dist/lib/number-format.d.ts +114 -0
- package/dist/lib/number-format.js +313 -0
- package/dist/lib/patch.d.ts +72 -0
- package/dist/lib/patch.js +344 -0
- package/dist/lib/path.d.ts +34 -0
- package/dist/lib/path.js +85 -0
- package/dist/lib/search.d.ts +10 -0
- package/dist/lib/search.js +20 -0
- package/dist/lib/shiki-themes.d.ts +7 -0
- package/dist/lib/shiki-themes.js +115 -0
- package/dist/lib/shiki.d.ts +101 -0
- package/dist/lib/shiki.js +353 -0
- package/dist/lib/shortcut-context.d.ts +23 -0
- package/dist/lib/shortcut-context.js +29 -0
- package/dist/lib/text.d.ts +56 -0
- package/dist/lib/text.js +149 -0
- package/dist/lib/theme-scope.d.ts +18 -0
- package/dist/lib/theme-scope.js +24 -0
- package/dist/lib/theme.d.ts +40 -0
- package/dist/lib/theme.js +96 -0
- package/dist/lib/tree-keys.d.ts +72 -0
- package/dist/lib/tree-keys.js +126 -0
- package/dist/lib/url.d.ts +17 -0
- package/dist/lib/url.js +38 -0
- package/dist/lib/use-copy-to-clipboard.d.ts +13 -0
- package/dist/lib/use-copy-to-clipboard.js +40 -0
- package/dist/lib/use-delayed-loading.d.ts +11 -0
- package/dist/lib/use-delayed-loading.js +31 -0
- package/dist/lib/use-file-picker.d.ts +26 -0
- package/dist/lib/use-file-picker.js +43 -0
- package/dist/lib/use-hydrated.d.ts +6 -0
- package/dist/lib/use-hydrated.js +11 -0
- package/dist/lib/use-latest.d.ts +12 -0
- package/dist/lib/use-latest.js +22 -0
- package/dist/lib/use-resolved-theme.d.ts +12 -0
- package/dist/lib/use-resolved-theme.js +25 -0
- package/dist/styles/base.css +393 -0
- package/dist/styles/code.css +35 -0
- package/dist/styles/fonts.css +12 -0
- package/dist/styles/index.css +13 -0
- package/dist/styles/tokens.gen.css +494 -0
- package/package.json +208 -4
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Toggle as BaseToggle } from "@base-ui/react/toggle";
|
|
4
|
+
import { ToggleGroup as BaseToggleGroup } from "@base-ui/react/toggle-group";
|
|
5
|
+
import { createContext, use, useCallback, useMemo, useState, } from "react";
|
|
6
|
+
import { cn, resolveClassName, tv } from "../lib/cn.js";
|
|
7
|
+
import { DisabledReasonTooltip, disabledReasonClass } from "./tooltip.js";
|
|
8
|
+
const segmentedControlStyles = tv({
|
|
9
|
+
slots: {
|
|
10
|
+
// Segments keep their labels' width: a control that doesn't fit overflows its container (put it
|
|
11
|
+
// in a scroller or let the row wrap) instead of squeezing labels into each other. Block
|
|
12
|
+
// controls share the width and truncate their labels instead.
|
|
13
|
+
root: [
|
|
14
|
+
"relative inline-flex shrink-0 items-center gap-0.5 bg-hover p-0.5",
|
|
15
|
+
"[box-shadow:inset_0_0_0_1px_var(--border-subtle)]",
|
|
16
|
+
"data-disabled:pointer-events-none data-disabled:opacity-45",
|
|
17
|
+
],
|
|
18
|
+
item: [
|
|
19
|
+
"group/segment relative inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap font-medium touch-target",
|
|
20
|
+
"text-fg-muted outline-none transition-colors duration-(--duration-fast) ease-(--ease-standard)",
|
|
21
|
+
"hover:text-fg data-pressed:text-fg",
|
|
22
|
+
"focus-visible:ring-focus focus-visible:outline-offset-0",
|
|
23
|
+
"data-disabled:pointer-events-none data-disabled:opacity-45",
|
|
24
|
+
"[&_svg]:shrink-0",
|
|
25
|
+
],
|
|
26
|
+
pill: [
|
|
27
|
+
"absolute inset-0 bg-surface-raised dark:bg-gray-5",
|
|
28
|
+
"[box-shadow:var(--shadow-sm),0_0_0_0.5px_var(--border)]",
|
|
29
|
+
],
|
|
30
|
+
// A label with a count ("All 4") keeps its 6 px gap: the label's parts sit in one flex row.
|
|
31
|
+
text: "inline-flex min-w-0 items-center gap-1.5",
|
|
32
|
+
label: [
|
|
33
|
+
"relative z-10 inline-flex min-w-0 items-center justify-center gap-1.5",
|
|
34
|
+
"transition-[scale] duration-(--duration-spring-snappy) ease-(--ease-spring-snappy)",
|
|
35
|
+
"group-active/segment:scale-[0.96] motion-reduce:group-active/segment:scale-100",
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
variants: {
|
|
39
|
+
size: {
|
|
40
|
+
sm: {
|
|
41
|
+
root: "h-7 rounded-md",
|
|
42
|
+
item: "h-6 rounded-sm px-2 text-xs [&_svg:not([class*='size-'])]:size-3.5",
|
|
43
|
+
pill: "rounded-sm",
|
|
44
|
+
},
|
|
45
|
+
md: {
|
|
46
|
+
root: "h-8 rounded-lg",
|
|
47
|
+
item: "h-7 rounded-md px-2.5 text-sm [&_svg:not([class*='size-'])]:size-4",
|
|
48
|
+
pill: "rounded-md",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
/** Stretch to the container; segments share the width equally. */
|
|
52
|
+
block: {
|
|
53
|
+
true: { root: "flex w-full min-w-0 shrink", item: "min-w-0 flex-1 shrink", text: "block truncate" },
|
|
54
|
+
},
|
|
55
|
+
/** Icon-only segments: square buttons. */
|
|
56
|
+
iconOnly: { true: {} },
|
|
57
|
+
},
|
|
58
|
+
compoundVariants: [
|
|
59
|
+
{ iconOnly: true, size: "sm", class: { item: "w-6 px-0" } },
|
|
60
|
+
{ iconOnly: true, size: "md", class: { item: "w-7 px-0" } },
|
|
61
|
+
],
|
|
62
|
+
defaultVariants: { size: "md" },
|
|
63
|
+
});
|
|
64
|
+
const SegmentedControlContext = createContext(null);
|
|
65
|
+
/**
|
|
66
|
+
* Single-select segmented control. The selected segment takes the pill at once: nothing slides.
|
|
67
|
+
* Arrow keys move focus; Space/Enter select.
|
|
68
|
+
*/
|
|
69
|
+
export function SegmentedControl({ value: valueProp, defaultValue, onValueChange, size, block, className, children, ...props }) {
|
|
70
|
+
const [uncontrolled, setUncontrolled] = useState(defaultValue ?? "");
|
|
71
|
+
const value = valueProp ?? uncontrolled;
|
|
72
|
+
const styles = segmentedControlStyles({ size, block });
|
|
73
|
+
const handleChange = useCallback((next) => {
|
|
74
|
+
const selected = next.find((candidate) => candidate !== value);
|
|
75
|
+
// Pressing the active segment would empty the group — a segmented control stays selected.
|
|
76
|
+
if (selected === undefined)
|
|
77
|
+
return;
|
|
78
|
+
if (valueProp === undefined)
|
|
79
|
+
setUncontrolled(selected);
|
|
80
|
+
onValueChange?.(selected);
|
|
81
|
+
}, [value, valueProp, onValueChange]);
|
|
82
|
+
const context = useMemo(() => ({ value, size, block }), [value, size, block]);
|
|
83
|
+
return (_jsx(SegmentedControlContext, { value: context, children: _jsx(BaseToggleGroup, { "data-slot": "segmented-control", value: value === "" ? [] : [value], onValueChange: handleChange, className: (state) => styles.root({ className: resolveClassName(className, state) }), ...props, children: children }) }));
|
|
84
|
+
}
|
|
85
|
+
export function SegmentedControlItem({ value, icon, disabledReason, className, children, ...props }) {
|
|
86
|
+
const context = use(SegmentedControlContext);
|
|
87
|
+
if (!context)
|
|
88
|
+
throw new Error("<SegmentedControlItem> must be used inside <SegmentedControl>.");
|
|
89
|
+
const selected = context.value === value;
|
|
90
|
+
const iconOnly = icon !== undefined && (children === undefined || children === null);
|
|
91
|
+
const styles = segmentedControlStyles({ size: context.size, block: context.block, iconOnly });
|
|
92
|
+
// Base UI's toggle can't be disabled and focusable: a reasoned segment is `aria-disabled` and turns
|
|
93
|
+
// its press down instead.
|
|
94
|
+
const reasoned = disabledReason !== undefined;
|
|
95
|
+
const item = (_jsxs(BaseToggle, { "data-slot": "segmented-control-item", value: value, ...(reasoned && {
|
|
96
|
+
"aria-disabled": true,
|
|
97
|
+
"data-disabled": "",
|
|
98
|
+
onPressedChange: (_pressed, details) => details.cancel(),
|
|
99
|
+
}), className: (state) => styles.item({ className: cn(reasoned && disabledReasonClass, resolveClassName(className, state)) }), ...props, children: [selected && _jsx("span", { "aria-hidden": true, className: styles.pill() }), _jsxs("span", { className: styles.label(), children: [icon !== undefined && (_jsx("span", { "aria-hidden": true, className: "flex items-center", children: icon })), children !== undefined && children !== null && _jsx("span", { className: styles.text(), children: children })] })] }));
|
|
100
|
+
return reasoned ? _jsx(DisabledReasonTooltip, { reason: disabledReason, children: item }) : item;
|
|
101
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Select as BaseSelect } from "@base-ui/react/select";
|
|
2
|
+
import { type ComponentProps, type ReactNode } from "react";
|
|
3
|
+
import { type VariantProps } from "../lib/cn.js";
|
|
4
|
+
/** The floating listbox surface. */
|
|
5
|
+
export declare const listboxPopupClass: string;
|
|
6
|
+
export declare const listboxItemClass: string;
|
|
7
|
+
export declare const listboxIndicatorClass: string;
|
|
8
|
+
export declare const listboxGroupLabelClass = "flex h-7 select-none items-center px-2 text-xs font-medium text-fg-subtle";
|
|
9
|
+
export declare const listboxSeparatorClass = "-mx-1 my-1 h-px bg-border-subtle";
|
|
10
|
+
export declare const listboxIconClass = "flex size-4 shrink-0 items-center justify-center text-fg-subtle transition-colors duration-(--duration-instant) group-data-highlighted/listbox-item:text-fg-muted";
|
|
11
|
+
export declare const selectTriggerStyles: import("tailwind-variants").TVReturnType<{
|
|
12
|
+
variant: {
|
|
13
|
+
/** Framed, matches Input. */
|
|
14
|
+
default: string[];
|
|
15
|
+
/** Chromeless until hovered: toolbars, inline pickers ("Last 7 days ⌄"). */
|
|
16
|
+
ghost: string[];
|
|
17
|
+
};
|
|
18
|
+
size: {
|
|
19
|
+
sm: "h-7 gap-1.5 rounded-md pe-1.5 ps-2 text-sm [&_svg:not([class*='size-'])]:size-3.5";
|
|
20
|
+
md: "h-8 gap-2 rounded-lg pe-2 ps-2.5 text-base [&_svg:not([class*='size-'])]:size-4";
|
|
21
|
+
lg: "h-10 gap-2.5 rounded-xl pe-2.5 ps-3 text-md [&_svg:not([class*='size-'])]:size-4";
|
|
22
|
+
};
|
|
23
|
+
block: {
|
|
24
|
+
true: "w-full";
|
|
25
|
+
};
|
|
26
|
+
}, undefined, string[], {
|
|
27
|
+
variant: {
|
|
28
|
+
/** Framed, matches Input. */
|
|
29
|
+
default: string[];
|
|
30
|
+
/** Chromeless until hovered: toolbars, inline pickers ("Last 7 days ⌄"). */
|
|
31
|
+
ghost: string[];
|
|
32
|
+
};
|
|
33
|
+
size: {
|
|
34
|
+
sm: "h-7 gap-1.5 rounded-md pe-1.5 ps-2 text-sm [&_svg:not([class*='size-'])]:size-3.5";
|
|
35
|
+
md: "h-8 gap-2 rounded-lg pe-2 ps-2.5 text-base [&_svg:not([class*='size-'])]:size-4";
|
|
36
|
+
lg: "h-10 gap-2.5 rounded-xl pe-2.5 ps-3 text-md [&_svg:not([class*='size-'])]:size-4";
|
|
37
|
+
};
|
|
38
|
+
block: {
|
|
39
|
+
true: "w-full";
|
|
40
|
+
};
|
|
41
|
+
}, undefined, import("tailwind-variants").TVReturnTypeLike<{
|
|
42
|
+
variant: {
|
|
43
|
+
/** Framed, matches Input. */
|
|
44
|
+
default: string[];
|
|
45
|
+
/** Chromeless until hovered: toolbars, inline pickers ("Last 7 days ⌄"). */
|
|
46
|
+
ghost: string[];
|
|
47
|
+
};
|
|
48
|
+
size: {
|
|
49
|
+
sm: "h-7 gap-1.5 rounded-md pe-1.5 ps-2 text-sm [&_svg:not([class*='size-'])]:size-3.5";
|
|
50
|
+
md: "h-8 gap-2 rounded-lg pe-2 ps-2.5 text-base [&_svg:not([class*='size-'])]:size-4";
|
|
51
|
+
lg: "h-10 gap-2.5 rounded-xl pe-2.5 ps-3 text-md [&_svg:not([class*='size-'])]:size-4";
|
|
52
|
+
};
|
|
53
|
+
block: {
|
|
54
|
+
true: "w-full";
|
|
55
|
+
};
|
|
56
|
+
}, undefined>>;
|
|
57
|
+
export type SelectTriggerVariants = VariantProps<typeof selectTriggerStyles>;
|
|
58
|
+
export declare const SelectRoot: typeof BaseSelect.Root;
|
|
59
|
+
export interface SelectTriggerProps extends ComponentProps<typeof BaseSelect.Trigger>, SelectTriggerVariants {
|
|
60
|
+
/** Icon before the value (e.g. a provider logo). */
|
|
61
|
+
leading?: ReactNode;
|
|
62
|
+
}
|
|
63
|
+
export declare function SelectTrigger({ variant, size, block, leading, className, children, ...props }: SelectTriggerProps): import("react").JSX.Element;
|
|
64
|
+
export declare function SelectValue({ className, ...props }: ComponentProps<typeof BaseSelect.Value>): import("react").JSX.Element;
|
|
65
|
+
export interface SelectContentProps extends ComponentProps<typeof BaseSelect.Popup> {
|
|
66
|
+
side?: ComponentProps<typeof BaseSelect.Positioner>["side"];
|
|
67
|
+
align?: ComponentProps<typeof BaseSelect.Positioner>["align"];
|
|
68
|
+
sideOffset?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Overlap the trigger so the selected item lines up with the value (macOS-style). Off by
|
|
71
|
+
* default: the kit's popups drop below their trigger.
|
|
72
|
+
*/
|
|
73
|
+
alignItemWithTrigger?: boolean;
|
|
74
|
+
}
|
|
75
|
+
/** Portal + positioner + popup + scroll arrows + list in one. Children are items/groups. */
|
|
76
|
+
export declare function SelectContent({ side, align, sideOffset, alignItemWithTrigger, className, children, ...props }: SelectContentProps): import("react").JSX.Element;
|
|
77
|
+
export interface SelectItemProps extends Omit<ComponentProps<typeof BaseSelect.Item>, "children"> {
|
|
78
|
+
/** Leading icon (provider logo, lucide icon). */
|
|
79
|
+
icon?: ReactNode;
|
|
80
|
+
/** Secondary line under the label ("Most capable · 1M context"). */
|
|
81
|
+
description?: ReactNode;
|
|
82
|
+
/** Trailing adornment before the check (badge, shortcut…). */
|
|
83
|
+
trailing?: ReactNode;
|
|
84
|
+
children?: ReactNode;
|
|
85
|
+
}
|
|
86
|
+
export declare function SelectItem({ icon, description, trailing, className, children, ...props }: SelectItemProps): import("react").JSX.Element;
|
|
87
|
+
export declare function SelectGroup({ className, ...props }: ComponentProps<typeof BaseSelect.Group>): import("react").JSX.Element;
|
|
88
|
+
export declare function SelectGroupLabel({ className, ...props }: ComponentProps<typeof BaseSelect.GroupLabel>): import("react").JSX.Element;
|
|
89
|
+
export declare function SelectSeparator({ className, ...props }: ComponentProps<typeof BaseSelect.Separator>): import("react").JSX.Element;
|
|
90
|
+
export interface SelectOption<Value> {
|
|
91
|
+
value: Value;
|
|
92
|
+
/** Plain text — also used by typeahead and the trigger. */
|
|
93
|
+
label: string;
|
|
94
|
+
description?: ReactNode;
|
|
95
|
+
icon?: ReactNode;
|
|
96
|
+
disabled?: boolean;
|
|
97
|
+
}
|
|
98
|
+
export interface SelectOptionGroup<Value> {
|
|
99
|
+
label: string;
|
|
100
|
+
options: readonly SelectOption<Value>[];
|
|
101
|
+
}
|
|
102
|
+
type SelectEntry<Value> = SelectOption<Value> | SelectOptionGroup<Value>;
|
|
103
|
+
export interface SelectProps<Value> extends Omit<BaseSelect.Root.Props<Value>, "items" | "children" | "multiple">, SelectTriggerVariants {
|
|
104
|
+
/** Flat options, or groups of options (separated automatically). */
|
|
105
|
+
items: readonly SelectEntry<Value>[];
|
|
106
|
+
placeholder?: ReactNode;
|
|
107
|
+
/** Icon before the value in the trigger. */
|
|
108
|
+
leading?: ReactNode;
|
|
109
|
+
/** Required when there's no visible <Field> label. */
|
|
110
|
+
"aria-label"?: string;
|
|
111
|
+
/** Classes for the trigger. */
|
|
112
|
+
className?: string | undefined;
|
|
113
|
+
contentClassName?: string | undefined;
|
|
114
|
+
side?: SelectContentProps["side"];
|
|
115
|
+
align?: SelectContentProps["align"];
|
|
116
|
+
alignItemWithTrigger?: boolean;
|
|
117
|
+
}
|
|
118
|
+
/** Single-value select from a list of options: `<Select items={models} defaultValue="opus" />`. */
|
|
119
|
+
export declare function Select<Value>({ items, placeholder, leading, variant, size, block, "aria-label": ariaLabel, className, contentClassName, side, align, alignItemWithTrigger, ...props }: SelectProps<Value>): import("react").JSX.Element;
|
|
120
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Select as BaseSelect } from "@base-ui/react/select";
|
|
4
|
+
import { Check, ChevronDown, ChevronsUpDown, ChevronUp } from "lucide-react";
|
|
5
|
+
import { Fragment } from "react";
|
|
6
|
+
import { cn, tv, withBase } from "../lib/cn.js";
|
|
7
|
+
import { usePortalContainer } from "../lib/theme-scope.js";
|
|
8
|
+
import { fieldChrome } from "./input.js";
|
|
9
|
+
import { ItemLabel } from "./item-label.js";
|
|
10
|
+
/* ------------------------------------------------------------------------------------------ */
|
|
11
|
+
/* Shared listbox styles — reused by Combobox. */
|
|
12
|
+
/* ------------------------------------------------------------------------------------------ */
|
|
13
|
+
/** The floating listbox surface. */
|
|
14
|
+
export const listboxPopupClass = cn("popup-motion surface-raised min-w-(--anchor-width) max-w-(--available-width) rounded-xl p-1 text-fg outline-none");
|
|
15
|
+
export const listboxItemClass = cn("group/listbox-item relative flex min-h-8 w-full cursor-default select-none items-center gap-2 rounded-md px-2", "text-base text-fg outline-none", "transition-[background-color,color] duration-(--duration-instant) ease-(--ease-standard)", "data-highlighted:bg-hover", "data-disabled:pointer-events-none data-disabled:opacity-45", "[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4");
|
|
16
|
+
export const listboxIndicatorClass = cn("ms-auto flex size-4 shrink-0 items-center justify-center ps-0 text-fg", "transition-[opacity,scale] duration-(--duration-spring-snappy) ease-(--ease-spring-snappy)", "data-starting-style:scale-50 data-starting-style:opacity-0 data-ending-style:scale-50 data-ending-style:opacity-0", "motion-reduce:transition-none");
|
|
17
|
+
export const listboxGroupLabelClass = "flex h-7 select-none items-center px-2 text-xs font-medium text-fg-subtle";
|
|
18
|
+
export const listboxSeparatorClass = "-mx-1 my-1 h-px bg-border-subtle";
|
|
19
|
+
export const listboxIconClass = "flex size-4 shrink-0 items-center justify-center text-fg-subtle transition-colors duration-(--duration-instant) group-data-highlighted/listbox-item:text-fg-muted";
|
|
20
|
+
/* ------------------------------------------------------------------------------------------ */
|
|
21
|
+
/* Trigger */
|
|
22
|
+
/* ------------------------------------------------------------------------------------------ */
|
|
23
|
+
export const selectTriggerStyles = tv({
|
|
24
|
+
base: [
|
|
25
|
+
"group/select-trigger relative inline-flex min-w-0 shrink-0 select-none items-center justify-between text-start outline-none touch-target",
|
|
26
|
+
"data-disabled:pointer-events-none data-disabled:opacity-45",
|
|
27
|
+
"data-readonly:pointer-events-none",
|
|
28
|
+
"[&_svg]:shrink-0",
|
|
29
|
+
],
|
|
30
|
+
variants: {
|
|
31
|
+
variant: {
|
|
32
|
+
/** Framed, matches Input. */
|
|
33
|
+
default: [
|
|
34
|
+
fieldChrome,
|
|
35
|
+
"focus-visible:border-border-strong focus-visible:ring-3 data-popup-open:border-border-strong",
|
|
36
|
+
"data-readonly:bg-bg-subtle data-readonly:shadow-none",
|
|
37
|
+
],
|
|
38
|
+
/** Chromeless until hovered: toolbars, inline pickers ("Last 7 days ⌄"). */
|
|
39
|
+
ghost: [
|
|
40
|
+
"border border-transparent text-fg-muted transition-[background-color,color,box-shadow] duration-(--duration-fast)",
|
|
41
|
+
"hover:bg-hover hover:text-fg data-popup-open:bg-hover data-popup-open:text-fg",
|
|
42
|
+
"focus-visible:ring-focus",
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
size: {
|
|
46
|
+
sm: "h-7 gap-1.5 rounded-md pe-1.5 ps-2 text-sm [&_svg:not([class*='size-'])]:size-3.5",
|
|
47
|
+
md: "h-8 gap-2 rounded-lg pe-2 ps-2.5 text-base [&_svg:not([class*='size-'])]:size-4",
|
|
48
|
+
lg: "h-10 gap-2.5 rounded-xl pe-2.5 ps-3 text-md [&_svg:not([class*='size-'])]:size-4",
|
|
49
|
+
},
|
|
50
|
+
block: { true: "w-full" },
|
|
51
|
+
},
|
|
52
|
+
defaultVariants: { variant: "default", size: "md" },
|
|
53
|
+
});
|
|
54
|
+
export const SelectRoot = BaseSelect.Root;
|
|
55
|
+
export function SelectTrigger({ variant, size, block, leading, className, children, ...props }) {
|
|
56
|
+
return (_jsxs(BaseSelect.Trigger, { "data-slot": "select-trigger", className: withBase(selectTriggerStyles({ variant, size, block }), className), ...props, children: [leading !== undefined && (_jsx("span", { "aria-hidden": true, className: "flex shrink-0 items-center text-fg-subtle", children: leading })), _jsx("span", { className: "flex min-w-0 flex-1 items-center", children: children }), _jsx(BaseSelect.Icon, { className: cn("flex shrink-0 items-center text-fg-subtle transition-colors duration-(--duration-fast)", "group-hover/select-trigger:text-fg-muted group-data-popup-open/select-trigger:text-fg-muted"), children: _jsx(ChevronsUpDown, { "aria-hidden": true, className: "size-3.5" }) })] }));
|
|
57
|
+
}
|
|
58
|
+
export function SelectValue({ className, ...props }) {
|
|
59
|
+
return (_jsx(BaseSelect.Value, { "data-slot": "select-value", className: withBase("min-w-0 truncate data-placeholder:text-fg-subtle", className), ...props }));
|
|
60
|
+
}
|
|
61
|
+
/* ------------------------------------------------------------------------------------------ */
|
|
62
|
+
/* Popup */
|
|
63
|
+
/* ------------------------------------------------------------------------------------------ */
|
|
64
|
+
const scrollArrowClass = cn("z-10 flex h-6 w-full cursor-default items-center justify-center bg-surface-raised text-fg-subtle", "transition-opacity duration-(--duration-fast) data-starting-style:opacity-0 data-ending-style:opacity-0", "before:absolute before:inset-x-0 before:h-full before:content-['']", "data-[direction=up]:top-0 data-[direction=up]:rounded-t-xl data-[direction=up]:data-[side=none]:before:-top-full", "data-[direction=down]:bottom-0 data-[direction=down]:rounded-b-xl data-[direction=down]:data-[side=none]:before:-bottom-full");
|
|
65
|
+
/** Portal + positioner + popup + scroll arrows + list in one. Children are items/groups. */
|
|
66
|
+
export function SelectContent({ side = "bottom", align = "start", sideOffset = 6, alignItemWithTrigger = false, className, children, ...props }) {
|
|
67
|
+
const container = usePortalContainer();
|
|
68
|
+
return (_jsx(BaseSelect.Portal, { container: container, children: _jsx(BaseSelect.Positioner, { side: side, align: align, sideOffset: sideOffset, alignItemWithTrigger: alignItemWithTrigger, collisionPadding: 8, className: "z-(--z-overlay) select-none outline-none", children: _jsxs(BaseSelect.Popup, { "data-slot": "select-content", className: withBase(cn(listboxPopupClass, "relative overflow-hidden p-0"), className), ...props, children: [_jsx(BaseSelect.ScrollUpArrow, { className: scrollArrowClass, children: _jsx(ChevronUp, { "aria-hidden": true, className: "size-3.5" }) }), _jsx(BaseSelect.List, { className: "relative max-h-[min(24rem,var(--available-height))] scroll-py-6 overflow-y-auto overscroll-contain p-1 outline-none scrollbar-none", children: children }), _jsx(BaseSelect.ScrollDownArrow, { className: scrollArrowClass, children: _jsx(ChevronDown, { "aria-hidden": true, className: "size-3.5" }) })] }) }) }));
|
|
69
|
+
}
|
|
70
|
+
export function SelectItem({ icon, description, trailing, className, children, ...props }) {
|
|
71
|
+
return (_jsxs(BaseSelect.Item, { "data-slot": "select-item", className: withBase(listboxItemClass, className), ...props, children: [icon !== undefined && (_jsx("span", { "aria-hidden": true, className: listboxIconClass, children: icon })), _jsx(ItemLabel, { description: description, as: BaseSelect.ItemText, children: children }), trailing, _jsx(BaseSelect.ItemIndicator, { className: listboxIndicatorClass, children: _jsx(Check, { "aria-hidden": true, strokeWidth: 2.25, className: "size-3.5" }) })] }));
|
|
72
|
+
}
|
|
73
|
+
export function SelectGroup({ className, ...props }) {
|
|
74
|
+
return (_jsx(BaseSelect.Group, { "data-slot": "select-group", className: withBase("flex flex-col", className), ...props }));
|
|
75
|
+
}
|
|
76
|
+
export function SelectGroupLabel({ className, ...props }) {
|
|
77
|
+
return (_jsx(BaseSelect.GroupLabel, { "data-slot": "select-group-label", className: withBase(listboxGroupLabelClass, className), ...props }));
|
|
78
|
+
}
|
|
79
|
+
export function SelectSeparator({ className, ...props }) {
|
|
80
|
+
return (_jsx(BaseSelect.Separator, { "data-slot": "select-separator", className: withBase(listboxSeparatorClass, className), ...props }));
|
|
81
|
+
}
|
|
82
|
+
const isGroup = (entry) => "options" in entry;
|
|
83
|
+
/** Single-value select from a list of options: `<Select items={models} defaultValue="opus" />`. */
|
|
84
|
+
export function Select({ items, placeholder, leading, variant, size, block, "aria-label": ariaLabel, className, contentClassName, side, align, alignItemWithTrigger, ...props }) {
|
|
85
|
+
const flat = items.flatMap((entry) => (isGroup(entry) ? entry.options : [entry]));
|
|
86
|
+
const labels = flat.map((option) => ({ value: option.value, label: option.label }));
|
|
87
|
+
const renderOption = (option) => (_jsx(SelectItem, { value: option.value, icon: option.icon, description: option.description, disabled: option.disabled === true, children: option.label }, option.label));
|
|
88
|
+
return (_jsxs(BaseSelect.Root, { items: labels, ...props, children: [_jsx(SelectTrigger, { variant: variant, size: size, block: block, leading: leading, className: className, ...(ariaLabel === undefined ? {} : { "aria-label": ariaLabel }), children: _jsx(SelectValue, { placeholder: placeholder }) }), _jsx(SelectContent, { ...(side === undefined ? {} : { side }), ...(align === undefined ? {} : { align }), ...(alignItemWithTrigger === undefined ? {} : { alignItemWithTrigger }), ...(contentClassName === undefined ? {} : { className: contentClassName }), children: items.map((entry, index) => isGroup(entry) ? (_jsxs(Fragment, { children: [index > 0 && _jsx(SelectSeparator, {}), _jsxs(SelectGroup, { children: [_jsx(SelectGroupLabel, { children: entry.label }), entry.options.map(renderOption)] })] }, entry.label)) : (renderOption(entry))) })] }));
|
|
89
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Separator as BaseSeparator } from "@base-ui/react/separator";
|
|
2
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
3
|
+
export interface SeparatorProps extends Omit<ComponentProps<typeof BaseSeparator>, "children"> {
|
|
4
|
+
/** Centered text (e.g. "Today", "or"). Horizontal only. */
|
|
5
|
+
label?: ReactNode;
|
|
6
|
+
/** Stronger line for section breaks. */
|
|
7
|
+
strong?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** A hairline divider. With `label`, two lines flank centered text. */
|
|
10
|
+
export declare function Separator({ orientation, label, strong, className, ...props }: SeparatorProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Separator as BaseSeparator } from "@base-ui/react/separator";
|
|
4
|
+
import { cn, resolveClassName } from "../lib/cn.js";
|
|
5
|
+
/** A hairline divider. With `label`, two lines flank centered text. */
|
|
6
|
+
export function Separator({ orientation = "horizontal", label, strong = false, className, ...props }) {
|
|
7
|
+
const line = strong ? "bg-border" : "bg-border-subtle";
|
|
8
|
+
if (label !== undefined && orientation === "horizontal") {
|
|
9
|
+
return (_jsxs(BaseSeparator, { "data-slot": "separator", orientation: "horizontal", className: (state) => cn("flex w-full items-center gap-3 text-xs font-medium text-fg-subtle", resolveClassName(className, state)), ...props, children: [_jsx("span", { "aria-hidden": true, className: cn("h-px flex-1", line) }), _jsx("span", { className: "shrink-0 select-none", children: label }), _jsx("span", { "aria-hidden": true, className: cn("h-px flex-1", line) })] }));
|
|
10
|
+
}
|
|
11
|
+
return (_jsx(BaseSeparator, { "data-slot": "separator", orientation: orientation, className: (state) => cn("shrink-0", line, orientation === "horizontal" ? "h-px w-full" : "w-px self-stretch", resolveClassName(className, state)), ...props }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Drawer as BaseDrawer } from "@base-ui/react/drawer";
|
|
2
|
+
import type { ComponentProps, ReactElement, ReactNode } from "react";
|
|
3
|
+
import { type VariantProps } from "../lib/cn.js";
|
|
4
|
+
/** The edge a sheet slides from; `start` and `end` follow the writing direction. */
|
|
5
|
+
export type SheetSide = "start" | "end" | "top" | "bottom";
|
|
6
|
+
declare const sheetPopupStyles: import("tailwind-variants").TVReturnType<{
|
|
7
|
+
side: {
|
|
8
|
+
end: string[];
|
|
9
|
+
start: string[];
|
|
10
|
+
bottom: string[];
|
|
11
|
+
top: string[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Side sheets' width: sm for a short form, md for a list with
|
|
15
|
+
* detail, lg for a two-column editor. Under 1280 px windows md narrows to 440 px and lg to 520 px.
|
|
16
|
+
*/
|
|
17
|
+
size: {
|
|
18
|
+
sm: "";
|
|
19
|
+
md: "";
|
|
20
|
+
lg: "";
|
|
21
|
+
};
|
|
22
|
+
}, undefined, string[], {
|
|
23
|
+
side: {
|
|
24
|
+
end: string[];
|
|
25
|
+
start: string[];
|
|
26
|
+
bottom: string[];
|
|
27
|
+
top: string[];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Side sheets' width: sm for a short form, md for a list with
|
|
31
|
+
* detail, lg for a two-column editor. Under 1280 px windows md narrows to 440 px and lg to 520 px.
|
|
32
|
+
*/
|
|
33
|
+
size: {
|
|
34
|
+
sm: "";
|
|
35
|
+
md: "";
|
|
36
|
+
lg: "";
|
|
37
|
+
};
|
|
38
|
+
}, undefined, import("tailwind-variants").TVReturnTypeLike<{
|
|
39
|
+
side: {
|
|
40
|
+
end: string[];
|
|
41
|
+
start: string[];
|
|
42
|
+
bottom: string[];
|
|
43
|
+
top: string[];
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Side sheets' width: sm for a short form, md for a list with
|
|
47
|
+
* detail, lg for a two-column editor. Under 1280 px windows md narrows to 440 px and lg to 520 px.
|
|
48
|
+
*/
|
|
49
|
+
size: {
|
|
50
|
+
sm: "";
|
|
51
|
+
md: "";
|
|
52
|
+
lg: "";
|
|
53
|
+
};
|
|
54
|
+
}, undefined>>;
|
|
55
|
+
type SheetVariants = VariantProps<typeof sheetPopupStyles>;
|
|
56
|
+
export type SheetSize = NonNullable<SheetVariants["size"]>;
|
|
57
|
+
export interface SheetRootProps extends Omit<ComponentProps<typeof BaseDrawer.Root>, "swipeDirection"> {
|
|
58
|
+
/** Edge the sheet slides from; also sets the swipe-to-dismiss direction. */
|
|
59
|
+
side?: SheetSide;
|
|
60
|
+
}
|
|
61
|
+
/** Drawer root with `side` mapped to Base UI's `swipeDirection`. */
|
|
62
|
+
export declare function SheetRoot({ side, ...props }: SheetRootProps): import("react").JSX.Element;
|
|
63
|
+
export declare const SheetTrigger: BaseDrawer.Trigger;
|
|
64
|
+
export declare const SheetClose: BaseDrawer.Close;
|
|
65
|
+
export interface SheetContentProps extends ComponentProps<typeof BaseDrawer.Popup> {
|
|
66
|
+
/** Must match the `side` passed to SheetRoot. */
|
|
67
|
+
side?: SheetSide;
|
|
68
|
+
/** Render the corner close button. */
|
|
69
|
+
closable?: boolean;
|
|
70
|
+
/** Side sheets' width: sm 420 · md 520 · lg 640 px. */
|
|
71
|
+
size?: SheetSize;
|
|
72
|
+
}
|
|
73
|
+
/** Portal + backdrop + viewport + popup, sliding from `side`. */
|
|
74
|
+
export declare function SheetContent({ side, size, closable, className, children, ...props }: SheetContentProps): import("react").JSX.Element;
|
|
75
|
+
export declare function SheetHeader({ className, ...props }: ComponentProps<"div">): import("react").JSX.Element;
|
|
76
|
+
/** Scrollable middle region. */
|
|
77
|
+
export declare function SheetBody({ className, ...props }: ComponentProps<"div">): import("react").JSX.Element;
|
|
78
|
+
export declare function SheetFooter({ className, ...props }: ComponentProps<"div">): import("react").JSX.Element;
|
|
79
|
+
export declare function SheetTitle({ className, ...props }: ComponentProps<typeof BaseDrawer.Title>): import("react").JSX.Element;
|
|
80
|
+
export declare function SheetDescription({ className, ...props }: ComponentProps<typeof BaseDrawer.Description>): import("react").JSX.Element;
|
|
81
|
+
export declare function SheetCloseButton({ className, "aria-label": ariaLabel, ...props }: ComponentProps<typeof BaseDrawer.Close>): import("react").JSX.Element;
|
|
82
|
+
export interface SheetProps {
|
|
83
|
+
/** Optional trigger element; must accept a ref and DOM props (e.g. <Button>). */
|
|
84
|
+
trigger?: ReactElement;
|
|
85
|
+
side?: SheetSide;
|
|
86
|
+
title?: ReactNode;
|
|
87
|
+
description?: ReactNode;
|
|
88
|
+
children?: ReactNode;
|
|
89
|
+
footer?: ReactNode;
|
|
90
|
+
open?: boolean;
|
|
91
|
+
defaultOpen?: boolean;
|
|
92
|
+
onOpenChange?: ComponentProps<typeof BaseDrawer.Root>["onOpenChange"];
|
|
93
|
+
closable?: boolean;
|
|
94
|
+
/** Side sheets' width: sm 420 · md 520 · lg 640 px. */
|
|
95
|
+
size?: SheetSize;
|
|
96
|
+
/** Popup class. */
|
|
97
|
+
className?: string;
|
|
98
|
+
}
|
|
99
|
+
/** Convenience wrapper with header/body/footer layout. */
|
|
100
|
+
export declare function Sheet({ trigger, side, title, description, children, footer, open, defaultOpen, onOpenChange, closable, size, className, }: SheetProps): import("react").JSX.Element;
|
|
101
|
+
export {};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useDirection } from "@base-ui/react/direction-provider";
|
|
4
|
+
import { Drawer as BaseDrawer } from "@base-ui/react/drawer";
|
|
5
|
+
import { X } from "lucide-react";
|
|
6
|
+
import { useUiStrings } from "../i18n/provider.js";
|
|
7
|
+
import { cn, tv, withBase } from "../lib/cn.js";
|
|
8
|
+
import { usePortalContainer } from "../lib/theme-scope.js";
|
|
9
|
+
import { iconButtonStyles } from "./button.js";
|
|
10
|
+
import { dialogDescriptionClass, dialogTitleClass } from "./dialog.js";
|
|
11
|
+
/** Base UI swipes on screen: an `end` sheet is dismissed rightward left to right, leftward right to left. */
|
|
12
|
+
function swipeOf(side, direction) {
|
|
13
|
+
switch (side) {
|
|
14
|
+
case "top":
|
|
15
|
+
return "up";
|
|
16
|
+
case "bottom":
|
|
17
|
+
return "down";
|
|
18
|
+
case "start":
|
|
19
|
+
return direction === "rtl" ? "right" : "left";
|
|
20
|
+
case "end":
|
|
21
|
+
return direction === "rtl" ? "left" : "right";
|
|
22
|
+
default: {
|
|
23
|
+
const never = side;
|
|
24
|
+
return never;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const sheetViewport = tv({
|
|
29
|
+
base: "fixed inset-0 z-(--z-overlay) flex p-2 outline-none",
|
|
30
|
+
variants: {
|
|
31
|
+
side: {
|
|
32
|
+
start: "items-stretch justify-start",
|
|
33
|
+
end: "items-stretch justify-end",
|
|
34
|
+
top: "items-start justify-center",
|
|
35
|
+
bottom: "items-end justify-center",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const sheetPopupStyles = tv({
|
|
40
|
+
base: [
|
|
41
|
+
"relative flex flex-col overflow-hidden rounded-2xl border border-border-subtle bg-surface-overlay text-fg outline-none",
|
|
42
|
+
"[box-shadow:var(--shadow-xl),var(--shadow-inset-edge)] touch-auto overscroll-contain",
|
|
43
|
+
// Spring in; release speed follows swipe velocity; follow the finger 1:1 while swiping.
|
|
44
|
+
"transition-[transform,opacity] duration-(--duration-spring-smooth) ease-(--ease-spring-smooth)",
|
|
45
|
+
"data-ending-style:duration-[calc(var(--drawer-swipe-strength,1)*320ms)] data-ending-style:ease-(--ease-out)",
|
|
46
|
+
"data-swiping:select-none data-swiping:duration-0",
|
|
47
|
+
"motion-reduce:data-starting-style:opacity-0 motion-reduce:data-ending-style:opacity-0",
|
|
48
|
+
"motion-reduce:data-starting-style:[transform:none] motion-reduce:data-ending-style:[transform:none]",
|
|
49
|
+
],
|
|
50
|
+
variants: {
|
|
51
|
+
side: {
|
|
52
|
+
end: [
|
|
53
|
+
"h-full max-w-[calc(100vw-1rem)]",
|
|
54
|
+
"[transform:translateX(var(--drawer-swipe-movement-x))]",
|
|
55
|
+
"data-starting-style:[transform:translateX(calc(100%+0.75rem))] data-ending-style:[transform:translateX(calc(100%+0.75rem))]",
|
|
56
|
+
"rtl:data-starting-style:[transform:translateX(calc(-100%-0.75rem))] rtl:data-ending-style:[transform:translateX(calc(-100%-0.75rem))]",
|
|
57
|
+
],
|
|
58
|
+
start: [
|
|
59
|
+
"h-full max-w-[calc(100vw-1rem)]",
|
|
60
|
+
"[transform:translateX(var(--drawer-swipe-movement-x))]",
|
|
61
|
+
"data-starting-style:[transform:translateX(calc(-100%-0.75rem))] data-ending-style:[transform:translateX(calc(-100%-0.75rem))]",
|
|
62
|
+
"rtl:data-starting-style:[transform:translateX(calc(100%+0.75rem))] rtl:data-ending-style:[transform:translateX(calc(100%+0.75rem))]",
|
|
63
|
+
],
|
|
64
|
+
bottom: [
|
|
65
|
+
"max-h-[85dvh] w-full max-w-[720px]",
|
|
66
|
+
"[transform:translateY(calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y)))]",
|
|
67
|
+
"data-starting-style:[transform:translateY(calc(100%+0.75rem))] data-ending-style:[transform:translateY(calc(100%+0.75rem))]",
|
|
68
|
+
],
|
|
69
|
+
top: [
|
|
70
|
+
"max-h-[85dvh] w-full max-w-[720px]",
|
|
71
|
+
"[transform:translateY(var(--drawer-swipe-movement-y))]",
|
|
72
|
+
"data-starting-style:[transform:translateY(calc(-100%-0.75rem))] data-ending-style:[transform:translateY(calc(-100%-0.75rem))]",
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
/**
|
|
76
|
+
* Side sheets' width: sm for a short form, md for a list with
|
|
77
|
+
* detail, lg for a two-column editor. Under 1280 px windows md narrows to 440 px and lg to 520 px.
|
|
78
|
+
*/
|
|
79
|
+
size: {
|
|
80
|
+
sm: "",
|
|
81
|
+
md: "",
|
|
82
|
+
lg: "",
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
compoundVariants: [
|
|
86
|
+
{ side: ["end", "start"], size: "sm", class: "w-[420px]" },
|
|
87
|
+
{ side: ["end", "start"], size: "md", class: "w-[520px] max-xl:w-[440px]" },
|
|
88
|
+
{ side: ["end", "start"], size: "lg", class: "w-[640px] max-xl:w-[520px]" },
|
|
89
|
+
],
|
|
90
|
+
defaultVariants: { side: "end", size: "sm" },
|
|
91
|
+
});
|
|
92
|
+
/** Drawer root with `side` mapped to Base UI's `swipeDirection`. */
|
|
93
|
+
export function SheetRoot({ side = "end", ...props }) {
|
|
94
|
+
const direction = useDirection();
|
|
95
|
+
return _jsx(BaseDrawer.Root, { swipeDirection: swipeOf(side, direction), ...props });
|
|
96
|
+
}
|
|
97
|
+
export const SheetTrigger = BaseDrawer.Trigger;
|
|
98
|
+
export const SheetClose = BaseDrawer.Close;
|
|
99
|
+
/** Portal + backdrop + viewport + popup, sliding from `side`. */
|
|
100
|
+
export function SheetContent({ side = "end", size = "sm", closable = true, className, children, ...props }) {
|
|
101
|
+
const container = usePortalContainer();
|
|
102
|
+
return (_jsxs(BaseDrawer.Portal, { container: container, children: [_jsx(BaseDrawer.Backdrop, { "data-slot": "sheet-backdrop", className: cn("fixed inset-0 z-(--z-overlay) min-h-dvh bg-scrim backdrop-blur-[2px]", "opacity-[calc(1-var(--drawer-swipe-progress,0))] transition-opacity duration-(--duration-moderate) ease-(--ease-out)",
|
|
103
|
+
// A closing backdrop lets a click through to what's under it.
|
|
104
|
+
"data-starting-style:opacity-0 data-ending-style:pointer-events-none data-ending-style:opacity-0 data-swiping:duration-0", "supports-[-webkit-touch-callout:none]:absolute") }), _jsx(BaseDrawer.Viewport, { "data-slot": "sheet-viewport", className: sheetViewport({ side }), children: _jsxs(BaseDrawer.Popup, { "data-slot": "sheet", "data-side": side, className: withBase(sheetPopupStyles({ side, size }), className), ...props, children: [side === "bottom" && (_jsx("div", { "aria-hidden": true, className: "mx-auto mt-2 h-1 w-9 shrink-0 rounded-full bg-border-strong" })), _jsx(BaseDrawer.Content, { className: "flex min-h-0 flex-1 flex-col", children: children }), closable && _jsx(SheetCloseButton, {})] }) })] }));
|
|
105
|
+
}
|
|
106
|
+
export function SheetHeader({ className, ...props }) {
|
|
107
|
+
return (_jsx("div", { "data-slot": "sheet-header", className: cn("flex shrink-0 flex-col gap-1 px-5 pt-5 pb-3 pe-12", className), ...props }));
|
|
108
|
+
}
|
|
109
|
+
/** Scrollable middle region. */
|
|
110
|
+
export function SheetBody({ className, ...props }) {
|
|
111
|
+
return (_jsx("div", { "data-slot": "sheet-body", className: cn("min-h-0 flex-1 overflow-y-auto px-5 pb-4", className), ...props }));
|
|
112
|
+
}
|
|
113
|
+
export function SheetFooter({ className, ...props }) {
|
|
114
|
+
return (_jsx("div", { "data-slot": "sheet-footer", className: cn("flex shrink-0 items-center justify-end gap-2 border-t border-border-subtle px-5 py-3", className), ...props }));
|
|
115
|
+
}
|
|
116
|
+
export function SheetTitle({ className, ...props }) {
|
|
117
|
+
return (_jsx(BaseDrawer.Title, { "data-slot": "sheet-title", className: withBase(cn(dialogTitleClass, "text-md"), className), ...props }));
|
|
118
|
+
}
|
|
119
|
+
export function SheetDescription({ className, ...props }) {
|
|
120
|
+
return (_jsx(BaseDrawer.Description, { "data-slot": "sheet-description", className: withBase(cn(dialogDescriptionClass, "text-sm"), className), ...props }));
|
|
121
|
+
}
|
|
122
|
+
export function SheetCloseButton({ className, "aria-label": ariaLabel, ...props }) {
|
|
123
|
+
const t = useUiStrings().primitives;
|
|
124
|
+
return (_jsx(BaseDrawer.Close, { "data-slot": "sheet-close", "aria-label": ariaLabel ?? t.close, className: withBase(cn(iconButtonStyles({ variant: "ghost", size: "sm" }), "absolute top-3.5 end-3.5"), className), ...props, children: _jsx(X, { "aria-hidden": true }) }));
|
|
125
|
+
}
|
|
126
|
+
/** Convenience wrapper with header/body/footer layout. */
|
|
127
|
+
export function Sheet({ trigger, side = "end", title, description, children, footer, open, defaultOpen, onOpenChange, closable, size, className, }) {
|
|
128
|
+
return (_jsxs(SheetRoot, { side: side, ...(open !== undefined && { open }), ...(defaultOpen !== undefined && { defaultOpen }), ...(onOpenChange && { onOpenChange }), children: [trigger && _jsx(BaseDrawer.Trigger, { render: trigger }), _jsxs(SheetContent, { side: side, ...(size !== undefined && { size }), ...(closable !== undefined && { closable }), className: className, children: [(title !== undefined || description !== undefined) && (_jsxs(SheetHeader, { children: [title !== undefined && _jsx(SheetTitle, { children: title }), description !== undefined && _jsx(SheetDescription, { children: description })] })), children !== undefined && _jsx(SheetBody, { children: children }), footer !== undefined && _jsx(SheetFooter, { children: footer })] })] }));
|
|
129
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export interface ShortcutRecorderProps {
|
|
3
|
+
/** The shortcut now, as logical keys (`["mod", "shift", "space"]`); `null` when there is none. */
|
|
4
|
+
readonly keys: readonly string[] | null;
|
|
5
|
+
/**
|
|
6
|
+
* The shortcut a key press makes, in the caller's own format, or `undefined` when the press can't
|
|
7
|
+
* be one (a key without the modifier the shortcut needs).
|
|
8
|
+
*/
|
|
9
|
+
readonly parse: (event: KeyboardEvent) => string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* What `parse` takes besides the key, for the hint after a refused press: any one of these
|
|
12
|
+
* modifiers (logical keys: `mod`, `ctrl`, `alt`, `shift`), or a function key alone when
|
|
13
|
+
* `functionKey` is set.
|
|
14
|
+
*/
|
|
15
|
+
readonly needs: {
|
|
16
|
+
readonly modifiers: readonly string[];
|
|
17
|
+
readonly functionKey?: boolean;
|
|
18
|
+
};
|
|
19
|
+
/** A press `parse` made a shortcut of. */
|
|
20
|
+
readonly onRecord: (shortcut: string) => void;
|
|
21
|
+
/** Actions beside Change while it isn't recording: Reset, Turn off. */
|
|
22
|
+
readonly children?: ReactNode;
|
|
23
|
+
readonly className?: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Records a keyboard shortcut. Change starts recording and the next combination becomes the
|
|
27
|
+
* shortcut; Escape, a second click on Change or focus leaving it stops without one. While recording,
|
|
28
|
+
* every key press is the recorder's: the app's own shortcuts don't act on it. A press that can't be a
|
|
29
|
+
* shortcut says what it needs; the state and the recorded keys are announced.
|
|
30
|
+
*/
|
|
31
|
+
export declare function ShortcutRecorder({ keys, parse, needs, onRecord, children, className, }: ShortcutRecorderProps): import("react").JSX.Element;
|