@montytools/cli 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/bin/catalog.mjs +75 -0
- package/bin/monty.mjs +397 -0
- package/package.json +25 -0
- package/template/.env.example +3 -0
- package/template/AGENTS.md +133 -0
- package/template/components.json +28 -0
- package/template/index.html +12 -0
- package/template/monty.config.ts +20 -0
- package/template/package.json +37 -0
- package/template/src/components/ui/badge.tsx +49 -0
- package/template/src/components/ui/button.tsx +65 -0
- package/template/src/components/ui/card.tsx +103 -0
- package/template/src/components/ui/empty.tsx +101 -0
- package/template/src/components/ui/field.tsx +236 -0
- package/template/src/components/ui/input.tsx +19 -0
- package/template/src/components/ui/label.tsx +22 -0
- package/template/src/components/ui/select.tsx +192 -0
- package/template/src/components/ui/separator.tsx +28 -0
- package/template/src/components/ui/spinner.tsx +10 -0
- package/template/src/components/ui/table.tsx +114 -0
- package/template/src/index.css +134 -0
- package/template/src/lib/utils.ts +6 -0
- package/template/src/main.tsx +24 -0
- package/template/src/routes/__root.tsx +5 -0
- package/template/src/routes/index.tsx +263 -0
- package/template/src/vite-env.d.ts +1 -0
- package/template/tsconfig.json +22 -0
- package/template/vite.config.ts +16 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineApp } from "@montytools/sdk";
|
|
3
|
+
|
|
4
|
+
// This file is the ONLY place data shapes are defined. Edit it, save, and the
|
|
5
|
+
// typed hooks in your components update immediately.
|
|
6
|
+
export const app = defineApp({
|
|
7
|
+
slug: "expenses",
|
|
8
|
+
name: "Expenses",
|
|
9
|
+
icon: "receipt",
|
|
10
|
+
tables: {
|
|
11
|
+
expenses: z.object({
|
|
12
|
+
title: z.string().min(1),
|
|
13
|
+
amount: z.number().positive(),
|
|
14
|
+
status: z.enum(["draft", "submitted", "approved"]).default("draft"),
|
|
15
|
+
assigneeId: z.string().optional(), // a workspace member's userId (from useMembers)
|
|
16
|
+
}),
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export type App = typeof app;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@monty/template",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite dev --port 5173",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"typecheck": "tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@fontsource-variable/roboto": "^5.2.10",
|
|
13
|
+
"@montytools/sdk": "^0.1.0",
|
|
14
|
+
"@tanstack/react-router": "1.170.17",
|
|
15
|
+
"class-variance-authority": "^0.7.1",
|
|
16
|
+
"clsx": "^2.1.1",
|
|
17
|
+
"lucide-react": "^1.23.0",
|
|
18
|
+
"radix-ui": "^1.6.1",
|
|
19
|
+
"react": "19.2.7",
|
|
20
|
+
"react-dom": "19.2.7",
|
|
21
|
+
"tailwind-merge": "^3.6.0",
|
|
22
|
+
"zod": "^4.4.3"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@tailwindcss/vite": "^4.3.2",
|
|
26
|
+
"@tanstack/router-plugin": "1.168.19",
|
|
27
|
+
"@types/node": "22.20.0",
|
|
28
|
+
"@types/react": "19.2.17",
|
|
29
|
+
"@types/react-dom": "19.2.3",
|
|
30
|
+
"@vitejs/plugin-react": "6.0.3",
|
|
31
|
+
"shadcn": "^4.12.0",
|
|
32
|
+
"tailwindcss": "^4.3.2",
|
|
33
|
+
"tw-animate-css": "^1.4.0",
|
|
34
|
+
"typescript": "6.0.3",
|
|
35
|
+
"vite": "8.1.3"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { Slot } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
const badgeVariants = cva(
|
|
8
|
+
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-none border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
13
|
+
secondary:
|
|
14
|
+
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
|
15
|
+
destructive:
|
|
16
|
+
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
|
|
17
|
+
outline:
|
|
18
|
+
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
|
19
|
+
ghost:
|
|
20
|
+
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
|
21
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
variant: "default",
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
function Badge({
|
|
31
|
+
className,
|
|
32
|
+
variant = "default",
|
|
33
|
+
asChild = false,
|
|
34
|
+
...props
|
|
35
|
+
}: React.ComponentProps<"span"> &
|
|
36
|
+
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
|
37
|
+
const Comp = asChild ? Slot.Root : "span"
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Comp
|
|
41
|
+
data-slot="badge"
|
|
42
|
+
data-variant={variant}
|
|
43
|
+
className={cn(badgeVariants({ variant }), className)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { Badge, badgeVariants }
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { Slot } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
const buttonVariants = cva(
|
|
8
|
+
"group/button inline-flex shrink-0 items-center justify-center rounded-none border border-transparent bg-clip-padding text-xs font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
|
13
|
+
outline:
|
|
14
|
+
"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
15
|
+
secondary:
|
|
16
|
+
"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
17
|
+
ghost:
|
|
18
|
+
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
19
|
+
destructive:
|
|
20
|
+
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
21
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
22
|
+
},
|
|
23
|
+
size: {
|
|
24
|
+
default:
|
|
25
|
+
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
26
|
+
xs: "h-6 gap-1 rounded-none px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
27
|
+
sm: "h-7 gap-1 rounded-none px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
28
|
+
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
29
|
+
icon: "size-8",
|
|
30
|
+
"icon-xs": "size-6 rounded-none [&_svg:not([class*='size-'])]:size-3",
|
|
31
|
+
"icon-sm": "size-7 rounded-none",
|
|
32
|
+
"icon-lg": "size-9",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
variant: "default",
|
|
37
|
+
size: "default",
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
function Button({
|
|
43
|
+
className,
|
|
44
|
+
variant = "default",
|
|
45
|
+
size = "default",
|
|
46
|
+
asChild = false,
|
|
47
|
+
...props
|
|
48
|
+
}: React.ComponentProps<"button"> &
|
|
49
|
+
VariantProps<typeof buttonVariants> & {
|
|
50
|
+
asChild?: boolean
|
|
51
|
+
}) {
|
|
52
|
+
const Comp = asChild ? Slot.Root : "button"
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Comp
|
|
56
|
+
data-slot="button"
|
|
57
|
+
data-variant={variant}
|
|
58
|
+
data-size={size}
|
|
59
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { Button, buttonVariants }
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
function Card({
|
|
6
|
+
className,
|
|
7
|
+
size = "default",
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
data-slot="card"
|
|
13
|
+
data-size={size}
|
|
14
|
+
className={cn(
|
|
15
|
+
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-none bg-card py-(--card-spacing) text-xs/relaxed text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none",
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
data-slot="card-header"
|
|
27
|
+
className={cn(
|
|
28
|
+
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-none px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
data-slot="card-title"
|
|
40
|
+
className={cn(
|
|
41
|
+
"font-heading text-sm font-medium group-data-[size=sm]/card:text-sm",
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
data-slot="card-description"
|
|
53
|
+
className={cn("text-xs/relaxed text-muted-foreground", className)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
60
|
+
return (
|
|
61
|
+
<div
|
|
62
|
+
data-slot="card-action"
|
|
63
|
+
className={cn(
|
|
64
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
65
|
+
className
|
|
66
|
+
)}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
data-slot="card-content"
|
|
76
|
+
className={cn("px-(--card-spacing)", className)}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
data-slot="card-footer"
|
|
86
|
+
className={cn(
|
|
87
|
+
"flex items-center rounded-none border-t p-(--card-spacing)",
|
|
88
|
+
className
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
Card,
|
|
97
|
+
CardHeader,
|
|
98
|
+
CardFooter,
|
|
99
|
+
CardTitle,
|
|
100
|
+
CardAction,
|
|
101
|
+
CardDescription,
|
|
102
|
+
CardContent,
|
|
103
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="empty"
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-none border-dashed p-6 text-center text-balance",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
data-slot="empty-header"
|
|
22
|
+
className={cn("flex max-w-sm flex-col items-center gap-2", className)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const emptyMediaVariants = cva(
|
|
29
|
+
"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
30
|
+
{
|
|
31
|
+
variants: {
|
|
32
|
+
variant: {
|
|
33
|
+
default: "bg-transparent",
|
|
34
|
+
icon: "flex size-8 shrink-0 items-center justify-center rounded-none bg-muted text-foreground [&_svg:not([class*='size-'])]:size-4",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
defaultVariants: {
|
|
38
|
+
variant: "default",
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
function EmptyMedia({
|
|
44
|
+
className,
|
|
45
|
+
variant = "default",
|
|
46
|
+
...props
|
|
47
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
data-slot="empty-icon"
|
|
51
|
+
data-variant={variant}
|
|
52
|
+
className={cn(emptyMediaVariants({ variant, className }))}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
data-slot="empty-title"
|
|
62
|
+
className={cn("font-heading text-sm font-medium", className)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
data-slot="empty-description"
|
|
72
|
+
className={cn(
|
|
73
|
+
"text-xs/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
74
|
+
className
|
|
75
|
+
)}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
82
|
+
return (
|
|
83
|
+
<div
|
|
84
|
+
data-slot="empty-content"
|
|
85
|
+
className={cn(
|
|
86
|
+
"flex w-full max-w-sm min-w-0 flex-col items-center gap-2.5 text-xs text-balance",
|
|
87
|
+
className
|
|
88
|
+
)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
Empty,
|
|
96
|
+
EmptyHeader,
|
|
97
|
+
EmptyTitle,
|
|
98
|
+
EmptyDescription,
|
|
99
|
+
EmptyContent,
|
|
100
|
+
EmptyMedia,
|
|
101
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { useMemo } from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
import { Label } from "@/components/ui/label"
|
|
6
|
+
import { Separator } from "@/components/ui/separator"
|
|
7
|
+
|
|
8
|
+
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
|
9
|
+
return (
|
|
10
|
+
<fieldset
|
|
11
|
+
data-slot="field-set"
|
|
12
|
+
className={cn(
|
|
13
|
+
"flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
|
|
14
|
+
className
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function FieldLegend({
|
|
22
|
+
className,
|
|
23
|
+
variant = "legend",
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
|
|
26
|
+
return (
|
|
27
|
+
<legend
|
|
28
|
+
data-slot="field-legend"
|
|
29
|
+
data-variant={variant}
|
|
30
|
+
className={cn(
|
|
31
|
+
"mb-2.5 font-medium data-[variant=label]:text-xs data-[variant=legend]:text-sm",
|
|
32
|
+
className
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
data-slot="field-group"
|
|
43
|
+
className={cn(
|
|
44
|
+
"group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
|
|
45
|
+
className
|
|
46
|
+
)}
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const fieldVariants = cva(
|
|
53
|
+
"group/field flex w-full gap-2 data-[invalid=true]:text-destructive",
|
|
54
|
+
{
|
|
55
|
+
variants: {
|
|
56
|
+
orientation: {
|
|
57
|
+
vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
|
|
58
|
+
horizontal:
|
|
59
|
+
"flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
60
|
+
responsive:
|
|
61
|
+
"flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
defaultVariants: {
|
|
65
|
+
orientation: "vertical",
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
function Field({
|
|
71
|
+
className,
|
|
72
|
+
orientation = "vertical",
|
|
73
|
+
...props
|
|
74
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
role="group"
|
|
78
|
+
data-slot="field"
|
|
79
|
+
data-orientation={orientation}
|
|
80
|
+
className={cn(fieldVariants({ orientation }), className)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
87
|
+
return (
|
|
88
|
+
<div
|
|
89
|
+
data-slot="field-content"
|
|
90
|
+
className={cn(
|
|
91
|
+
"group/field-content flex flex-1 flex-col gap-0.5 leading-snug",
|
|
92
|
+
className
|
|
93
|
+
)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function FieldLabel({
|
|
100
|
+
className,
|
|
101
|
+
...props
|
|
102
|
+
}: React.ComponentProps<typeof Label>) {
|
|
103
|
+
return (
|
|
104
|
+
<Label
|
|
105
|
+
data-slot="field-label"
|
|
106
|
+
className={cn(
|
|
107
|
+
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-none has-[>[data-slot=field]]:border *:data-[slot=field]:p-2 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",
|
|
108
|
+
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
|
|
109
|
+
className
|
|
110
|
+
)}
|
|
111
|
+
{...props}
|
|
112
|
+
/>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
117
|
+
return (
|
|
118
|
+
<div
|
|
119
|
+
data-slot="field-label"
|
|
120
|
+
className={cn(
|
|
121
|
+
"flex w-fit items-center gap-2 text-xs/relaxed group-data-[disabled=true]/field:opacity-50",
|
|
122
|
+
className
|
|
123
|
+
)}
|
|
124
|
+
{...props}
|
|
125
|
+
/>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
130
|
+
return (
|
|
131
|
+
<p
|
|
132
|
+
data-slot="field-description"
|
|
133
|
+
className={cn(
|
|
134
|
+
"text-left text-xs/relaxed leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
|
|
135
|
+
"last:mt-0 nth-last-2:-mt-1",
|
|
136
|
+
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
137
|
+
className
|
|
138
|
+
)}
|
|
139
|
+
{...props}
|
|
140
|
+
/>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function FieldSeparator({
|
|
145
|
+
children,
|
|
146
|
+
className,
|
|
147
|
+
...props
|
|
148
|
+
}: React.ComponentProps<"div"> & {
|
|
149
|
+
children?: React.ReactNode
|
|
150
|
+
}) {
|
|
151
|
+
return (
|
|
152
|
+
<div
|
|
153
|
+
data-slot="field-separator"
|
|
154
|
+
data-content={!!children}
|
|
155
|
+
className={cn(
|
|
156
|
+
"relative -my-2 h-5 text-xs group-data-[variant=outline]/field-group:-mb-2",
|
|
157
|
+
className
|
|
158
|
+
)}
|
|
159
|
+
{...props}
|
|
160
|
+
>
|
|
161
|
+
<Separator className="absolute inset-0 top-1/2" />
|
|
162
|
+
{children && (
|
|
163
|
+
<span
|
|
164
|
+
className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
|
|
165
|
+
data-slot="field-separator-content"
|
|
166
|
+
>
|
|
167
|
+
{children}
|
|
168
|
+
</span>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function FieldError({
|
|
175
|
+
className,
|
|
176
|
+
children,
|
|
177
|
+
errors,
|
|
178
|
+
...props
|
|
179
|
+
}: React.ComponentProps<"div"> & {
|
|
180
|
+
errors?: Array<{ message?: string } | undefined>
|
|
181
|
+
}) {
|
|
182
|
+
const content = useMemo(() => {
|
|
183
|
+
if (children) {
|
|
184
|
+
return children
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!errors?.length) {
|
|
188
|
+
return null
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const uniqueErrors = [
|
|
192
|
+
...new Map(errors.map((error) => [error?.message, error])).values(),
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
if (uniqueErrors?.length == 1) {
|
|
196
|
+
return uniqueErrors[0]?.message
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<ul className="ml-4 flex list-disc flex-col gap-1">
|
|
201
|
+
{uniqueErrors.map(
|
|
202
|
+
(error, index) =>
|
|
203
|
+
error?.message && <li key={index}>{error.message}</li>
|
|
204
|
+
)}
|
|
205
|
+
</ul>
|
|
206
|
+
)
|
|
207
|
+
}, [children, errors])
|
|
208
|
+
|
|
209
|
+
if (!content) {
|
|
210
|
+
return null
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<div
|
|
215
|
+
role="alert"
|
|
216
|
+
data-slot="field-error"
|
|
217
|
+
className={cn("text-xs font-normal text-destructive", className)}
|
|
218
|
+
{...props}
|
|
219
|
+
>
|
|
220
|
+
{content}
|
|
221
|
+
</div>
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export {
|
|
226
|
+
Field,
|
|
227
|
+
FieldLabel,
|
|
228
|
+
FieldDescription,
|
|
229
|
+
FieldError,
|
|
230
|
+
FieldGroup,
|
|
231
|
+
FieldLegend,
|
|
232
|
+
FieldSeparator,
|
|
233
|
+
FieldSet,
|
|
234
|
+
FieldContent,
|
|
235
|
+
FieldTitle,
|
|
236
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
6
|
+
return (
|
|
7
|
+
<input
|
|
8
|
+
type={type}
|
|
9
|
+
data-slot="input"
|
|
10
|
+
className={cn(
|
|
11
|
+
"h-8 w-full min-w-0 rounded-none border border-input bg-transparent px-2.5 py-1 text-xs transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-xs file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 md:text-xs dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { Input }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Label as LabelPrimitive } from "radix-ui"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
|
|
6
|
+
function Label({
|
|
7
|
+
className,
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
10
|
+
return (
|
|
11
|
+
<LabelPrimitive.Root
|
|
12
|
+
data-slot="label"
|
|
13
|
+
className={cn(
|
|
14
|
+
"flex items-center gap-2 text-xs leading-none select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { Label }
|