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,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form. Form state and validation wired to fields.
|
|
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 type { Label as LabelPrimitive } from "radix-ui";
|
|
14
|
+
import { Slot } from "radix-ui";
|
|
15
|
+
import {
|
|
16
|
+
Controller,
|
|
17
|
+
FormProvider,
|
|
18
|
+
useFormContext,
|
|
19
|
+
useFormState,
|
|
20
|
+
type ControllerProps,
|
|
21
|
+
type FieldPath,
|
|
22
|
+
type FieldValues,
|
|
23
|
+
} from "react-hook-form";
|
|
24
|
+
|
|
25
|
+
import { Label } from "./label";
|
|
26
|
+
|
|
27
|
+
const Form = FormProvider;
|
|
28
|
+
|
|
29
|
+
type FormFieldContextValue<
|
|
30
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
31
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
32
|
+
> = {
|
|
33
|
+
name: TName;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
|
|
37
|
+
|
|
38
|
+
const FormField = <
|
|
39
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
40
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
41
|
+
>({
|
|
42
|
+
...props
|
|
43
|
+
}: ControllerProps<TFieldValues, TName>) => {
|
|
44
|
+
return (
|
|
45
|
+
<FormFieldContext.Provider value={{ name: props.name }}>
|
|
46
|
+
<Controller {...props} />
|
|
47
|
+
</FormFieldContext.Provider>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const useFormField = () => {
|
|
52
|
+
const fieldContext = React.useContext(FormFieldContext);
|
|
53
|
+
const itemContext = React.useContext(FormItemContext);
|
|
54
|
+
const { getFieldState } = useFormContext();
|
|
55
|
+
const formState = useFormState({ name: fieldContext.name });
|
|
56
|
+
const fieldState = getFieldState(fieldContext.name, formState);
|
|
57
|
+
|
|
58
|
+
if (!fieldContext) {
|
|
59
|
+
throw new Error("useFormField should be used within <FormField>");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { id } = itemContext;
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
id,
|
|
66
|
+
name: fieldContext.name,
|
|
67
|
+
formItemId: `${id}-form-item`,
|
|
68
|
+
formDescriptionId: `${id}-form-item-description`,
|
|
69
|
+
formMessageId: `${id}-form-item-message`,
|
|
70
|
+
...fieldState,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
type FormItemContextValue = {
|
|
75
|
+
id: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
|
|
79
|
+
|
|
80
|
+
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
|
81
|
+
const id = React.useId();
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<FormItemContext.Provider value={{ id }}>
|
|
85
|
+
<div data-slot="form-item" className={cn("grid gap-2", className)} {...props} />
|
|
86
|
+
</FormItemContext.Provider>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function FormLabel({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
91
|
+
const { error, formItemId } = useFormField();
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Label
|
|
95
|
+
data-slot="form-label"
|
|
96
|
+
data-error={!!error}
|
|
97
|
+
className={cn("data-[error=true]:text-destructive", className)}
|
|
98
|
+
htmlFor={formItemId}
|
|
99
|
+
{...props}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function FormControl({ ...props }: React.ComponentProps<typeof Slot.Root>) {
|
|
105
|
+
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<Slot.Root
|
|
109
|
+
data-slot="form-control"
|
|
110
|
+
id={formItemId}
|
|
111
|
+
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
|
112
|
+
aria-invalid={!!error}
|
|
113
|
+
{...props}
|
|
114
|
+
/>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
119
|
+
const { formDescriptionId } = useFormField();
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<p
|
|
123
|
+
data-slot="form-description"
|
|
124
|
+
id={formDescriptionId}
|
|
125
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
|
132
|
+
const { error, formMessageId } = useFormField();
|
|
133
|
+
const body = error ? String(error?.message ?? "") : props.children;
|
|
134
|
+
|
|
135
|
+
if (!body) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<p data-slot="form-message" id={formMessageId} className={cn("text-sm text-destructive", className)} {...props}>
|
|
141
|
+
{body}
|
|
142
|
+
</p>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField };
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input group. An input with addons such as icons or buttons.
|
|
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 { cva, type VariantProps } from "class-variance-authority";
|
|
13
|
+
import { cn } from "../lib/utils";
|
|
14
|
+
|
|
15
|
+
import { Button } from "./button";
|
|
16
|
+
import { Input } from "./input";
|
|
17
|
+
import { Textarea } from "./textarea";
|
|
18
|
+
|
|
19
|
+
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
data-slot="input-group"
|
|
23
|
+
role="group"
|
|
24
|
+
className={cn(
|
|
25
|
+
"group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none",
|
|
26
|
+
"h-9 min-w-0 has-[>textarea]:h-auto",
|
|
27
|
+
|
|
28
|
+
// Variants based on alignment.
|
|
29
|
+
"has-[>[data-align=inline-start]]:[&>input]:pl-2",
|
|
30
|
+
"has-[>[data-align=inline-end]]:[&>input]:pr-2",
|
|
31
|
+
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
|
|
32
|
+
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
|
|
33
|
+
|
|
34
|
+
// Focus state.
|
|
35
|
+
"has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50",
|
|
36
|
+
|
|
37
|
+
// Error state.
|
|
38
|
+
"has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20",
|
|
39
|
+
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const inputGroupAddonVariants = cva(
|
|
48
|
+
"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
|
|
49
|
+
{
|
|
50
|
+
variants: {
|
|
51
|
+
align: {
|
|
52
|
+
"inline-start": "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
|
|
53
|
+
"inline-end": "order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
|
|
54
|
+
"block-start":
|
|
55
|
+
"order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3",
|
|
56
|
+
"block-end": "order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
defaultVariants: {
|
|
60
|
+
align: "inline-start",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
function InputGroupAddon({
|
|
66
|
+
className,
|
|
67
|
+
align = "inline-start",
|
|
68
|
+
...props
|
|
69
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
role="group"
|
|
73
|
+
data-slot="input-group-addon"
|
|
74
|
+
data-align={align}
|
|
75
|
+
className={cn(inputGroupAddonVariants({ align }), className)}
|
|
76
|
+
onClick={(e) => {
|
|
77
|
+
if ((e.target as HTMLElement).closest("button")) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
e.currentTarget.parentElement?.querySelector("input")?.focus();
|
|
81
|
+
}}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const inputGroupButtonVariants = cva("flex items-center gap-2 text-sm shadow-none", {
|
|
88
|
+
variants: {
|
|
89
|
+
size: {
|
|
90
|
+
xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
|
|
91
|
+
sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",
|
|
92
|
+
"icon-xs": "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
|
|
93
|
+
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
defaultVariants: {
|
|
97
|
+
size: "xs",
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
function InputGroupButton({
|
|
102
|
+
className,
|
|
103
|
+
type = "button",
|
|
104
|
+
variant = "ghost",
|
|
105
|
+
size = "xs",
|
|
106
|
+
...props
|
|
107
|
+
}: Omit<React.ComponentProps<typeof Button>, "size"> & VariantProps<typeof inputGroupButtonVariants>) {
|
|
108
|
+
return (
|
|
109
|
+
<Button
|
|
110
|
+
type={type}
|
|
111
|
+
data-size={size}
|
|
112
|
+
variant={variant}
|
|
113
|
+
className={cn(inputGroupButtonVariants({ size }), className)}
|
|
114
|
+
{...props}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
|
|
120
|
+
return (
|
|
121
|
+
<span
|
|
122
|
+
className={cn(
|
|
123
|
+
"flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
|
124
|
+
className,
|
|
125
|
+
)}
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function InputGroupInput({ className, ...props }: React.ComponentProps<"input">) {
|
|
132
|
+
return (
|
|
133
|
+
<Input
|
|
134
|
+
data-slot="input-group-control"
|
|
135
|
+
className={cn("flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0", className)}
|
|
136
|
+
{...props}
|
|
137
|
+
/>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function InputGroupTextarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
142
|
+
return (
|
|
143
|
+
<Textarea
|
|
144
|
+
data-slot="input-group-control"
|
|
145
|
+
className={cn(
|
|
146
|
+
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0",
|
|
147
|
+
className,
|
|
148
|
+
)}
|
|
149
|
+
{...props}
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupInput, InputGroupTextarea };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input otp. Separate boxes for a one-time code.
|
|
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 { OTPInput, OTPInputContext } from "input-otp";
|
|
14
|
+
import { MinusIcon } from "lucide-react";
|
|
15
|
+
|
|
16
|
+
function InputOTP({
|
|
17
|
+
className,
|
|
18
|
+
containerClassName,
|
|
19
|
+
...props
|
|
20
|
+
}: React.ComponentProps<typeof OTPInput> & {
|
|
21
|
+
containerClassName?: string;
|
|
22
|
+
}) {
|
|
23
|
+
return (
|
|
24
|
+
<OTPInput
|
|
25
|
+
data-slot="input-otp"
|
|
26
|
+
containerClassName={cn("flex items-center gap-2 has-disabled:opacity-50", containerClassName)}
|
|
27
|
+
className={cn("disabled:cursor-not-allowed", className)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
34
|
+
return <div data-slot="input-otp-group" className={cn("flex items-center", className)} {...props} />;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function InputOTPSlot({
|
|
38
|
+
index,
|
|
39
|
+
className,
|
|
40
|
+
...props
|
|
41
|
+
}: React.ComponentProps<"div"> & {
|
|
42
|
+
index: number;
|
|
43
|
+
}) {
|
|
44
|
+
const inputOTPContext = React.useContext(OTPInputContext);
|
|
45
|
+
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div
|
|
49
|
+
data-slot="input-otp-slot"
|
|
50
|
+
data-active={isActive}
|
|
51
|
+
className={cn(
|
|
52
|
+
"relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md aria-invalid:border-destructive data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-[3px] data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:border-destructive data-[active=true]:aria-invalid:ring-destructive/20",
|
|
53
|
+
className,
|
|
54
|
+
)}
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
{char}
|
|
58
|
+
{hasFakeCaret && (
|
|
59
|
+
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
|
60
|
+
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
|
|
68
|
+
return (
|
|
69
|
+
<div data-slot="input-otp-separator" role="separator" {...props}>
|
|
70
|
+
<MinusIcon />
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input. Single-line text entry. Pair it with a Label that stays visible.
|
|
3
|
+
*
|
|
4
|
+
* Reads only component keys: --input-*.
|
|
5
|
+
*/
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { cn } from "../lib/utils";
|
|
8
|
+
|
|
9
|
+
export function Input({ className, type = "text", ...props }: React.InputHTMLAttributes<HTMLInputElement>) {
|
|
10
|
+
return (
|
|
11
|
+
<input
|
|
12
|
+
type={type}
|
|
13
|
+
data-slot="input"
|
|
14
|
+
className={cn(
|
|
15
|
+
"w-full min-w-0 h-[var(--input-height)] px-[var(--input-padding-x)] rounded-[var(--input-radius)]",
|
|
16
|
+
"bg-[var(--input-background)] text-[var(--input-text)] text-[length:var(--input-font-size)] [font-family:var(--type-body-font-family)]",
|
|
17
|
+
"border-[length:var(--border-width-default)] border-[var(--input-border)] placeholder:text-[var(--input-placeholder)]",
|
|
18
|
+
"outline-none focus-visible:border-[var(--input-border-focus)] focus-visible:ring-[length:var(--border-width-focus)] focus-visible:ring-[color:var(--input-border-focus)]",
|
|
19
|
+
"aria-invalid:border-[var(--color-status-danger)] disabled:bg-[var(--input-disabled-background)] disabled:border-[var(--input-disabled-border)] disabled:text-[var(--input-disabled-text)]",
|
|
20
|
+
className,
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kbd. A keyboard key or shortcut.
|
|
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 { cn } from "../lib/utils";
|
|
10
|
+
|
|
11
|
+
function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
|
|
12
|
+
return (
|
|
13
|
+
<kbd
|
|
14
|
+
data-slot="kbd"
|
|
15
|
+
className={cn(
|
|
16
|
+
"pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none",
|
|
17
|
+
"[&_svg:not([class*='size-'])]:size-3",
|
|
18
|
+
"[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background",
|
|
19
|
+
className,
|
|
20
|
+
)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
27
|
+
return <kbd data-slot="kbd-group" className={cn("inline-flex items-center gap-1", className)} {...props} />;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { Kbd, KbdGroup };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Label. Names a field. Keep it visible above the field; never use a placeholder instead.
|
|
3
|
+
*
|
|
4
|
+
* Reads the label text role: --type-label-*.
|
|
5
|
+
*/
|
|
6
|
+
import { Label as LabelPrimitive } from "radix-ui";
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { cn } from "../lib/utils";
|
|
9
|
+
|
|
10
|
+
export function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
11
|
+
return (
|
|
12
|
+
<LabelPrimitive.Root
|
|
13
|
+
data-slot="label"
|
|
14
|
+
className={cn(
|
|
15
|
+
"inline-flex items-center gap-2 text-[var(--color-text-primary)] [font-family:var(--type-label-font-family)]",
|
|
16
|
+
"text-[length:var(--type-label-font-size)] [font-weight:var(--type-label-font-weight)] leading-[var(--type-label-line-height)]",
|
|
17
|
+
className,
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native select. The browser select, styled.
|
|
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 * as React from "react";
|
|
10
|
+
import { cn } from "../lib/utils";
|
|
11
|
+
import { ChevronDownIcon } from "lucide-react";
|
|
12
|
+
|
|
13
|
+
function NativeSelect({
|
|
14
|
+
className,
|
|
15
|
+
size = "default",
|
|
16
|
+
...props
|
|
17
|
+
}: Omit<React.ComponentProps<"select">, "size"> & { size?: "sm" | "default" }) {
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
className="group/native-select relative w-fit has-[select:disabled]:opacity-50"
|
|
21
|
+
data-slot="native-select-wrapper"
|
|
22
|
+
>
|
|
23
|
+
<select
|
|
24
|
+
data-slot="native-select"
|
|
25
|
+
data-size={size}
|
|
26
|
+
className={cn(
|
|
27
|
+
"h-9 w-full min-w-0 appearance-none rounded-md border border-input bg-transparent px-3 py-2 pr-9 text-sm shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed data-[size=sm]:h-8 data-[size=sm]:py-1",
|
|
28
|
+
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
29
|
+
"aria-invalid:border-destructive aria-invalid:ring-destructive/20",
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
<ChevronDownIcon
|
|
35
|
+
className="pointer-events-none absolute top-1/2 right-3.5 size-4 -translate-y-1/2 text-muted-foreground opacity-50 select-none"
|
|
36
|
+
aria-hidden="true"
|
|
37
|
+
data-slot="native-select-icon"
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function NativeSelectOption({ className, ...props }: React.ComponentProps<"option">) {
|
|
44
|
+
return (
|
|
45
|
+
<option data-slot="native-select-option" className={cn("bg-[Canvas] text-[CanvasText]", className)} {...props} />
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function NativeSelectOptGroup({ className, ...props }: React.ComponentProps<"optgroup">) {
|
|
50
|
+
return (
|
|
51
|
+
<optgroup
|
|
52
|
+
data-slot="native-select-optgroup"
|
|
53
|
+
className={cn("bg-[Canvas] text-[CanvasText]", className)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { NativeSelect, NativeSelectOptGroup, NativeSelectOption };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigation menu. Site navigation with links and panels.
|
|
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 * as React from "react";
|
|
10
|
+
import { cva } from "class-variance-authority";
|
|
11
|
+
import { cn } from "../lib/utils";
|
|
12
|
+
import { ChevronDownIcon } from "lucide-react";
|
|
13
|
+
import { NavigationMenu as NavigationMenuPrimitive } from "radix-ui";
|
|
14
|
+
|
|
15
|
+
function NavigationMenu({
|
|
16
|
+
className,
|
|
17
|
+
children,
|
|
18
|
+
viewport = true,
|
|
19
|
+
...props
|
|
20
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
|
|
21
|
+
viewport?: boolean;
|
|
22
|
+
}) {
|
|
23
|
+
return (
|
|
24
|
+
<NavigationMenuPrimitive.Root
|
|
25
|
+
data-slot="navigation-menu"
|
|
26
|
+
data-viewport={viewport}
|
|
27
|
+
className={cn("group/navigation-menu relative flex max-w-max flex-1 items-center justify-center", className)}
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
{viewport && <NavigationMenuViewport />}
|
|
32
|
+
</NavigationMenuPrimitive.Root>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function NavigationMenuList({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
|
|
37
|
+
return (
|
|
38
|
+
<NavigationMenuPrimitive.List
|
|
39
|
+
data-slot="navigation-menu-list"
|
|
40
|
+
className={cn("group flex flex-1 list-none items-center justify-center gap-1", className)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function NavigationMenuItem({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
|
|
47
|
+
return (
|
|
48
|
+
<NavigationMenuPrimitive.Item data-slot="navigation-menu-item" className={cn("relative", className)} {...props} />
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const navigationMenuTriggerStyle = cva(
|
|
53
|
+
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-[color,box-shadow] outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=open]:bg-accent/50 data-[state=open]:text-accent-foreground data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent",
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
function NavigationMenuTrigger({
|
|
57
|
+
className,
|
|
58
|
+
children,
|
|
59
|
+
...props
|
|
60
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
|
|
61
|
+
return (
|
|
62
|
+
<NavigationMenuPrimitive.Trigger
|
|
63
|
+
data-slot="navigation-menu-trigger"
|
|
64
|
+
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
|
65
|
+
{...props}
|
|
66
|
+
>
|
|
67
|
+
{children}{" "}
|
|
68
|
+
<ChevronDownIcon
|
|
69
|
+
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
|
|
70
|
+
aria-hidden="true"
|
|
71
|
+
/>
|
|
72
|
+
</NavigationMenuPrimitive.Trigger>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function NavigationMenuContent({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
|
|
77
|
+
return (
|
|
78
|
+
<NavigationMenuPrimitive.Content
|
|
79
|
+
data-slot="navigation-menu-content"
|
|
80
|
+
className={cn(
|
|
81
|
+
"top-0 left-0 w-full p-2 pr-2.5 data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out md:absolute md:w-auto",
|
|
82
|
+
"group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95",
|
|
83
|
+
className,
|
|
84
|
+
)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function NavigationMenuViewport({
|
|
91
|
+
className,
|
|
92
|
+
...props
|
|
93
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
|
|
94
|
+
return (
|
|
95
|
+
<div className={cn("absolute top-full left-0 isolate z-50 flex justify-center")}>
|
|
96
|
+
<NavigationMenuPrimitive.Viewport
|
|
97
|
+
data-slot="navigation-menu-viewport"
|
|
98
|
+
className={cn(
|
|
99
|
+
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
|
100
|
+
className,
|
|
101
|
+
)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function NavigationMenuLink({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
|
|
109
|
+
return (
|
|
110
|
+
<NavigationMenuPrimitive.Link
|
|
111
|
+
data-slot="navigation-menu-link"
|
|
112
|
+
className={cn(
|
|
113
|
+
"flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground data-[active=true]:hover:bg-accent data-[active=true]:focus:bg-accent [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
|
114
|
+
className,
|
|
115
|
+
)}
|
|
116
|
+
{...props}
|
|
117
|
+
/>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function NavigationMenuIndicator({
|
|
122
|
+
className,
|
|
123
|
+
...props
|
|
124
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
|
|
125
|
+
return (
|
|
126
|
+
<NavigationMenuPrimitive.Indicator
|
|
127
|
+
data-slot="navigation-menu-indicator"
|
|
128
|
+
className={cn(
|
|
129
|
+
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:animate-in data-[state=visible]:fade-in",
|
|
130
|
+
className,
|
|
131
|
+
)}
|
|
132
|
+
{...props}
|
|
133
|
+
>
|
|
134
|
+
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
|
135
|
+
</NavigationMenuPrimitive.Indicator>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export {
|
|
140
|
+
NavigationMenu,
|
|
141
|
+
NavigationMenuList,
|
|
142
|
+
NavigationMenuItem,
|
|
143
|
+
NavigationMenuContent,
|
|
144
|
+
NavigationMenuTrigger,
|
|
145
|
+
NavigationMenuLink,
|
|
146
|
+
NavigationMenuIndicator,
|
|
147
|
+
NavigationMenuViewport,
|
|
148
|
+
navigationMenuTriggerStyle,
|
|
149
|
+
};
|