open-ds 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/LICENSE +21 -0
- package/README.md +66 -2
- package/assets/schemes/accessible-first.json +3093 -0
- package/assets/schemes/brand-expressive.json +3124 -0
- package/assets/schemes/content-first.json +3110 -0
- package/assets/schemes/dense-data.json +3489 -0
- package/assets/schemes/grammar.json +3903 -0
- package/assets/schemes/multi-brand.json +4359 -0
- package/assets/skills/open-ds/SKILL.md +50 -0
- package/assets/skills/open-ds-build/SKILL.md +19 -0
- package/assets/skills/open-ds-change/SKILL.md +19 -0
- package/assets/skills/open-ds-choose/SKILL.md +18 -0
- package/assets/skills/open-ds-review/SKILL.md +22 -0
- package/assets/skills/open-ds-write/SKILL.md +13 -0
- package/assets/stories/accordion.stories.tsx +46 -0
- package/assets/stories/alert-dialog.stories.tsx +65 -0
- package/assets/stories/alert.stories.tsx +44 -0
- package/assets/stories/aspect-ratio.stories.tsx +37 -0
- package/assets/stories/avatar.stories.tsx +42 -0
- package/assets/stories/badge.stories.tsx +39 -0
- package/assets/stories/breadcrumb.stories.tsx +54 -0
- package/assets/stories/button.stories.tsx +46 -0
- package/assets/stories/calendar.stories.tsx +30 -0
- package/assets/stories/card.stories.tsx +50 -0
- package/assets/stories/carousel.stories.tsx +64 -0
- package/assets/stories/chart.stories.tsx +61 -0
- package/assets/stories/checkbox.stories.tsx +29 -0
- package/assets/stories/collapsible.stories.tsx +39 -0
- package/assets/stories/combobox.stories.tsx +57 -0
- package/assets/stories/command.stories.tsx +60 -0
- package/assets/stories/context-menu.stories.tsx +55 -0
- package/assets/stories/data-table.stories.tsx +93 -0
- package/assets/stories/date-picker.stories.tsx +27 -0
- package/assets/stories/dialog.stories.tsx +70 -0
- package/assets/stories/drawer.stories.tsx +62 -0
- package/assets/stories/dropdown-menu.stories.tsx +65 -0
- package/assets/stories/empty.stories.tsx +39 -0
- package/assets/stories/field.stories.tsx +42 -0
- package/assets/stories/form.stories.tsx +56 -0
- package/assets/stories/input-group.stories.tsx +41 -0
- package/assets/stories/input-otp.stories.tsx +48 -0
- package/assets/stories/input.stories.tsx +32 -0
- package/assets/stories/kbd.stories.tsx +30 -0
- package/assets/stories/label.stories.tsx +26 -0
- package/assets/stories/native-select.stories.tsx +39 -0
- package/assets/stories/navigation-menu.stories.tsx +41 -0
- package/assets/stories/pagination.stories.tsx +61 -0
- package/assets/stories/popover.stories.tsx +37 -0
- package/assets/stories/progress.stories.tsx +30 -0
- package/assets/stories/radio-group.stories.tsx +45 -0
- package/assets/stories/resizable.stories.tsx +54 -0
- package/assets/stories/scroll-area.stories.tsx +47 -0
- package/assets/stories/select.stories.tsx +57 -0
- package/assets/stories/separator.stories.tsx +28 -0
- package/assets/stories/sheet.stories.tsx +46 -0
- package/assets/stories/sidebar.stories.tsx +62 -0
- package/assets/stories/skeleton.stories.tsx +27 -0
- package/assets/stories/slider.stories.tsx +44 -0
- package/assets/stories/spinner.stories.tsx +25 -0
- package/assets/stories/switch.stories.tsx +29 -0
- package/assets/stories/table.stories.tsx +97 -0
- package/assets/stories/tabs.stories.tsx +55 -0
- package/assets/stories/textarea.stories.tsx +28 -0
- package/assets/stories/toast.stories.tsx +30 -0
- package/assets/stories/toggle-group.stories.tsx +41 -0
- package/assets/stories/toggle.stories.tsx +20 -0
- package/assets/stories/tooltip.stories.tsx +49 -0
- package/assets/tokens/accessible-first/content.json +32 -0
- package/assets/tokens/accessible-first/dark.tokens.json +581 -0
- package/assets/tokens/accessible-first/light.tokens.json +581 -0
- package/assets/tokens/accessible-first/shadcn.css +21 -0
- package/assets/tokens/accessible-first/theme.css +101 -0
- package/assets/tokens/accessible-first/tokens.css +331 -0
- package/assets/tokens/accessible-first/tokens.json +1616 -0
- package/assets/tokens/accessible-first/tokens.scoped.css +333 -0
- package/assets/tokens/brand-expressive/content.json +32 -0
- package/assets/tokens/brand-expressive/dark.tokens.json +585 -0
- package/assets/tokens/brand-expressive/light.tokens.json +585 -0
- package/assets/tokens/brand-expressive/shadcn.css +21 -0
- package/assets/tokens/brand-expressive/theme.css +101 -0
- package/assets/tokens/brand-expressive/tokens.css +331 -0
- package/assets/tokens/brand-expressive/tokens.json +1651 -0
- package/assets/tokens/brand-expressive/tokens.scoped.css +333 -0
- package/assets/tokens/content-first/content.json +32 -0
- package/assets/tokens/content-first/dark.tokens.json +582 -0
- package/assets/tokens/content-first/light.tokens.json +582 -0
- package/assets/tokens/content-first/shadcn.css +21 -0
- package/assets/tokens/content-first/theme.css +101 -0
- package/assets/tokens/content-first/tokens.css +331 -0
- package/assets/tokens/content-first/tokens.json +1631 -0
- package/assets/tokens/content-first/tokens.scoped.css +333 -0
- package/assets/tokens/dense-data/content.json +32 -0
- package/assets/tokens/dense-data/dark.tokens.json +580 -0
- package/assets/tokens/dense-data/light.tokens.json +580 -0
- package/assets/tokens/dense-data/shadcn.css +21 -0
- package/assets/tokens/dense-data/theme.css +101 -0
- package/assets/tokens/dense-data/tokens.css +331 -0
- package/assets/tokens/dense-data/tokens.json +1623 -0
- package/assets/tokens/dense-data/tokens.scoped.css +333 -0
- package/assets/tokens/index.json +143 -0
- package/assets/tokens/multi-brand/aster-dark.tokens.json +625 -0
- package/assets/tokens/multi-brand/aster-light.tokens.json +625 -0
- package/assets/tokens/multi-brand/birch-dark.tokens.json +625 -0
- package/assets/tokens/multi-brand/birch-light.tokens.json +625 -0
- package/assets/tokens/multi-brand/content.json +32 -0
- package/assets/tokens/multi-brand/coral-dark.tokens.json +625 -0
- package/assets/tokens/multi-brand/coral-light.tokens.json +625 -0
- package/assets/tokens/multi-brand/shadcn.css +21 -0
- package/assets/tokens/multi-brand/theme.css +101 -0
- package/assets/tokens/multi-brand/tokens.css +1002 -0
- package/assets/tokens/multi-brand/tokens.json +1754 -0
- package/assets/tokens/multi-brand/tokens.scoped.css +1022 -0
- package/assets/tokens/shadcn-map.json +34 -0
- package/assets/ui/components/accordion.tsx +60 -0
- package/assets/ui/components/alert-dialog.tsx +171 -0
- package/assets/ui/components/alert.tsx +56 -0
- package/assets/ui/components/aspect-ratio.tsx +17 -0
- package/assets/ui/components/avatar.tsx +96 -0
- package/assets/ui/components/badge.tsx +36 -0
- package/assets/ui/components/breadcrumb.tsx +99 -0
- package/assets/ui/components/button.tsx +53 -0
- package/assets/ui/components/calendar.tsx +169 -0
- package/assets/ui/components/card.tsx +72 -0
- package/assets/ui/components/carousel.tsx +224 -0
- package/assets/ui/components/chart.tsx +315 -0
- package/assets/ui/components/checkbox.tsx +29 -0
- package/assets/ui/components/collapsible.tsx +25 -0
- package/assets/ui/components/combobox.tsx +275 -0
- package/assets/ui/components/command.tsx +145 -0
- package/assets/ui/components/context-menu.tsx +218 -0
- package/assets/ui/components/data-table.tsx +307 -0
- package/assets/ui/components/date-picker.tsx +79 -0
- package/assets/ui/components/dialog.tsx +68 -0
- package/assets/ui/components/drawer.tsx +115 -0
- package/assets/ui/components/dropdown-menu.tsx +58 -0
- package/assets/ui/components/empty.tsx +92 -0
- package/assets/ui/components/field.tsx +234 -0
- package/assets/ui/components/form.tsx +146 -0
- package/assets/ui/components/input-group.tsx +154 -0
- package/assets/ui/components/input-otp.tsx +75 -0
- package/assets/ui/components/input.tsx +25 -0
- package/assets/ui/components/kbd.tsx +30 -0
- package/assets/ui/components/label.tsx +22 -0
- package/assets/ui/components/native-select.tsx +59 -0
- package/assets/ui/components/navigation-menu.tsx +149 -0
- package/assets/ui/components/pagination.tsx +108 -0
- package/assets/ui/components/popover.tsx +61 -0
- package/assets/ui/components/progress.tsx +31 -0
- package/assets/ui/components/radio-group.tsx +40 -0
- package/assets/ui/components/resizable.tsx +54 -0
- package/assets/ui/components/scroll-area.tsx +55 -0
- package/assets/ui/components/select.tsx +81 -0
- package/assets/ui/components/separator.tsx +35 -0
- package/assets/ui/components/sheet.tsx +114 -0
- package/assets/ui/components/sidebar.tsx +685 -0
- package/assets/ui/components/skeleton.tsx +15 -0
- package/assets/ui/components/slider.tsx +70 -0
- package/assets/ui/components/spinner.tsx +16 -0
- package/assets/ui/components/switch.tsx +32 -0
- package/assets/ui/components/table.tsx +78 -0
- package/assets/ui/components/tabs.tsx +49 -0
- package/assets/ui/components/textarea.tsx +25 -0
- package/assets/ui/components/toast.tsx +54 -0
- package/assets/ui/components/toggle-group.tsx +88 -0
- package/assets/ui/components/toggle.tsx +48 -0
- package/assets/ui/components/tooltip.tsx +34 -0
- package/assets/ui/lib/use-mobile.ts +20 -0
- package/assets/ui/lib/utils.ts +7 -0
- package/assets/ui/styles.css +27 -0
- package/bin/open-ds.js +6 -0
- package/package.json +56 -4
- package/src/agents.mjs +150 -0
- package/src/cli.mjs +445 -0
- package/src/components.mjs +69 -0
- package/src/context.mjs +143 -0
- package/src/create.mjs +377 -0
- package/src/docs.mjs +570 -0
- package/src/guidance.mjs +63 -0
- package/src/index.mjs +20 -0
- package/src/mcp.mjs +660 -0
- package/src/readmes.mjs +106 -0
- package/src/schemes.mjs +188 -0
- package/src/tokens.mjs +127 -0
- package/templates/project/.github/workflows/ci.yml +15 -0
- package/templates/project/.storybook/component-docs.mdx +26 -0
- package/templates/project/.storybook/main.ts +17 -0
- package/templates/project/.storybook/manager-head.html +3 -0
- package/templates/project/.storybook/manager.ts +37 -0
- package/templates/project/.storybook/preview-head.html +3 -0
- package/templates/project/.storybook/preview.tsx +82 -0
- package/templates/project/docs/docusaurus.config.ts +64 -0
- package/templates/project/docs/sidebars.ts +8 -0
- package/templates/project/docs/src/css/custom.css +275 -0
- package/templates/project/gitignore +6 -0
- package/templates/project/src/styles/index.css +16 -0
- package/templates/project/src/vite-env.d.ts +7 -0
- package/templates/project/stories/docs-blocks.tsx +190 -0
- package/templates/project/stories/samples.tsx +102 -0
- package/templates/project/stories/usage.tsx +31 -0
- package/templates/project/tokens/build.mjs +468 -0
- package/templates/project/tsconfig.json +15 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date picker. A button that opens a calendar, for one date or a range.
|
|
3
|
+
*
|
|
4
|
+
* Built from the system's own button, popover and calendar, after shadcn/ui's Date Picker (MIT).
|
|
5
|
+
* Reads the shadcn variables, which theme.css maps onto the scheme's semantic tokens, and the
|
|
6
|
+
* radius, shadow and text scale that shadcn.css maps onto the scheme's tokens. The placeholder is a
|
|
7
|
+
* prop, so the words come from the scheme.
|
|
8
|
+
*/
|
|
9
|
+
"use client";
|
|
10
|
+
|
|
11
|
+
import { format } from "date-fns";
|
|
12
|
+
import { CalendarIcon } from "lucide-react";
|
|
13
|
+
import * as React from "react";
|
|
14
|
+
import type { DateRange } from "react-day-picker";
|
|
15
|
+
import { cn } from "../lib/utils";
|
|
16
|
+
import { Button } from "./button";
|
|
17
|
+
import { Calendar } from "./calendar";
|
|
18
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
|
19
|
+
|
|
20
|
+
export type { DateRange };
|
|
21
|
+
|
|
22
|
+
type Common = { placeholder?: string; className?: string; dateFormat?: string };
|
|
23
|
+
|
|
24
|
+
export type DatePickerProps =
|
|
25
|
+
| (Common & { mode?: "single"; value?: Date; defaultValue?: Date; onChange?: (date: Date | undefined) => void })
|
|
26
|
+
| (Common & { mode: "range"; value?: DateRange; defaultValue?: DateRange; onChange?: (range: DateRange | undefined) => void });
|
|
27
|
+
|
|
28
|
+
export function DatePicker(props: DatePickerProps) {
|
|
29
|
+
const { placeholder = "Pick a date", className, dateFormat = "d MMM yyyy" } = props;
|
|
30
|
+
const [inner, setInner] = React.useState<Date | DateRange | undefined>(props.defaultValue);
|
|
31
|
+
const value = props.value ?? inner;
|
|
32
|
+
const label =
|
|
33
|
+
props.mode === "range"
|
|
34
|
+
? (() => {
|
|
35
|
+
const r = value as DateRange | undefined;
|
|
36
|
+
if (!r?.from) return placeholder;
|
|
37
|
+
return r.to ? `${format(r.from, dateFormat)} – ${format(r.to, dateFormat)}` : format(r.from, dateFormat);
|
|
38
|
+
})()
|
|
39
|
+
: value
|
|
40
|
+
? format(value as Date, dateFormat)
|
|
41
|
+
: placeholder;
|
|
42
|
+
return (
|
|
43
|
+
<Popover>
|
|
44
|
+
<PopoverTrigger asChild>
|
|
45
|
+
<Button
|
|
46
|
+
variant="secondary"
|
|
47
|
+
data-slot="date-picker"
|
|
48
|
+
data-empty={!value}
|
|
49
|
+
className={cn("justify-start text-left font-normal data-[empty=true]:text-muted-foreground", className)}
|
|
50
|
+
>
|
|
51
|
+
<CalendarIcon />
|
|
52
|
+
{label}
|
|
53
|
+
</Button>
|
|
54
|
+
</PopoverTrigger>
|
|
55
|
+
<PopoverContent className="w-auto p-0" align="start">
|
|
56
|
+
{props.mode === "range" ? (
|
|
57
|
+
<Calendar
|
|
58
|
+
mode="range"
|
|
59
|
+
numberOfMonths={2}
|
|
60
|
+
selected={value as DateRange | undefined}
|
|
61
|
+
onSelect={(r) => {
|
|
62
|
+
setInner(r);
|
|
63
|
+
props.onChange?.(r);
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
) : (
|
|
67
|
+
<Calendar
|
|
68
|
+
mode="single"
|
|
69
|
+
selected={value as Date | undefined}
|
|
70
|
+
onSelect={(d) => {
|
|
71
|
+
setInner(d);
|
|
72
|
+
(props.onChange as ((d: Date | undefined) => void) | undefined)?.(d);
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
76
|
+
</PopoverContent>
|
|
77
|
+
</Popover>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialog. Interrupts to ask for a decision, such as confirming a delete. The scrim dims the
|
|
3
|
+
* page where the scheme answers the menu-or-dialog situation with a dimmed page.
|
|
4
|
+
*
|
|
5
|
+
* Reads only component keys: --overlay-*.
|
|
6
|
+
*/
|
|
7
|
+
import { X } from "lucide-react";
|
|
8
|
+
import { Dialog as DialogPrimitive } from "radix-ui";
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
import { cn } from "../lib/utils";
|
|
11
|
+
|
|
12
|
+
export const Dialog = DialogPrimitive.Root;
|
|
13
|
+
export const DialogTrigger = DialogPrimitive.Trigger;
|
|
14
|
+
export const DialogClose = DialogPrimitive.Close;
|
|
15
|
+
|
|
16
|
+
export function DialogContent({ className, children, ...props }: React.ComponentProps<typeof DialogPrimitive.Content>) {
|
|
17
|
+
return (
|
|
18
|
+
<DialogPrimitive.Portal>
|
|
19
|
+
<DialogPrimitive.Overlay data-slot="dialog-overlay" className="fixed inset-0 z-50 bg-[var(--overlay-scrim)]" />
|
|
20
|
+
<DialogPrimitive.Content
|
|
21
|
+
data-slot="dialog-content"
|
|
22
|
+
className={cn(
|
|
23
|
+
"fixed left-1/2 top-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] sm:max-w-lg -translate-x-1/2 -translate-y-1/2",
|
|
24
|
+
"gap-[var(--space-gap-group)] p-[var(--space-inset-container)] rounded-[var(--overlay-radius)]",
|
|
25
|
+
"bg-[var(--overlay-background)] text-[var(--color-text-primary)] [box-shadow:var(--overlay-shadow)]",
|
|
26
|
+
"border-[length:var(--border-width-default)] border-[var(--color-border-default)]",
|
|
27
|
+
className,
|
|
28
|
+
)}
|
|
29
|
+
{...props}
|
|
30
|
+
>
|
|
31
|
+
{children}
|
|
32
|
+
<DialogPrimitive.Close className="absolute right-3 top-3 rounded-[var(--radius-badge)] p-1 text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-sunken)]">
|
|
33
|
+
<X className="size-[var(--size-icon)]" />
|
|
34
|
+
<span className="sr-only">Close</span>
|
|
35
|
+
</DialogPrimitive.Close>
|
|
36
|
+
</DialogPrimitive.Content>
|
|
37
|
+
</DialogPrimitive.Portal>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function DialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
42
|
+
return <div className={cn("flex flex-col gap-[var(--space-gap-tight)]", className)} {...props} />;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function DialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
46
|
+
return <div className={cn("flex justify-end gap-[var(--space-gap-default)]", className)} {...props} />;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
50
|
+
return (
|
|
51
|
+
<DialogPrimitive.Title
|
|
52
|
+
className={cn(
|
|
53
|
+
"[font-family:var(--type-heading-font-family)] text-[length:var(--type-heading-font-size)] [font-weight:var(--type-heading-font-weight)]",
|
|
54
|
+
className,
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
62
|
+
return (
|
|
63
|
+
<DialogPrimitive.Description
|
|
64
|
+
className={cn("text-[var(--color-text-secondary)] text-[length:var(--type-body-font-size)]", className)}
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drawer. A panel that slides up from the bottom, for phones.
|
|
3
|
+
*
|
|
4
|
+
* From shadcn/ui (new-york-v4, MIT), carried by the schemes whose inventory names it.
|
|
5
|
+
* Reads the shadcn variables, which theme.css maps onto the scheme's semantic tokens, and the
|
|
6
|
+
* radius, shadow and text scale that shadcn.css maps onto the scheme's tokens. Dark mode comes
|
|
7
|
+
* from the tokens, so no dark: classes.
|
|
8
|
+
*/
|
|
9
|
+
"use client";
|
|
10
|
+
|
|
11
|
+
import * as React from "react";
|
|
12
|
+
import { cn } from "../lib/utils";
|
|
13
|
+
import { Drawer as DrawerPrimitive } from "vaul";
|
|
14
|
+
|
|
15
|
+
function Drawer({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
|
16
|
+
return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function DrawerTrigger({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
|
20
|
+
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function DrawerPortal({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
|
24
|
+
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function DrawerClose({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Close>) {
|
|
28
|
+
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function DrawerOverlay({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
|
|
32
|
+
return (
|
|
33
|
+
<DrawerPrimitive.Overlay
|
|
34
|
+
data-slot="drawer-overlay"
|
|
35
|
+
className={cn(
|
|
36
|
+
"fixed inset-0 z-50 bg-[var(--color-surface-scrim)] data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
37
|
+
className,
|
|
38
|
+
)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function DrawerContent({ className, children, ...props }: React.ComponentProps<typeof DrawerPrimitive.Content>) {
|
|
45
|
+
return (
|
|
46
|
+
<DrawerPortal data-slot="drawer-portal">
|
|
47
|
+
<DrawerOverlay />
|
|
48
|
+
<DrawerPrimitive.Content
|
|
49
|
+
data-slot="drawer-content"
|
|
50
|
+
className={cn(
|
|
51
|
+
"group/drawer-content fixed z-50 flex h-auto flex-col bg-background",
|
|
52
|
+
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
|
|
53
|
+
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
|
|
54
|
+
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
|
|
55
|
+
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
|
|
56
|
+
className,
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
>
|
|
60
|
+
<div className="mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full bg-muted group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
|
|
61
|
+
{children}
|
|
62
|
+
</DrawerPrimitive.Content>
|
|
63
|
+
</DrawerPortal>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
data-slot="drawer-header"
|
|
71
|
+
className={cn(
|
|
72
|
+
"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
|
|
73
|
+
className,
|
|
74
|
+
)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
81
|
+
return <div data-slot="drawer-footer" className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function DrawerTitle({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Title>) {
|
|
85
|
+
return (
|
|
86
|
+
<DrawerPrimitive.Title
|
|
87
|
+
data-slot="drawer-title"
|
|
88
|
+
className={cn("font-semibold text-foreground", className)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function DrawerDescription({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Description>) {
|
|
95
|
+
return (
|
|
96
|
+
<DrawerPrimitive.Description
|
|
97
|
+
data-slot="drawer-description"
|
|
98
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
99
|
+
{...props}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export {
|
|
105
|
+
Drawer,
|
|
106
|
+
DrawerPortal,
|
|
107
|
+
DrawerOverlay,
|
|
108
|
+
DrawerTrigger,
|
|
109
|
+
DrawerClose,
|
|
110
|
+
DrawerContent,
|
|
111
|
+
DrawerHeader,
|
|
112
|
+
DrawerFooter,
|
|
113
|
+
DrawerTitle,
|
|
114
|
+
DrawerDescription,
|
|
115
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dropdown menu. A list of actions behind one button, such as the row actions behind a
|
|
3
|
+
* "more" icon button.
|
|
4
|
+
*
|
|
5
|
+
* Reads only component keys: --overlay-*.
|
|
6
|
+
*/
|
|
7
|
+
import { DropdownMenu as MenuPrimitive } from "radix-ui";
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import { cn } from "../lib/utils";
|
|
10
|
+
|
|
11
|
+
export const DropdownMenu = MenuPrimitive.Root;
|
|
12
|
+
export const DropdownMenuTrigger = MenuPrimitive.Trigger;
|
|
13
|
+
export const DropdownMenuGroup = MenuPrimitive.Group;
|
|
14
|
+
|
|
15
|
+
export function DropdownMenuContent({
|
|
16
|
+
className,
|
|
17
|
+
sideOffset = 4,
|
|
18
|
+
...props
|
|
19
|
+
}: React.ComponentProps<typeof MenuPrimitive.Content>) {
|
|
20
|
+
return (
|
|
21
|
+
<MenuPrimitive.Portal>
|
|
22
|
+
<MenuPrimitive.Content
|
|
23
|
+
data-slot="dropdown-menu-content"
|
|
24
|
+
sideOffset={sideOffset}
|
|
25
|
+
className={cn(
|
|
26
|
+
"z-50 min-w-[10rem] p-1 rounded-[var(--overlay-radius)] bg-[var(--overlay-background)] text-[var(--color-text-primary)]",
|
|
27
|
+
"[box-shadow:var(--overlay-shadow)] border-[length:var(--border-width-default)] border-[var(--color-border-default)]",
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
</MenuPrimitive.Portal>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function DropdownMenuItem({
|
|
37
|
+
className,
|
|
38
|
+
tone = "default",
|
|
39
|
+
...props
|
|
40
|
+
}: React.ComponentProps<typeof MenuPrimitive.Item> & { tone?: "default" | "danger" }) {
|
|
41
|
+
return (
|
|
42
|
+
<MenuPrimitive.Item
|
|
43
|
+
data-slot="dropdown-menu-item"
|
|
44
|
+
className={cn(
|
|
45
|
+
"flex cursor-default select-none items-center gap-2 px-[var(--space-inset-cell-x)] min-h-[var(--overlay-item-height)] rounded-[var(--radius-badge)]",
|
|
46
|
+
"text-[length:var(--type-body-font-size)] outline-none focus:bg-[var(--color-surface-sunken)] data-[disabled]:opacity-[var(--opacity-disabled)]",
|
|
47
|
+
"[&_svg]:size-[var(--size-icon)]",
|
|
48
|
+
tone === "danger" && "text-[var(--color-status-danger)]",
|
|
49
|
+
className,
|
|
50
|
+
)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof MenuPrimitive.Separator>) {
|
|
57
|
+
return <MenuPrimitive.Separator className={cn("my-1 h-px bg-[var(--color-border-default)]", className)} {...props} />;
|
|
58
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Empty. What to show when there is nothing yet.
|
|
3
|
+
*
|
|
4
|
+
* From shadcn/ui (new-york-v4, MIT), carried by the schemes whose inventory names it.
|
|
5
|
+
* Reads the shadcn variables, which theme.css maps onto the scheme's semantic tokens, and the
|
|
6
|
+
* radius, shadow and text scale that shadcn.css maps onto the scheme's tokens. Dark mode comes
|
|
7
|
+
* from the tokens, so no dark: classes.
|
|
8
|
+
*/
|
|
9
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
10
|
+
import { cn } from "../lib/utils";
|
|
11
|
+
|
|
12
|
+
function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
data-slot="empty"
|
|
16
|
+
className={cn(
|
|
17
|
+
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
data-slot="empty-header"
|
|
29
|
+
className={cn("flex max-w-sm flex-col items-center gap-2 text-center", className)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const emptyMediaVariants = cva(
|
|
36
|
+
"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
37
|
+
{
|
|
38
|
+
variants: {
|
|
39
|
+
variant: {
|
|
40
|
+
default: "bg-transparent",
|
|
41
|
+
icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
defaultVariants: {
|
|
45
|
+
variant: "default",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
function EmptyMedia({
|
|
51
|
+
className,
|
|
52
|
+
variant = "default",
|
|
53
|
+
...props
|
|
54
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
data-slot="empty-icon"
|
|
58
|
+
data-variant={variant}
|
|
59
|
+
className={cn(emptyMediaVariants({ variant, className }))}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
66
|
+
return <div data-slot="empty-title" className={cn("text-lg font-medium tracking-tight", className)} {...props} />;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
data-slot="empty-description"
|
|
73
|
+
className={cn(
|
|
74
|
+
"text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
75
|
+
className,
|
|
76
|
+
)}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
data-slot="empty-content"
|
|
86
|
+
className={cn("flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance", className)}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia };
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Field. A label, control, hint and error laid out together.
|
|
3
|
+
*
|
|
4
|
+
* From shadcn/ui (new-york-v4, MIT), carried by the schemes whose inventory names it.
|
|
5
|
+
* Reads the shadcn variables, which theme.css maps onto the scheme's semantic tokens, and the
|
|
6
|
+
* radius, shadow and text scale that shadcn.css maps onto the scheme's tokens. Dark mode comes
|
|
7
|
+
* from the tokens, so no dark: classes.
|
|
8
|
+
*/
|
|
9
|
+
"use client";
|
|
10
|
+
|
|
11
|
+
import { useMemo } from "react";
|
|
12
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
13
|
+
import { cn } from "../lib/utils";
|
|
14
|
+
|
|
15
|
+
import { Label } from "./label";
|
|
16
|
+
import { Separator } from "./separator";
|
|
17
|
+
|
|
18
|
+
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
|
19
|
+
return (
|
|
20
|
+
<fieldset
|
|
21
|
+
data-slot="field-set"
|
|
22
|
+
className={cn(
|
|
23
|
+
"flex flex-col gap-6",
|
|
24
|
+
"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
|
|
25
|
+
className,
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function FieldLegend({
|
|
33
|
+
className,
|
|
34
|
+
variant = "legend",
|
|
35
|
+
...props
|
|
36
|
+
}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
|
|
37
|
+
return (
|
|
38
|
+
<legend
|
|
39
|
+
data-slot="field-legend"
|
|
40
|
+
data-variant={variant}
|
|
41
|
+
className={cn("mb-3 font-medium", "data-[variant=legend]:text-base", "data-[variant=label]:text-sm", className)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
data-slot="field-group"
|
|
51
|
+
className={cn(
|
|
52
|
+
"group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
|
|
53
|
+
className,
|
|
54
|
+
)}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const fieldVariants = cva("group/field flex w-full gap-3 data-[invalid=true]:text-destructive", {
|
|
61
|
+
variants: {
|
|
62
|
+
orientation: {
|
|
63
|
+
vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
|
|
64
|
+
horizontal: [
|
|
65
|
+
"flex-row items-center",
|
|
66
|
+
"[&>[data-slot=field-label]]:flex-auto",
|
|
67
|
+
"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
68
|
+
],
|
|
69
|
+
responsive: [
|
|
70
|
+
"flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
|
|
71
|
+
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
|
|
72
|
+
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
defaultVariants: {
|
|
77
|
+
orientation: "vertical",
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
function Field({
|
|
82
|
+
className,
|
|
83
|
+
orientation = "vertical",
|
|
84
|
+
...props
|
|
85
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
|
|
86
|
+
return (
|
|
87
|
+
<div
|
|
88
|
+
role="group"
|
|
89
|
+
data-slot="field"
|
|
90
|
+
data-orientation={orientation}
|
|
91
|
+
className={cn(fieldVariants({ orientation }), className)}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
98
|
+
return (
|
|
99
|
+
<div
|
|
100
|
+
data-slot="field-content"
|
|
101
|
+
className={cn("group/field-content flex flex-1 flex-col gap-1.5 leading-snug", className)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
|
|
108
|
+
return (
|
|
109
|
+
<Label
|
|
110
|
+
data-slot="field-label"
|
|
111
|
+
className={cn(
|
|
112
|
+
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
|
|
113
|
+
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
|
|
114
|
+
"has-data-[state=checked]:border-primary has-data-[state=checked]:bg-primary/5",
|
|
115
|
+
className,
|
|
116
|
+
)}
|
|
117
|
+
{...props}
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
data-slot="field-label"
|
|
126
|
+
className={cn(
|
|
127
|
+
"flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50",
|
|
128
|
+
className,
|
|
129
|
+
)}
|
|
130
|
+
{...props}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
136
|
+
return (
|
|
137
|
+
<p
|
|
138
|
+
data-slot="field-description"
|
|
139
|
+
className={cn(
|
|
140
|
+
"text-sm leading-normal font-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance",
|
|
141
|
+
"last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
|
|
142
|
+
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
143
|
+
className,
|
|
144
|
+
)}
|
|
145
|
+
{...props}
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function FieldSeparator({
|
|
151
|
+
children,
|
|
152
|
+
className,
|
|
153
|
+
...props
|
|
154
|
+
}: React.ComponentProps<"div"> & {
|
|
155
|
+
children?: React.ReactNode;
|
|
156
|
+
}) {
|
|
157
|
+
return (
|
|
158
|
+
<div
|
|
159
|
+
data-slot="field-separator"
|
|
160
|
+
data-content={!!children}
|
|
161
|
+
className={cn("relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2", className)}
|
|
162
|
+
{...props}
|
|
163
|
+
>
|
|
164
|
+
<Separator className="absolute inset-0 top-1/2" />
|
|
165
|
+
{children && (
|
|
166
|
+
<span
|
|
167
|
+
className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
|
|
168
|
+
data-slot="field-separator-content"
|
|
169
|
+
>
|
|
170
|
+
{children}
|
|
171
|
+
</span>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function FieldError({
|
|
178
|
+
className,
|
|
179
|
+
children,
|
|
180
|
+
errors,
|
|
181
|
+
...props
|
|
182
|
+
}: React.ComponentProps<"div"> & {
|
|
183
|
+
errors?: Array<{ message?: string } | undefined>;
|
|
184
|
+
}) {
|
|
185
|
+
const content = useMemo(() => {
|
|
186
|
+
if (children) {
|
|
187
|
+
return children;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!errors?.length) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()];
|
|
195
|
+
|
|
196
|
+
if (uniqueErrors?.length == 1) {
|
|
197
|
+
return uniqueErrors[0]?.message;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<ul className="ml-4 flex list-disc flex-col gap-1">
|
|
202
|
+
{uniqueErrors.map((error, index) => error?.message && <li key={index}>{error.message}</li>)}
|
|
203
|
+
</ul>
|
|
204
|
+
);
|
|
205
|
+
}, [children, errors]);
|
|
206
|
+
|
|
207
|
+
if (!content) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<div
|
|
213
|
+
role="alert"
|
|
214
|
+
data-slot="field-error"
|
|
215
|
+
className={cn("text-sm font-normal text-destructive", className)}
|
|
216
|
+
{...props}
|
|
217
|
+
>
|
|
218
|
+
{content}
|
|
219
|
+
</div>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export {
|
|
224
|
+
Field,
|
|
225
|
+
FieldLabel,
|
|
226
|
+
FieldDescription,
|
|
227
|
+
FieldError,
|
|
228
|
+
FieldGroup,
|
|
229
|
+
FieldLegend,
|
|
230
|
+
FieldSeparator,
|
|
231
|
+
FieldSet,
|
|
232
|
+
FieldContent,
|
|
233
|
+
FieldTitle,
|
|
234
|
+
};
|