@wealthx/shadcn 0.0.1 → 1.0.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/.turbo/turbo-build.log +160 -0
- package/CHANGELOG.md +13 -0
- package/CHANGES.md +345 -0
- package/dist/chunk-2WZVSBAY.mjs +232 -0
- package/dist/chunk-2Y7YJKPE.mjs +47 -0
- package/dist/chunk-3U7SD3MS.mjs +55 -0
- package/dist/chunk-3VQNJ235.mjs +114 -0
- package/dist/chunk-55CEW76V.mjs +35 -0
- package/dist/chunk-6AFMNC42.mjs +146 -0
- package/dist/chunk-6OJF6XRN.mjs +117 -0
- package/dist/chunk-7LDIMXGM.mjs +181 -0
- package/dist/chunk-AMJ23O53.mjs +122 -0
- package/dist/chunk-BBJBJSXQ.mjs +44 -0
- package/dist/chunk-BGP2N52Z.mjs +126 -0
- package/dist/chunk-BMFN37JH.mjs +41 -0
- package/dist/chunk-CGOKTPXU.mjs +79 -0
- package/dist/chunk-CZ3BW5GL.mjs +81 -0
- package/dist/chunk-DBHJ5KC3.mjs +55 -0
- package/dist/chunk-DDPA2XXS.mjs +97 -0
- package/dist/chunk-DS2AMHN2.mjs +30 -0
- package/dist/chunk-E3K6O4FZ.mjs +57 -0
- package/dist/chunk-FWCSY2DS.mjs +37 -0
- package/dist/chunk-GPRJQ24C.mjs +28 -0
- package/dist/chunk-HS7TFG7V.mjs +24 -0
- package/dist/chunk-HUVTPUV2.mjs +256 -0
- package/dist/chunk-IAOOZCUY.mjs +90 -0
- package/dist/chunk-JF4PHPD5.mjs +111 -0
- package/dist/chunk-JU2RUWHF.mjs +123 -0
- package/dist/chunk-KKHTJNMM.mjs +86 -0
- package/dist/chunk-MJIEMGRD.mjs +266 -0
- package/dist/chunk-MKFL5MNH.mjs +372 -0
- package/dist/chunk-MQ72DIBH.mjs +105 -0
- package/dist/chunk-NGYG2EA6.mjs +148 -0
- package/dist/chunk-NWZ46DJL.mjs +213 -0
- package/dist/chunk-OXQQNQZI.mjs +75 -0
- package/dist/chunk-PMKODV6M.mjs +161 -0
- package/dist/chunk-QOJ2DQD6.mjs +57 -0
- package/dist/chunk-RL772EH7.mjs +126 -0
- package/dist/chunk-SLWCCURD.mjs +99 -0
- package/dist/chunk-V7CNWJT3.mjs +10 -0
- package/dist/chunk-VG6UF6UT.mjs +68 -0
- package/dist/chunk-VYMHBV6D.mjs +123 -0
- package/dist/chunk-VZ2NR7L3.mjs +195 -0
- package/dist/chunk-YN5SYTOO.mjs +117 -0
- package/dist/chunk-Z3MK2KKZ.mjs +83 -0
- package/dist/chunk-ZN2QKLF6.mjs +187 -0
- package/dist/chunk-ZZV5JVNW.mjs +34 -0
- package/dist/components/ui/accordion.js +142 -0
- package/dist/components/ui/accordion.mjs +14 -0
- package/dist/components/ui/alert-dialog.js +413 -0
- package/dist/components/ui/alert-dialog.mjs +34 -0
- package/dist/components/ui/alert.js +134 -0
- package/dist/components/ui/alert.mjs +12 -0
- package/dist/components/ui/avatar.js +173 -0
- package/dist/components/ui/avatar.mjs +18 -0
- package/dist/components/ui/badge.js +163 -0
- package/dist/components/ui/badge.mjs +11 -0
- package/dist/components/ui/button.js +198 -0
- package/dist/components/ui/button.mjs +11 -0
- package/dist/components/ui/calendar.js +408 -0
- package/dist/components/ui/calendar.mjs +12 -0
- package/dist/components/ui/card.js +156 -0
- package/dist/components/ui/card.mjs +20 -0
- package/dist/components/ui/checkbox.js +166 -0
- package/dist/components/ui/checkbox.mjs +11 -0
- package/dist/components/ui/chip.js +199 -0
- package/dist/components/ui/chip.mjs +10 -0
- package/dist/components/ui/data-table.js +925 -0
- package/dist/components/ui/data-table.mjs +29 -0
- package/dist/components/ui/date-picker.js +561 -0
- package/dist/components/ui/date-picker.mjs +15 -0
- package/dist/components/ui/dialog.js +378 -0
- package/dist/components/ui/dialog.mjs +30 -0
- package/dist/components/ui/drawer.js +213 -0
- package/dist/components/ui/drawer.mjs +28 -0
- package/dist/components/ui/dropdown-menu.js +338 -0
- package/dist/components/ui/dropdown-menu.mjs +38 -0
- package/dist/components/ui/empty.js +173 -0
- package/dist/components/ui/empty.mjs +18 -0
- package/dist/components/ui/field.js +359 -0
- package/dist/components/ui/field.mjs +28 -0
- package/dist/components/ui/input-group.js +406 -0
- package/dist/components/ui/input-group.mjs +22 -0
- package/dist/components/ui/input-otp.js +149 -0
- package/dist/components/ui/input-otp.mjs +14 -0
- package/dist/components/ui/input.js +81 -0
- package/dist/components/ui/input.mjs +8 -0
- package/dist/components/ui/label.js +85 -0
- package/dist/components/ui/label.mjs +8 -0
- package/dist/components/ui/pagination.js +333 -0
- package/dist/components/ui/pagination.mjs +22 -0
- package/dist/components/ui/popover.js +167 -0
- package/dist/components/ui/popover.mjs +22 -0
- package/dist/components/ui/progress.js +97 -0
- package/dist/components/ui/progress.mjs +8 -0
- package/dist/components/ui/radio-group.js +178 -0
- package/dist/components/ui/radio-group.mjs +12 -0
- package/dist/components/ui/select.js +262 -0
- package/dist/components/ui/select.mjs +28 -0
- package/dist/components/ui/separator.js +86 -0
- package/dist/components/ui/separator.mjs +8 -0
- package/dist/components/ui/sheet.js +227 -0
- package/dist/components/ui/sheet.mjs +26 -0
- package/dist/components/ui/skeleton.js +75 -0
- package/dist/components/ui/skeleton.mjs +8 -0
- package/dist/components/ui/sonner.js +86 -0
- package/dist/components/ui/sonner.mjs +7 -0
- package/dist/components/ui/spinner.js +93 -0
- package/dist/components/ui/spinner.mjs +10 -0
- package/dist/components/ui/switch.js +178 -0
- package/dist/components/ui/switch.mjs +11 -0
- package/dist/components/ui/table.js +184 -0
- package/dist/components/ui/table.mjs +22 -0
- package/dist/components/ui/tabs.js +181 -0
- package/dist/components/ui/tabs.mjs +16 -0
- package/dist/components/ui/textarea.js +79 -0
- package/dist/components/ui/textarea.mjs +8 -0
- package/dist/components/ui/toggle-group.js +184 -0
- package/dist/components/ui/toggle-group.mjs +12 -0
- package/dist/components/ui/toggle.js +108 -0
- package/dist/components/ui/toggle.mjs +11 -0
- package/dist/components/ui/tooltip.js +140 -0
- package/dist/components/ui/tooltip.mjs +16 -0
- package/dist/index.js +4409 -0
- package/dist/index.mjs +462 -0
- package/dist/lib/colors.js +84 -0
- package/dist/lib/colors.mjs +13 -0
- package/dist/lib/theme-provider.js +150 -0
- package/dist/lib/theme-provider.mjs +13 -0
- package/dist/lib/typography.js +157 -0
- package/dist/lib/typography.mjs +25 -0
- package/dist/lib/utils.js +34 -0
- package/dist/lib/utils.mjs +7 -0
- package/dist/styles.css +2 -0
- package/package.json +228 -11
- package/scripts/build-css.ts +15 -9
- package/src/components/index.tsx +443 -0
- package/src/components/ui/accordion.tsx +99 -0
- package/src/components/ui/alert-dialog.tsx +239 -0
- package/src/components/ui/alert.tsx +81 -0
- package/src/components/ui/avatar.tsx +130 -0
- package/src/components/ui/badge.tsx +57 -0
- package/src/components/ui/button.tsx +69 -37
- package/src/components/ui/calendar.tsx +252 -0
- package/src/components/ui/card.tsx +106 -0
- package/src/components/ui/checkbox.tsx +111 -0
- package/src/components/ui/chip.tsx +65 -0
- package/src/components/ui/data-table.tsx +490 -0
- package/src/components/ui/date-picker.tsx +133 -0
- package/src/components/ui/dialog.tsx +195 -0
- package/src/components/ui/drawer.tsx +169 -0
- package/src/components/ui/dropdown-menu.tsx +315 -0
- package/src/components/ui/empty.tsx +128 -0
- package/src/components/ui/field.tsx +273 -0
- package/src/components/ui/input-group.tsx +190 -0
- package/src/components/ui/input-otp.tsx +90 -0
- package/src/components/ui/input.tsx +28 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/pagination.tsx +148 -0
- package/src/components/ui/popover.tsx +112 -0
- package/src/components/ui/progress.tsx +40 -0
- package/src/components/ui/radio-group.tsx +129 -0
- package/src/components/ui/select.tsx +201 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/sheet.tsx +182 -0
- package/src/components/ui/skeleton.tsx +22 -0
- package/src/components/ui/sonner.tsx +48 -0
- package/src/components/ui/spinner.tsx +41 -0
- package/src/components/ui/switch.tsx +126 -0
- package/src/components/ui/table.tsx +143 -0
- package/src/components/ui/tabs.tsx +119 -0
- package/src/components/ui/textarea.tsx +28 -0
- package/src/components/ui/toggle-group.tsx +94 -0
- package/src/components/ui/toggle.tsx +59 -0
- package/src/components/ui/tooltip.tsx +80 -0
- package/src/index.ts +15 -3
- package/src/lib/colors.ts +74 -0
- package/src/lib/slot.tsx +68 -0
- package/src/lib/theme-provider.tsx +134 -0
- package/src/lib/typography.ts +153 -0
- package/src/lib/utils.ts +1 -1
- package/src/styles/globals.css +377 -107
- package/src/styles/styles-css.ts +1 -1
- package/tsup.config.ts +48 -2
- package/src/provider/ShadcnProvider.tsx +0 -89
- package/src/provider/index.ts +0 -2
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { type ReactElement } from "react"
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Empty — WealthX DS (shadcn registry base)
|
|
8
|
+
*
|
|
9
|
+
* Composable empty-state component for no-data / blank-state screens.
|
|
10
|
+
* WealthX overrides:
|
|
11
|
+
* - `rounded-lg` removed — sharp corners per WealthX DS
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export type EmptyProps = React.ComponentProps<"div">
|
|
15
|
+
|
|
16
|
+
function Empty({ className, ...props }: EmptyProps): ReactElement {
|
|
17
|
+
return (
|
|
18
|
+
<div
|
|
19
|
+
className={cn(
|
|
20
|
+
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 border border-dashed p-6 text-center text-balance md:p-12",
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
data-slot="empty"
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type EmptyHeaderProps = React.ComponentProps<"div">
|
|
30
|
+
|
|
31
|
+
function EmptyHeader({ className, ...props }: EmptyHeaderProps): ReactElement {
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
className={cn(
|
|
35
|
+
"flex max-w-sm flex-col items-center gap-2 text-center",
|
|
36
|
+
className
|
|
37
|
+
)}
|
|
38
|
+
data-slot="empty-header"
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const emptyMediaVariants = cva(
|
|
45
|
+
"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
46
|
+
{
|
|
47
|
+
variants: {
|
|
48
|
+
variant: {
|
|
49
|
+
default: "[&_svg:not([class*='size-'])]:size-10",
|
|
50
|
+
icon: "flex size-10 shrink-0 items-center justify-center text-muted-foreground [&_svg:not([class*='size-'])]:size-6",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
defaultVariants: {
|
|
54
|
+
variant: "default",
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
export type EmptyMediaProps = React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>
|
|
60
|
+
|
|
61
|
+
function EmptyMedia({
|
|
62
|
+
className,
|
|
63
|
+
variant = "default",
|
|
64
|
+
...props
|
|
65
|
+
}: EmptyMediaProps): ReactElement {
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
className={cn(emptyMediaVariants({ variant, className }))}
|
|
69
|
+
data-slot="empty-icon"
|
|
70
|
+
data-variant={variant}
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type EmptyTitleProps = React.ComponentProps<"div">
|
|
77
|
+
|
|
78
|
+
function EmptyTitle({ className, ...props }: EmptyTitleProps): ReactElement {
|
|
79
|
+
return (
|
|
80
|
+
<div
|
|
81
|
+
className={cn("text-lg font-medium tracking-tight", className)}
|
|
82
|
+
data-slot="empty-title"
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type EmptyDescriptionProps = React.ComponentProps<"div">
|
|
89
|
+
|
|
90
|
+
function EmptyDescription({
|
|
91
|
+
className,
|
|
92
|
+
...props
|
|
93
|
+
}: EmptyDescriptionProps): ReactElement {
|
|
94
|
+
return (
|
|
95
|
+
<div
|
|
96
|
+
className={cn(
|
|
97
|
+
"text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
98
|
+
className
|
|
99
|
+
)}
|
|
100
|
+
data-slot="empty-description"
|
|
101
|
+
{...props}
|
|
102
|
+
/>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type EmptyContentProps = React.ComponentProps<"div">
|
|
107
|
+
|
|
108
|
+
function EmptyContent({ className, ...props }: EmptyContentProps): ReactElement {
|
|
109
|
+
return (
|
|
110
|
+
<div
|
|
111
|
+
className={cn(
|
|
112
|
+
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
|
|
113
|
+
className
|
|
114
|
+
)}
|
|
115
|
+
data-slot="empty-content"
|
|
116
|
+
{...props}
|
|
117
|
+
/>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
Empty,
|
|
123
|
+
EmptyHeader,
|
|
124
|
+
EmptyTitle,
|
|
125
|
+
EmptyDescription,
|
|
126
|
+
EmptyContent,
|
|
127
|
+
EmptyMedia,
|
|
128
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { type ReactElement, useMemo } from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
import { Label } from "@/components/ui/label"
|
|
5
|
+
import { Separator } from "@/components/ui/separator"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Field — shadcn/WealthX
|
|
9
|
+
* Base: npx shadcn\@latest add field
|
|
10
|
+
* Figma: https://www.figma.com/design/9V9F0NGVsif8LGmEhVjOcT/Design-System---shadcn?node-id=1188-4205
|
|
11
|
+
*
|
|
12
|
+
* WealthX additions:
|
|
13
|
+
* - FieldDescription/FieldError: text-xs (12px) per Figma helper text spec
|
|
14
|
+
* - FieldError.uniqueErrors: Array.from() instead of spread on Map iterator (es5 compat)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export type FieldSetProps = React.ComponentProps<"fieldset">
|
|
18
|
+
|
|
19
|
+
function FieldSet({ className, ...props }: FieldSetProps): ReactElement {
|
|
20
|
+
return (
|
|
21
|
+
<fieldset
|
|
22
|
+
className={cn(
|
|
23
|
+
"flex flex-col gap-6",
|
|
24
|
+
"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
data-slot="field-set"
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type FieldLegendProps = React.ComponentProps<"legend"> & { variant?: "legend" | "label" }
|
|
34
|
+
|
|
35
|
+
function FieldLegend({
|
|
36
|
+
className,
|
|
37
|
+
variant = "legend",
|
|
38
|
+
...props
|
|
39
|
+
}: FieldLegendProps): ReactElement {
|
|
40
|
+
return (
|
|
41
|
+
<legend
|
|
42
|
+
className={cn(
|
|
43
|
+
"mb-3 font-medium",
|
|
44
|
+
"data-[variant=legend]:text-base",
|
|
45
|
+
"data-[variant=label]:text-sm",
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
data-slot="field-legend"
|
|
49
|
+
data-variant={variant}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type FieldGroupProps = React.ComponentProps<"div">
|
|
56
|
+
|
|
57
|
+
function FieldGroup({ className, ...props }: FieldGroupProps): ReactElement {
|
|
58
|
+
return (
|
|
59
|
+
<div
|
|
60
|
+
className={cn(
|
|
61
|
+
"group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
|
|
62
|
+
className
|
|
63
|
+
)}
|
|
64
|
+
data-slot="field-group"
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const fieldVariants = cva(
|
|
71
|
+
"group/field flex w-full gap-3 data-[invalid=true]:text-destructive",
|
|
72
|
+
{
|
|
73
|
+
variants: {
|
|
74
|
+
orientation: {
|
|
75
|
+
vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
|
|
76
|
+
horizontal: [
|
|
77
|
+
"flex-row items-center",
|
|
78
|
+
"[&>[data-slot=field-label]]:flex-auto",
|
|
79
|
+
"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
80
|
+
],
|
|
81
|
+
responsive: [
|
|
82
|
+
"flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
|
|
83
|
+
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
|
|
84
|
+
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
defaultVariants: {
|
|
89
|
+
orientation: "vertical",
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
export type FieldProps = React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>
|
|
95
|
+
|
|
96
|
+
function Field({
|
|
97
|
+
className,
|
|
98
|
+
orientation = "vertical",
|
|
99
|
+
...props
|
|
100
|
+
}: FieldProps): ReactElement {
|
|
101
|
+
return (
|
|
102
|
+
<div
|
|
103
|
+
className={cn(fieldVariants({ orientation }), className)}
|
|
104
|
+
data-orientation={orientation}
|
|
105
|
+
data-slot="field"
|
|
106
|
+
role="group"
|
|
107
|
+
{...props}
|
|
108
|
+
/>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export type FieldContentProps = React.ComponentProps<"div">
|
|
113
|
+
|
|
114
|
+
function FieldContent({ className, ...props }: FieldContentProps): ReactElement {
|
|
115
|
+
return (
|
|
116
|
+
<div
|
|
117
|
+
className={cn(
|
|
118
|
+
"group/field-content flex flex-1 flex-col gap-1.5 leading-snug",
|
|
119
|
+
className
|
|
120
|
+
)}
|
|
121
|
+
data-slot="field-content"
|
|
122
|
+
{...props}
|
|
123
|
+
/>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export type FieldLabelProps = React.ComponentProps<typeof Label>
|
|
128
|
+
|
|
129
|
+
function FieldLabel({
|
|
130
|
+
className,
|
|
131
|
+
...props
|
|
132
|
+
}: FieldLabelProps): ReactElement {
|
|
133
|
+
return (
|
|
134
|
+
<Label
|
|
135
|
+
className={cn(
|
|
136
|
+
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
|
|
137
|
+
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
|
|
138
|
+
"has-data-checked:border-primary has-data-checked:bg-primary/5 dark:has-data-checked:bg-primary/10",
|
|
139
|
+
className
|
|
140
|
+
)}
|
|
141
|
+
data-slot="field-label"
|
|
142
|
+
{...props}
|
|
143
|
+
/>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export type FieldTitleProps = React.ComponentProps<"div">
|
|
148
|
+
|
|
149
|
+
function FieldTitle({ className, ...props }: FieldTitleProps): ReactElement {
|
|
150
|
+
return (
|
|
151
|
+
<div
|
|
152
|
+
className={cn(
|
|
153
|
+
"flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50",
|
|
154
|
+
className
|
|
155
|
+
)}
|
|
156
|
+
data-slot="field-label"
|
|
157
|
+
{...props}
|
|
158
|
+
/>
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export type FieldDescriptionProps = React.ComponentProps<"p">
|
|
163
|
+
|
|
164
|
+
function FieldDescription({ className, ...props }: FieldDescriptionProps): ReactElement {
|
|
165
|
+
return (
|
|
166
|
+
<p
|
|
167
|
+
className={cn(
|
|
168
|
+
"text-xs leading-normal font-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance",
|
|
169
|
+
"last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
|
|
170
|
+
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
171
|
+
className
|
|
172
|
+
)}
|
|
173
|
+
data-slot="field-description"
|
|
174
|
+
{...props}
|
|
175
|
+
/>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export type FieldSeparatorProps = React.ComponentProps<"div"> & {
|
|
180
|
+
children?: React.ReactNode
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function FieldSeparator({
|
|
184
|
+
children,
|
|
185
|
+
className,
|
|
186
|
+
...props
|
|
187
|
+
}: FieldSeparatorProps): ReactElement {
|
|
188
|
+
return (
|
|
189
|
+
<div
|
|
190
|
+
className={cn(
|
|
191
|
+
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
|
|
192
|
+
className
|
|
193
|
+
)}
|
|
194
|
+
data-content={Boolean(children)}
|
|
195
|
+
data-slot="field-separator"
|
|
196
|
+
{...props}
|
|
197
|
+
>
|
|
198
|
+
<Separator className="absolute inset-0 top-1/2" />
|
|
199
|
+
{children ? <span
|
|
200
|
+
className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
|
|
201
|
+
data-slot="field-separator-content"
|
|
202
|
+
>
|
|
203
|
+
{children}
|
|
204
|
+
</span> : null}
|
|
205
|
+
</div>
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export type FieldErrorProps = React.ComponentProps<"div"> & {
|
|
210
|
+
errors?: ({ message?: string } | undefined)[]
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function FieldError({
|
|
214
|
+
className,
|
|
215
|
+
children,
|
|
216
|
+
errors,
|
|
217
|
+
...props
|
|
218
|
+
}: FieldErrorProps): ReactElement | null {
|
|
219
|
+
const content = useMemo(() => {
|
|
220
|
+
if (children) {
|
|
221
|
+
return children
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!errors?.length) {
|
|
225
|
+
return null
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const uniqueErrors = Array.from(
|
|
229
|
+
new Map(errors.map((error) => [error?.message, error])).values()
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if (uniqueErrors.length === 1) {
|
|
233
|
+
return uniqueErrors[0]?.message
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<ul className="ml-4 flex list-disc flex-col gap-1">
|
|
238
|
+
{uniqueErrors.map(
|
|
239
|
+
(error) =>
|
|
240
|
+
error?.message && <li key={error.message}>{error.message}</li>
|
|
241
|
+
)}
|
|
242
|
+
</ul>
|
|
243
|
+
)
|
|
244
|
+
}, [children, errors])
|
|
245
|
+
|
|
246
|
+
if (!content) {
|
|
247
|
+
return null
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<div
|
|
252
|
+
className={cn("text-xs font-normal text-destructive", className)}
|
|
253
|
+
data-slot="field-error"
|
|
254
|
+
role="alert"
|
|
255
|
+
{...props}
|
|
256
|
+
>
|
|
257
|
+
{content}
|
|
258
|
+
</div>
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export {
|
|
263
|
+
Field,
|
|
264
|
+
FieldLabel,
|
|
265
|
+
FieldDescription,
|
|
266
|
+
FieldError,
|
|
267
|
+
FieldGroup,
|
|
268
|
+
FieldLegend,
|
|
269
|
+
FieldSeparator,
|
|
270
|
+
FieldSet,
|
|
271
|
+
FieldContent,
|
|
272
|
+
FieldTitle,
|
|
273
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { type ReactElement } from "react"
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
import { Button } from "@/components/ui/button"
|
|
6
|
+
import { Input } from "@/components/ui/input"
|
|
7
|
+
import { Textarea } from "@/components/ui/textarea"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* InputGroup — shadcn/WealthX
|
|
11
|
+
* Base: npx shadcn\@latest add input-group
|
|
12
|
+
* WealthX: removed rounded-md (square corners per brand)
|
|
13
|
+
*/
|
|
14
|
+
export type InputGroupProps = React.ComponentProps<"div">
|
|
15
|
+
|
|
16
|
+
function InputGroup({ className, ...props }: InputGroupProps): ReactElement {
|
|
17
|
+
return (
|
|
18
|
+
<div
|
|
19
|
+
className={cn(
|
|
20
|
+
"group/input-group relative flex w-full items-center border border-input shadow-xs transition-[color,box-shadow] outline-none dark:bg-input/30",
|
|
21
|
+
"h-9 min-w-0 has-[>textarea]:h-auto",
|
|
22
|
+
|
|
23
|
+
// Variants based on alignment.
|
|
24
|
+
"has-[>[data-align=inline-start]]:[&>input]:pl-2",
|
|
25
|
+
"has-[>[data-align=inline-end]]:[&>input]:pr-2",
|
|
26
|
+
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
|
|
27
|
+
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
|
|
28
|
+
|
|
29
|
+
// Focus state.
|
|
30
|
+
"has-[[data-slot=input-group-control]:focus-visible]:border-primary has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-primary/20",
|
|
31
|
+
|
|
32
|
+
// Error state.
|
|
33
|
+
"has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
|
|
34
|
+
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
data-slot="input-group"
|
|
38
|
+
role="group"
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const inputGroupAddonVariants = cva(
|
|
45
|
+
"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",
|
|
46
|
+
{
|
|
47
|
+
variants: {
|
|
48
|
+
align: {
|
|
49
|
+
"inline-start":
|
|
50
|
+
"order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
|
|
51
|
+
"inline-end":
|
|
52
|
+
"order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
|
|
53
|
+
"block-start":
|
|
54
|
+
"order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3",
|
|
55
|
+
"block-end":
|
|
56
|
+
"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
|
+
export type InputGroupAddonProps = React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>
|
|
66
|
+
|
|
67
|
+
function InputGroupAddon({
|
|
68
|
+
className,
|
|
69
|
+
align = "inline-start",
|
|
70
|
+
...props
|
|
71
|
+
}: InputGroupAddonProps): ReactElement {
|
|
72
|
+
return (
|
|
73
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- click only forwards focus to the sibling input; the div is not truly interactive
|
|
74
|
+
<div
|
|
75
|
+
className={cn(inputGroupAddonVariants({ align }), className)}
|
|
76
|
+
data-align={align}
|
|
77
|
+
data-slot="input-group-addon"
|
|
78
|
+
onClick={(e) => {
|
|
79
|
+
if ((e.target as HTMLElement).closest("button")) {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
e.currentTarget.parentElement?.querySelector("input")?.focus()
|
|
83
|
+
}}
|
|
84
|
+
onKeyDown={(e) => {
|
|
85
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
86
|
+
e.currentTarget.parentElement?.querySelector("input")?.focus()
|
|
87
|
+
}
|
|
88
|
+
}}
|
|
89
|
+
role="group"
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const inputGroupButtonVariants = cva(
|
|
96
|
+
"flex items-center gap-2 text-sm shadow-none",
|
|
97
|
+
{
|
|
98
|
+
variants: {
|
|
99
|
+
size: {
|
|
100
|
+
xs: "h-6 gap-1 px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
|
|
101
|
+
sm: "h-8 gap-1.5 px-2.5 has-[>svg]:px-2.5",
|
|
102
|
+
"icon-xs": "size-6 p-0 has-[>svg]:p-0",
|
|
103
|
+
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
defaultVariants: {
|
|
107
|
+
size: "xs",
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
export type InputGroupButtonProps = Omit<React.ComponentProps<typeof Button>, "size"> &
|
|
113
|
+
VariantProps<typeof inputGroupButtonVariants>
|
|
114
|
+
|
|
115
|
+
function InputGroupButton({
|
|
116
|
+
className,
|
|
117
|
+
type = "button",
|
|
118
|
+
variant = "ghost",
|
|
119
|
+
size = "xs",
|
|
120
|
+
...props
|
|
121
|
+
}: InputGroupButtonProps): ReactElement {
|
|
122
|
+
return (
|
|
123
|
+
<Button
|
|
124
|
+
className={cn(inputGroupButtonVariants({ size }), className)}
|
|
125
|
+
data-size={size}
|
|
126
|
+
type={type}
|
|
127
|
+
variant={variant}
|
|
128
|
+
{...props}
|
|
129
|
+
/>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export type InputGroupTextProps = React.ComponentProps<"span">
|
|
134
|
+
|
|
135
|
+
function InputGroupText({ className, ...props }: InputGroupTextProps): ReactElement {
|
|
136
|
+
return (
|
|
137
|
+
<span
|
|
138
|
+
className={cn(
|
|
139
|
+
"flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
|
140
|
+
className
|
|
141
|
+
)}
|
|
142
|
+
{...props}
|
|
143
|
+
/>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export type InputGroupInputProps = React.ComponentProps<"input">
|
|
148
|
+
|
|
149
|
+
function InputGroupInput({
|
|
150
|
+
className,
|
|
151
|
+
...props
|
|
152
|
+
}: InputGroupInputProps): ReactElement {
|
|
153
|
+
return (
|
|
154
|
+
<Input
|
|
155
|
+
className={cn(
|
|
156
|
+
"flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
|
|
157
|
+
className
|
|
158
|
+
)}
|
|
159
|
+
data-slot="input-group-control"
|
|
160
|
+
{...props}
|
|
161
|
+
/>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export type InputGroupTextareaProps = React.ComponentProps<"textarea">
|
|
166
|
+
|
|
167
|
+
function InputGroupTextarea({
|
|
168
|
+
className,
|
|
169
|
+
...props
|
|
170
|
+
}: InputGroupTextareaProps): ReactElement {
|
|
171
|
+
return (
|
|
172
|
+
<Textarea
|
|
173
|
+
className={cn(
|
|
174
|
+
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
|
|
175
|
+
className
|
|
176
|
+
)}
|
|
177
|
+
data-slot="input-group-control"
|
|
178
|
+
{...props}
|
|
179
|
+
/>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export {
|
|
184
|
+
InputGroup,
|
|
185
|
+
InputGroupAddon,
|
|
186
|
+
InputGroupButton,
|
|
187
|
+
InputGroupText,
|
|
188
|
+
InputGroupInput,
|
|
189
|
+
InputGroupTextarea,
|
|
190
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InputOTP — shadcn/WealthX
|
|
3
|
+
* Base: npx shadcn\@latest add input-otp
|
|
4
|
+
* Figma: https://www.figma.com/design/9V9F0NGVsif8LGmEhVjOcT/Design-System---shadcn?node-id=101-698
|
|
5
|
+
*
|
|
6
|
+
* WealthX overrides:
|
|
7
|
+
* - InputOTPSlot: removed first:rounded-l-md last:rounded-r-md — sharp corners (radius-none)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { type ReactElement } from "react"
|
|
11
|
+
import * as React from "react"
|
|
12
|
+
import { OTPInput, OTPInputContext } from "input-otp"
|
|
13
|
+
import { MinusIcon } from "lucide-react"
|
|
14
|
+
import { cn } from "@/lib/utils"
|
|
15
|
+
|
|
16
|
+
export type InputOTPProps = React.ComponentProps<typeof OTPInput> & {
|
|
17
|
+
containerClassName?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function InputOTP({
|
|
21
|
+
className,
|
|
22
|
+
containerClassName,
|
|
23
|
+
...props
|
|
24
|
+
}: InputOTPProps): ReactElement {
|
|
25
|
+
return (
|
|
26
|
+
<OTPInput
|
|
27
|
+
className={cn("disabled:cursor-not-allowed", className)}
|
|
28
|
+
containerClassName={cn(
|
|
29
|
+
"flex items-center gap-2 has-disabled:opacity-50",
|
|
30
|
+
containerClassName
|
|
31
|
+
)}
|
|
32
|
+
data-slot="input-otp"
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type InputOTPGroupProps = React.ComponentProps<"div">
|
|
39
|
+
|
|
40
|
+
function InputOTPGroup({ className, ...props }: InputOTPGroupProps): ReactElement {
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
className={cn("flex items-center", className)}
|
|
44
|
+
data-slot="input-otp-group"
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type InputOTPSlotProps = React.ComponentProps<"div"> & {
|
|
51
|
+
index: number
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function InputOTPSlot({
|
|
55
|
+
index,
|
|
56
|
+
className,
|
|
57
|
+
...props
|
|
58
|
+
}: InputOTPSlotProps): ReactElement {
|
|
59
|
+
const inputOTPContext = React.useContext(OTPInputContext)
|
|
60
|
+
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index] ?? {}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
className={cn(
|
|
65
|
+
"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:border-l 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 dark:bg-input/30 dark:data-[active=true]:aria-invalid:ring-destructive/40",
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
data-active={isActive}
|
|
69
|
+
data-slot="input-otp-slot"
|
|
70
|
+
{...props}
|
|
71
|
+
>
|
|
72
|
+
{char}
|
|
73
|
+
{hasFakeCaret ? <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
|
74
|
+
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
|
|
75
|
+
</div> : null}
|
|
76
|
+
</div>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type InputOTPSeparatorProps = React.ComponentProps<"div">
|
|
81
|
+
|
|
82
|
+
function InputOTPSeparator({ ...props }: InputOTPSeparatorProps): ReactElement {
|
|
83
|
+
return (
|
|
84
|
+
<div data-slot="input-otp-separator" role="separator" {...props}>
|
|
85
|
+
<MinusIcon />
|
|
86
|
+
</div>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ReactElement } from "react"
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Input — shadcn/WealthX
|
|
7
|
+
* Base: npx shadcn\@latest add input
|
|
8
|
+
* WealthX: removed rounded-md (square corners), added font-sans.
|
|
9
|
+
*/
|
|
10
|
+
export type InputProps = React.ComponentProps<"input">
|
|
11
|
+
|
|
12
|
+
function Input({ className, type, ...props }: InputProps): ReactElement {
|
|
13
|
+
return (
|
|
14
|
+
<input
|
|
15
|
+
className={cn(
|
|
16
|
+
"h-9 w-full min-w-0 border border-input bg-transparent px-3 py-1 text-base font-sans shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
|
|
17
|
+
"focus-visible:border-primary focus-visible:ring-[3px] focus-visible:ring-primary/20",
|
|
18
|
+
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
19
|
+
className
|
|
20
|
+
)}
|
|
21
|
+
data-slot="input"
|
|
22
|
+
type={type}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { Input }
|