create-widget 24.1.1-beta.9 → 26.7.22

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.
Files changed (83) hide show
  1. package/LICENSE +27 -27
  2. package/bin/index.js +216 -0
  3. package/eslint/eslint.config.js +12 -0
  4. package/package.json +24 -15
  5. package/readme.md +80 -0
  6. package/template/react/README.md +41 -0
  7. package/template/react/components.json +25 -0
  8. package/template/react/env.d.ts +1 -0
  9. package/template/react/index.html +13 -0
  10. package/template/react/package.json +42 -0
  11. package/template/react/public/apple-touch-icon.png +0 -0
  12. package/template/react/public/favicon-96x96.png +0 -0
  13. package/template/react/public/favicon.ico +0 -0
  14. package/template/react/public/favicon.svg +3 -0
  15. package/template/react/public/logo.png +0 -0
  16. package/template/react/public/site.webmanifest +21 -0
  17. package/template/react/public/web-app-manifest-192x192.png +0 -0
  18. package/template/react/public/web-app-manifest-512x512.png +0 -0
  19. package/template/{public → react/public}/widget.json +55 -50
  20. package/template/react/src/App.tsx +17 -0
  21. package/template/react/src/assets/main.css +221 -0
  22. package/template/react/src/components/ui/button.tsx +67 -0
  23. package/template/react/src/components/ui/card.tsx +103 -0
  24. package/template/react/src/components/ui/color-picker.tsx +171 -0
  25. package/template/react/src/components/ui/field.tsx +238 -0
  26. package/template/react/src/components/ui/input.tsx +19 -0
  27. package/template/react/src/components/ui/label.tsx +22 -0
  28. package/template/react/src/components/ui/separator.tsx +28 -0
  29. package/template/react/src/components/ui/slider.tsx +57 -0
  30. package/template/react/src/components/ui/switch.tsx +31 -0
  31. package/template/react/src/components/ui/tabs.tsx +88 -0
  32. package/template/react/src/lib/utils.ts +7 -0
  33. package/template/react/src/main.tsx +20 -0
  34. package/template/react/src/pages/home-page.tsx +66 -0
  35. package/template/react/src/widgets/clock/Clock.widget.ts +21 -0
  36. package/template/react/src/widgets/clock/ClockConfigView.tsx +112 -0
  37. package/template/react/src/widgets/clock/ClockWidgetRoutes.ts +25 -0
  38. package/template/react/src/widgets/clock/ClockWidgetView.tsx +104 -0
  39. package/template/react/src/widgets/widget-router.ts +11 -0
  40. package/template/react/tsconfig.app.json +26 -0
  41. package/template/react/tsconfig.json +17 -0
  42. package/template/react/tsconfig.node.json +23 -0
  43. package/template/react/vite.config.ts +27 -0
  44. package/template/{widget.package.ts → react/widget.package.ts} +5 -1
  45. package/template/{.vscode → vue/.vscode}/extensions.json +3 -3
  46. package/template/vue/README.md +34 -0
  47. package/template/{env.d.ts → vue/env.d.ts} +1 -1
  48. package/template/{index.html → vue/index.html} +13 -13
  49. package/template/vue/package.json +34 -0
  50. package/template/vue/public/apple-touch-icon.png +0 -0
  51. package/template/vue/public/favicon-96x96.png +0 -0
  52. package/template/vue/public/favicon.ico +0 -0
  53. package/template/vue/public/favicon.svg +3 -0
  54. package/template/vue/public/logo.png +0 -0
  55. package/template/vue/public/preview_clock.png +0 -0
  56. package/template/vue/public/site.webmanifest +21 -0
  57. package/template/vue/public/web-app-manifest-192x192.png +0 -0
  58. package/template/vue/public/web-app-manifest-512x512.png +0 -0
  59. package/template/{src → vue/src}/App.vue +9 -9
  60. package/template/vue/src/Home.vue +10 -0
  61. package/template/{src → vue/src}/main.ts +13 -12
  62. package/template/{src → vue/src}/router/index.ts +4 -0
  63. package/template/{src → vue/src}/widgets/clock/Clock.widget.ts +21 -21
  64. package/template/{src → vue/src}/widgets/clock/ClockConfigView.vue +37 -38
  65. package/template/{src → vue/src}/widgets/clock/ClockWidgetRoutes.ts +28 -28
  66. package/template/{src → vue/src}/widgets/clock/ClockWidgetView.vue +38 -35
  67. package/template/{src → vue/src}/widgets/widget-router.ts +3 -3
  68. package/template/{tsconfig.app.json → vue/tsconfig.app.json} +13 -13
  69. package/template/{tsconfig.json → vue/tsconfig.json} +11 -11
  70. package/template/{tsconfig.node.json → vue/tsconfig.node.json} +17 -17
  71. package/template/vue/vite.config.ts +29 -0
  72. package/template/vue/widget.package.ts +24 -0
  73. package/unocss/src/main.ts +13 -0
  74. package/unocss/uno.config.ts +5 -0
  75. package/unocss/vite.config.ts +31 -0
  76. package/index.cjs +0 -7913
  77. package/template/README.md +0 -40
  78. package/template/package.json +0 -29
  79. package/template/public/favicon.ico +0 -0
  80. package/template/src/widgets/clock/model/ClockModel.ts +0 -5
  81. package/template/vite.config.ts +0 -15
  82. /package/template/{public → react/public}/preview_clock.png +0 -0
  83. /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 }