create-widget 24.1.1-beta.8 → 26.7.21
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 +27 -27
- package/bin/index.js +277 -0
- package/eslint/eslint.config.js +12 -0
- package/package.json +24 -15
- package/readme.md +82 -0
- package/template/react/README.md +41 -0
- package/template/react/components.json +25 -0
- package/template/react/env.d.ts +1 -0
- package/template/react/index.html +13 -0
- package/template/react/package.json +42 -0
- package/template/react/public/apple-touch-icon.png +0 -0
- package/template/react/public/favicon-96x96.png +0 -0
- package/template/react/public/favicon.ico +0 -0
- package/template/react/public/favicon.svg +3 -0
- package/template/react/public/logo.png +0 -0
- package/template/react/public/site.webmanifest +21 -0
- package/template/react/public/web-app-manifest-192x192.png +0 -0
- package/template/react/public/web-app-manifest-512x512.png +0 -0
- package/template/{public → react/public}/widget.json +55 -50
- package/template/react/src/App.tsx +17 -0
- package/template/react/src/assets/main.css +221 -0
- package/template/react/src/components/ui/button.tsx +67 -0
- package/template/react/src/components/ui/card.tsx +103 -0
- package/template/react/src/components/ui/color-picker.tsx +171 -0
- package/template/react/src/components/ui/field.tsx +238 -0
- package/template/react/src/components/ui/input.tsx +19 -0
- package/template/react/src/components/ui/label.tsx +22 -0
- package/template/react/src/components/ui/separator.tsx +28 -0
- package/template/react/src/components/ui/slider.tsx +57 -0
- package/template/react/src/components/ui/switch.tsx +31 -0
- package/template/react/src/components/ui/tabs.tsx +88 -0
- package/template/react/src/lib/utils.ts +7 -0
- package/template/react/src/main.tsx +20 -0
- package/template/react/src/pages/home-page.tsx +66 -0
- package/template/react/src/widgets/clock/Clock.widget.ts +21 -0
- package/template/react/src/widgets/clock/ClockConfigView.tsx +112 -0
- package/template/react/src/widgets/clock/ClockWidgetRoutes.ts +25 -0
- package/template/react/src/widgets/clock/ClockWidgetView.tsx +104 -0
- package/template/react/src/widgets/widget-router.ts +11 -0
- package/template/react/tsconfig.app.json +26 -0
- package/template/react/tsconfig.json +17 -0
- package/template/react/tsconfig.node.json +23 -0
- package/template/react/vite.config.ts +27 -0
- package/template/{widget.package.ts → react/widget.package.ts} +5 -1
- package/template/{.vscode → vue/.vscode}/extensions.json +3 -3
- package/template/vue/README.md +34 -0
- package/template/{env.d.ts → vue/env.d.ts} +1 -1
- package/template/{index.html → vue/index.html} +13 -13
- package/template/vue/package.json +34 -0
- package/template/vue/public/apple-touch-icon.png +0 -0
- package/template/vue/public/favicon-96x96.png +0 -0
- package/template/vue/public/favicon.ico +0 -0
- package/template/vue/public/favicon.svg +3 -0
- package/template/vue/public/logo.png +0 -0
- package/template/vue/public/preview_clock.png +0 -0
- package/template/vue/public/site.webmanifest +21 -0
- package/template/vue/public/web-app-manifest-192x192.png +0 -0
- package/template/vue/public/web-app-manifest-512x512.png +0 -0
- package/template/{src → vue/src}/App.vue +9 -9
- package/template/vue/src/Home.vue +10 -0
- package/template/{src → vue/src}/main.ts +13 -12
- package/template/{src → vue/src}/router/index.ts +4 -0
- package/template/{src → vue/src}/widgets/clock/Clock.widget.ts +21 -21
- package/template/{src → vue/src}/widgets/clock/ClockConfigView.vue +37 -38
- package/template/{src → vue/src}/widgets/clock/ClockWidgetRoutes.ts +28 -28
- package/template/{src → vue/src}/widgets/clock/ClockWidgetView.vue +38 -35
- package/template/{src → vue/src}/widgets/widget-router.ts +3 -3
- package/template/{tsconfig.app.json → vue/tsconfig.app.json} +13 -13
- package/template/{tsconfig.json → vue/tsconfig.json} +11 -11
- package/template/{tsconfig.node.json → vue/tsconfig.node.json} +17 -17
- package/template/vue/vite.config.ts +29 -0
- package/template/vue/widget.package.ts +24 -0
- package/unocss/src/main.ts +13 -0
- package/unocss/uno.config.ts +5 -0
- package/unocss/vite.config.ts +31 -0
- package/index.cjs +0 -7913
- package/template/README.md +0 -40
- package/template/package.json +0 -29
- package/template/public/favicon.ico +0 -0
- package/template/src/widgets/clock/model/ClockModel.ts +0 -5
- package/template/vite.config.ts +0 -15
- /package/template/{public → react/public}/preview_clock.png +0 -0
- /package/template/{src → vue/src}/assets/main.css +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
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-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 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-3 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-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg 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-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg 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":
|
|
31
|
+
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
32
|
+
"icon-sm":
|
|
33
|
+
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
|
34
|
+
"icon-lg": "size-9",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
defaultVariants: {
|
|
38
|
+
variant: "default",
|
|
39
|
+
size: "default",
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
function Button({
|
|
45
|
+
className,
|
|
46
|
+
variant = "default",
|
|
47
|
+
size = "default",
|
|
48
|
+
asChild = false,
|
|
49
|
+
...props
|
|
50
|
+
}: React.ComponentProps<"button"> &
|
|
51
|
+
VariantProps<typeof buttonVariants> & {
|
|
52
|
+
asChild?: boolean
|
|
53
|
+
}) {
|
|
54
|
+
const Comp = asChild ? Slot.Root : "button"
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Comp
|
|
58
|
+
data-slot="button"
|
|
59
|
+
data-variant={variant}
|
|
60
|
+
data-size={size}
|
|
61
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
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-xl bg-card py-(--card-spacing) text-sm 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-t-xl *:[img:last-child]:rounded-b-xl",
|
|
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-t-xl 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-base leading-snug 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-sm 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-b-xl border-t bg-muted/50 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,171 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
import { Input } from "./input"
|
|
6
|
+
|
|
7
|
+
const FALLBACK_COLOR = "#000000"
|
|
8
|
+
const DEFAULT_PRESETS = [
|
|
9
|
+
"#000000",
|
|
10
|
+
"#ffffff",
|
|
11
|
+
"#ef4444",
|
|
12
|
+
"#f97316",
|
|
13
|
+
"#eab308",
|
|
14
|
+
"#22c55e",
|
|
15
|
+
"#06b6d4",
|
|
16
|
+
"#3b82f6",
|
|
17
|
+
"#8b5cf6",
|
|
18
|
+
"#ec4899",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
const HEX_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
|
|
22
|
+
|
|
23
|
+
function normalizeHexColor(value: string | undefined, fallback = FALLBACK_COLOR) {
|
|
24
|
+
const trimmed = value?.trim()
|
|
25
|
+
|
|
26
|
+
if (!trimmed || !HEX_COLOR_PATTERN.test(trimmed)) {
|
|
27
|
+
return fallback
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (trimmed.length === 4) {
|
|
31
|
+
const [, r, g, b] = trimmed
|
|
32
|
+
return `#${r}${r}${g}${g}${b}${b}`.toLowerCase()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return trimmed.toLowerCase()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isHexColor(value: string) {
|
|
39
|
+
return HEX_COLOR_PATTERN.test(value.trim())
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ColorPickerProps extends Omit<React.ComponentProps<"input">, "type" | "value" | "defaultValue" | "onChange"> {
|
|
43
|
+
value?: string
|
|
44
|
+
defaultValue?: string
|
|
45
|
+
onValueChange?: (value: string) => void
|
|
46
|
+
presets?: string[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function ColorPicker({
|
|
50
|
+
className,
|
|
51
|
+
value,
|
|
52
|
+
defaultValue,
|
|
53
|
+
onValueChange,
|
|
54
|
+
presets = DEFAULT_PRESETS,
|
|
55
|
+
disabled,
|
|
56
|
+
...props
|
|
57
|
+
}: ColorPickerProps) {
|
|
58
|
+
const isControlled = value !== undefined
|
|
59
|
+
const [internalValue, setInternalValue] = React.useState(() => normalizeHexColor(defaultValue))
|
|
60
|
+
const currentValue = isControlled ? normalizeHexColor(value, internalValue) : internalValue
|
|
61
|
+
const [textValue, setTextValue] = React.useState(currentValue)
|
|
62
|
+
|
|
63
|
+
React.useEffect(() => {
|
|
64
|
+
setTextValue(currentValue)
|
|
65
|
+
}, [currentValue])
|
|
66
|
+
|
|
67
|
+
const commitValue = React.useCallback((nextValue: string) => {
|
|
68
|
+
const normalizedValue = normalizeHexColor(nextValue, currentValue)
|
|
69
|
+
|
|
70
|
+
if (!isControlled) {
|
|
71
|
+
setInternalValue(normalizedValue)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
onValueChange?.(normalizedValue)
|
|
75
|
+
}, [currentValue, isControlled, onValueChange])
|
|
76
|
+
|
|
77
|
+
const handleTextBlur = React.useCallback(() => {
|
|
78
|
+
if (isHexColor(textValue)) {
|
|
79
|
+
commitValue(textValue)
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setTextValue(currentValue)
|
|
84
|
+
}, [commitValue, currentValue, textValue])
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div
|
|
88
|
+
data-slot="color-picker"
|
|
89
|
+
className={cn("flex flex-col gap-3", className)}
|
|
90
|
+
>
|
|
91
|
+
<div className="flex items-center gap-3">
|
|
92
|
+
<label
|
|
93
|
+
className={cn(
|
|
94
|
+
"relative inline-flex size-8 shrink-0 overflow-hidden rounded-lg border border-input bg-background shadow-xs transition-opacity",
|
|
95
|
+
disabled && "opacity-50"
|
|
96
|
+
)}
|
|
97
|
+
>
|
|
98
|
+
<span
|
|
99
|
+
className="pointer-events-none absolute inset-[3px] rounded-md border border-white/20"
|
|
100
|
+
style={{ backgroundColor: currentValue }}
|
|
101
|
+
/>
|
|
102
|
+
<input
|
|
103
|
+
aria-label="Pick a color"
|
|
104
|
+
className="absolute inset-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed"
|
|
105
|
+
disabled={disabled}
|
|
106
|
+
type="color"
|
|
107
|
+
value={currentValue}
|
|
108
|
+
onChange={event => commitValue(event.target.value)}
|
|
109
|
+
/>
|
|
110
|
+
</label>
|
|
111
|
+
|
|
112
|
+
<Input
|
|
113
|
+
autoCapitalize="off"
|
|
114
|
+
autoComplete="off"
|
|
115
|
+
className="font-mono uppercase"
|
|
116
|
+
disabled={disabled}
|
|
117
|
+
spellCheck={false}
|
|
118
|
+
value={textValue}
|
|
119
|
+
onBlur={handleTextBlur}
|
|
120
|
+
onChange={event => setTextValue(event.target.value)}
|
|
121
|
+
onKeyDown={event => {
|
|
122
|
+
if (event.key !== "Enter") {
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
event.preventDefault()
|
|
127
|
+
|
|
128
|
+
if (isHexColor(textValue)) {
|
|
129
|
+
commitValue(textValue)
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
setTextValue(currentValue)
|
|
134
|
+
}}
|
|
135
|
+
{...props}
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
{presets.length > 0 && (
|
|
140
|
+
<div className="flex flex-wrap gap-2">
|
|
141
|
+
{presets.map(color => {
|
|
142
|
+
const normalizedColor = normalizeHexColor(color, color)
|
|
143
|
+
const isSelected = normalizedColor === currentValue
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<button
|
|
147
|
+
key={normalizedColor}
|
|
148
|
+
aria-label={`Use ${normalizedColor}`}
|
|
149
|
+
aria-pressed={isSelected}
|
|
150
|
+
className={cn(
|
|
151
|
+
"inline-flex size-6 shrink-0 items-center justify-center rounded-md border border-input bg-background p-0.5 transition-all hover:scale-[1.04] focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50",
|
|
152
|
+
isSelected && "border-ring ring-2 ring-ring/30"
|
|
153
|
+
)}
|
|
154
|
+
disabled={disabled}
|
|
155
|
+
type="button"
|
|
156
|
+
onClick={() => commitValue(normalizedColor)}
|
|
157
|
+
>
|
|
158
|
+
<span
|
|
159
|
+
className="size-full rounded-[calc(var(--radius-sm)-2px)] border border-white/20"
|
|
160
|
+
style={{ backgroundColor: normalizedColor }}
|
|
161
|
+
/>
|
|
162
|
+
</button>
|
|
163
|
+
)
|
|
164
|
+
})}
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export { ColorPicker }
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react"
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
import { Label } from "@/components/ui/label"
|
|
8
|
+
import { Separator } from "@/components/ui/separator"
|
|
9
|
+
|
|
10
|
+
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
|
11
|
+
return (
|
|
12
|
+
<fieldset
|
|
13
|
+
data-slot="field-set"
|
|
14
|
+
className={cn(
|
|
15
|
+
"flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function FieldLegend({
|
|
24
|
+
className,
|
|
25
|
+
variant = "legend",
|
|
26
|
+
...props
|
|
27
|
+
}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
|
|
28
|
+
return (
|
|
29
|
+
<legend
|
|
30
|
+
data-slot="field-legend"
|
|
31
|
+
data-variant={variant}
|
|
32
|
+
className={cn(
|
|
33
|
+
"mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
|
|
34
|
+
className
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
data-slot="field-group"
|
|
45
|
+
className={cn(
|
|
46
|
+
"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",
|
|
47
|
+
className
|
|
48
|
+
)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const fieldVariants = cva(
|
|
55
|
+
"group/field flex w-full gap-2 data-[invalid=true]:text-destructive",
|
|
56
|
+
{
|
|
57
|
+
variants: {
|
|
58
|
+
orientation: {
|
|
59
|
+
vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
|
|
60
|
+
horizontal:
|
|
61
|
+
"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",
|
|
62
|
+
responsive:
|
|
63
|
+
"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",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
defaultVariants: {
|
|
67
|
+
orientation: "vertical",
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
function Field({
|
|
73
|
+
className,
|
|
74
|
+
orientation = "vertical",
|
|
75
|
+
...props
|
|
76
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
|
|
77
|
+
return (
|
|
78
|
+
<div
|
|
79
|
+
role="group"
|
|
80
|
+
data-slot="field"
|
|
81
|
+
data-orientation={orientation}
|
|
82
|
+
className={cn(fieldVariants({ orientation }), className)}
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
89
|
+
return (
|
|
90
|
+
<div
|
|
91
|
+
data-slot="field-content"
|
|
92
|
+
className={cn(
|
|
93
|
+
"group/field-content flex flex-1 flex-col gap-0.5 leading-snug",
|
|
94
|
+
className
|
|
95
|
+
)}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function FieldLabel({
|
|
102
|
+
className,
|
|
103
|
+
...props
|
|
104
|
+
}: React.ComponentProps<typeof Label>) {
|
|
105
|
+
return (
|
|
106
|
+
<Label
|
|
107
|
+
data-slot="field-label"
|
|
108
|
+
className={cn(
|
|
109
|
+
"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-lg has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",
|
|
110
|
+
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
|
|
111
|
+
className
|
|
112
|
+
)}
|
|
113
|
+
{...props}
|
|
114
|
+
/>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
119
|
+
return (
|
|
120
|
+
<div
|
|
121
|
+
data-slot="field-label"
|
|
122
|
+
className={cn(
|
|
123
|
+
"flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50",
|
|
124
|
+
className
|
|
125
|
+
)}
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
132
|
+
return (
|
|
133
|
+
<p
|
|
134
|
+
data-slot="field-description"
|
|
135
|
+
className={cn(
|
|
136
|
+
"text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
|
|
137
|
+
"last:mt-0 nth-last-2:-mt-1",
|
|
138
|
+
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
139
|
+
className
|
|
140
|
+
)}
|
|
141
|
+
{...props}
|
|
142
|
+
/>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function FieldSeparator({
|
|
147
|
+
children,
|
|
148
|
+
className,
|
|
149
|
+
...props
|
|
150
|
+
}: React.ComponentProps<"div"> & {
|
|
151
|
+
children?: React.ReactNode
|
|
152
|
+
}) {
|
|
153
|
+
return (
|
|
154
|
+
<div
|
|
155
|
+
data-slot="field-separator"
|
|
156
|
+
data-content={!!children}
|
|
157
|
+
className={cn(
|
|
158
|
+
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
|
|
159
|
+
className
|
|
160
|
+
)}
|
|
161
|
+
{...props}
|
|
162
|
+
>
|
|
163
|
+
<Separator className="absolute inset-0 top-1/2" />
|
|
164
|
+
{children && (
|
|
165
|
+
<span
|
|
166
|
+
className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
|
|
167
|
+
data-slot="field-separator-content"
|
|
168
|
+
>
|
|
169
|
+
{children}
|
|
170
|
+
</span>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function FieldError({
|
|
177
|
+
className,
|
|
178
|
+
children,
|
|
179
|
+
errors,
|
|
180
|
+
...props
|
|
181
|
+
}: React.ComponentProps<"div"> & {
|
|
182
|
+
errors?: Array<{ message?: string } | undefined>
|
|
183
|
+
}) {
|
|
184
|
+
const content = useMemo(() => {
|
|
185
|
+
if (children) {
|
|
186
|
+
return children
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!errors?.length) {
|
|
190
|
+
return null
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const uniqueErrors = [
|
|
194
|
+
...new Map(errors.map((error) => [error?.message, error])).values(),
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
if (uniqueErrors?.length == 1) {
|
|
198
|
+
return uniqueErrors[0]?.message
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<ul className="ml-4 flex list-disc flex-col gap-1">
|
|
203
|
+
{uniqueErrors.map(
|
|
204
|
+
(error, index) =>
|
|
205
|
+
error?.message && <li key={index}>{error.message}</li>
|
|
206
|
+
)}
|
|
207
|
+
</ul>
|
|
208
|
+
)
|
|
209
|
+
}, [children, errors])
|
|
210
|
+
|
|
211
|
+
if (!content) {
|
|
212
|
+
return null
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<div
|
|
217
|
+
role="alert"
|
|
218
|
+
data-slot="field-error"
|
|
219
|
+
className={cn("text-sm font-normal text-destructive", className)}
|
|
220
|
+
{...props}
|
|
221
|
+
>
|
|
222
|
+
{content}
|
|
223
|
+
</div>
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export {
|
|
228
|
+
Field,
|
|
229
|
+
FieldLabel,
|
|
230
|
+
FieldDescription,
|
|
231
|
+
FieldError,
|
|
232
|
+
FieldGroup,
|
|
233
|
+
FieldLegend,
|
|
234
|
+
FieldSeparator,
|
|
235
|
+
FieldSet,
|
|
236
|
+
FieldContent,
|
|
237
|
+
FieldTitle,
|
|
238
|
+
}
|
|
@@ -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-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 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-3 aria-invalid:ring-destructive/20 md:text-sm 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-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { Label }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Separator as SeparatorPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
function Separator({
|
|
9
|
+
className,
|
|
10
|
+
orientation = "horizontal",
|
|
11
|
+
decorative = true,
|
|
12
|
+
...props
|
|
13
|
+
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
|
14
|
+
return (
|
|
15
|
+
<SeparatorPrimitive.Root
|
|
16
|
+
data-slot="separator"
|
|
17
|
+
decorative={decorative}
|
|
18
|
+
orientation={orientation}
|
|
19
|
+
className={cn(
|
|
20
|
+
"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { Separator }
|