create-lve 0.6.1 → 0.6.2
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/package.json +1 -1
- package/template-react/components.json +25 -0
- package/template-react/package.json +10 -0
- package/template-react/src/app/layouts/root.tsx +1 -1
- package/template-react/src/app/providers.tsx +1 -6
- package/template-react/src/features/home/pages/index.tsx +31 -3
- package/template-react/src/main.tsx +7 -3
- package/template-react/src/shared/components/ui/alert-dialog.tsx +180 -0
- package/template-react/src/shared/components/ui/button.tsx +67 -0
- package/template-react/src/shared/components/ui/dialog.tsx +143 -0
- package/template-react/src/shared/components/ui/input.tsx +19 -0
- package/template-react/src/shared/hooks/index.ts +4 -0
- package/template-react/src/shared/hooks/use-click-outside.ts +20 -0
- package/template-react/src/shared/hooks/use-copy-to-clipboard.ts +22 -0
- package/template-react/src/shared/hooks/use-debounce.ts +17 -0
- package/template-react/src/shared/hooks/use-media-query.ts +23 -0
- package/template-react/src/shared/lib/utils.ts +6 -0
- package/template-react/src/style.css +199 -0
- package/template-react/vite.config.ts +4 -2
- package/template-react/src/lib/utils.ts +0 -0
- /package/template-react/{src/assets → public}/react.svg +0 -0
package/package.json
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "radix-nova",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/style.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"rtl": false,
|
|
15
|
+
"aliases": {
|
|
16
|
+
"components": "@/shared/components",
|
|
17
|
+
"utils": "@/shared/lib/utils",
|
|
18
|
+
"ui": "@/shared/components/ui",
|
|
19
|
+
"lib": "@/shared/lib",
|
|
20
|
+
"hooks": "@/shared/hooks"
|
|
21
|
+
},
|
|
22
|
+
"menuColor": "default",
|
|
23
|
+
"menuAccent": "subtle",
|
|
24
|
+
"registries": {}
|
|
25
|
+
}
|
|
@@ -10,11 +10,21 @@
|
|
|
10
10
|
"prepare": "vp config"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
+
"@fontsource-variable/inter": "^5.2.8",
|
|
14
|
+
"@radix-ui/react-alert-dialog": "^1.1.17",
|
|
15
|
+
"@radix-ui/react-dialog": "^1.1.17",
|
|
13
16
|
"@tanstack/react-query": "latest",
|
|
17
|
+
"class-variance-authority": "^0.7.1",
|
|
18
|
+
"clsx": "^2.1.1",
|
|
14
19
|
"jotai": "latest",
|
|
20
|
+
"lucide-react": "^1.21.0",
|
|
21
|
+
"radix-ui": "^1.6.0",
|
|
15
22
|
"react": "latest",
|
|
16
23
|
"react-dom": "latest",
|
|
17
24
|
"react-router": "latest",
|
|
25
|
+
"shadcn": "^4.11.0",
|
|
26
|
+
"tailwind-merge": "^3.6.0",
|
|
27
|
+
"tw-animate-css": "^1.4.0",
|
|
18
28
|
"zustand": "latest"
|
|
19
29
|
},
|
|
20
30
|
"devDependencies": {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { StrictMode } from 'react'
|
|
2
1
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
3
2
|
|
|
4
3
|
const queryClient = new QueryClient({
|
|
@@ -11,9 +10,5 @@ const queryClient = new QueryClient({
|
|
|
11
10
|
})
|
|
12
11
|
|
|
13
12
|
export function Providers({ children }: { children: React.ReactNode }) {
|
|
14
|
-
return
|
|
15
|
-
<StrictMode>
|
|
16
|
-
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
17
|
-
</StrictMode>
|
|
18
|
-
)
|
|
13
|
+
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
19
14
|
}
|
|
@@ -1,7 +1,35 @@
|
|
|
1
1
|
export default function HomePage() {
|
|
2
2
|
return (
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
|
|
3
|
+
<main className="relative flex min-h-dvh items-center justify-center bg-stone-50 px-8">
|
|
4
|
+
<div className="absolute right-8 top-1/2 -translate-y-1/2 font-mono text-[10px] tracking-widest text-stone-300 [writing-mode:vertical-rl]">
|
|
5
|
+
VITE × REACT
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div className="flex flex-col items-center gap-10">
|
|
9
|
+
<div className="flex items-center gap-8">
|
|
10
|
+
<img
|
|
11
|
+
src="/favicon.svg"
|
|
12
|
+
alt="Vite"
|
|
13
|
+
className="h-20 w-20 opacity-70 transition-opacity hover:opacity-100"
|
|
14
|
+
/>
|
|
15
|
+
<span className="text-2xl font-extralight text-stone-300">+</span>
|
|
16
|
+
<img
|
|
17
|
+
src="/react.svg"
|
|
18
|
+
alt="React"
|
|
19
|
+
className="h-20 w-20 opacity-70 transition-opacity hover:opacity-100 animate-[spin_30s_linear_infinite]"
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<h1 className="text-lg font-light tracking-[0.5em] text-stone-500">Vite + React</h1>
|
|
24
|
+
|
|
25
|
+
<div className="h-px w-12 bg-stone-200" />
|
|
26
|
+
|
|
27
|
+
<p className="text-xs tracking-widest text-stone-400">TypeScript · Shadcn · Tailwind CSS</p>
|
|
28
|
+
|
|
29
|
+
<p className="text-[11px] text-stone-300">Edit src/features/home/pages/index.tsx</p>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div className="absolute bottom-8 left-8 font-mono text-[10px] text-stone-300">v0.0.1</div>
|
|
33
|
+
</main>
|
|
6
34
|
)
|
|
7
35
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import './style.css'
|
|
2
|
+
import { StrictMode } from 'react'
|
|
1
3
|
import { createRoot } from 'react-dom/client'
|
|
2
4
|
import { Providers } from './app/providers'
|
|
3
5
|
import { router } from './app/router'
|
|
4
6
|
import { RouterProvider } from 'react-router'
|
|
5
7
|
|
|
6
8
|
createRoot(document.getElementById('root')!).render(
|
|
7
|
-
<
|
|
8
|
-
<
|
|
9
|
-
|
|
9
|
+
<StrictMode>
|
|
10
|
+
<Providers>
|
|
11
|
+
<RouterProvider router={router} />
|
|
12
|
+
</Providers>
|
|
13
|
+
</StrictMode>,
|
|
10
14
|
)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { AlertDialog as AlertDialogPrimitive } from 'radix-ui'
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/shared/lib/utils'
|
|
5
|
+
import { Button } from '@/shared/components/ui/button'
|
|
6
|
+
|
|
7
|
+
function AlertDialog({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
|
8
|
+
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function AlertDialogTrigger({
|
|
12
|
+
...props
|
|
13
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
|
|
14
|
+
return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function AlertDialogPortal({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
|
|
18
|
+
return <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function AlertDialogOverlay({
|
|
22
|
+
className,
|
|
23
|
+
...props
|
|
24
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
|
|
25
|
+
return (
|
|
26
|
+
<AlertDialogPrimitive.Overlay
|
|
27
|
+
data-slot="alert-dialog-overlay"
|
|
28
|
+
className={cn(
|
|
29
|
+
'fixed inset-0 z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0',
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function AlertDialogContent({
|
|
38
|
+
className,
|
|
39
|
+
size = 'default',
|
|
40
|
+
...props
|
|
41
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {
|
|
42
|
+
size?: 'default' | 'sm'
|
|
43
|
+
}) {
|
|
44
|
+
return (
|
|
45
|
+
<AlertDialogPortal>
|
|
46
|
+
<AlertDialogOverlay />
|
|
47
|
+
<AlertDialogPrimitive.Content
|
|
48
|
+
data-slot="alert-dialog-content"
|
|
49
|
+
data-size={size}
|
|
50
|
+
className={cn(
|
|
51
|
+
'group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
|
|
52
|
+
className,
|
|
53
|
+
)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
</AlertDialogPortal>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function AlertDialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
data-slot="alert-dialog-header"
|
|
64
|
+
className={cn(
|
|
65
|
+
'grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]',
|
|
66
|
+
className,
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function AlertDialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
data-slot="alert-dialog-footer"
|
|
77
|
+
className={cn(
|
|
78
|
+
'-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end',
|
|
79
|
+
className,
|
|
80
|
+
)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function AlertDialogMedia({ className, ...props }: React.ComponentProps<'div'>) {
|
|
87
|
+
return (
|
|
88
|
+
<div
|
|
89
|
+
data-slot="alert-dialog-media"
|
|
90
|
+
className={cn(
|
|
91
|
+
"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
|
|
92
|
+
className,
|
|
93
|
+
)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function AlertDialogTitle({
|
|
100
|
+
className,
|
|
101
|
+
...props
|
|
102
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
|
103
|
+
return (
|
|
104
|
+
<AlertDialogPrimitive.Title
|
|
105
|
+
data-slot="alert-dialog-title"
|
|
106
|
+
className={cn(
|
|
107
|
+
'font-heading text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2',
|
|
108
|
+
className,
|
|
109
|
+
)}
|
|
110
|
+
{...props}
|
|
111
|
+
/>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function AlertDialogDescription({
|
|
116
|
+
className,
|
|
117
|
+
...props
|
|
118
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
|
119
|
+
return (
|
|
120
|
+
<AlertDialogPrimitive.Description
|
|
121
|
+
data-slot="alert-dialog-description"
|
|
122
|
+
className={cn(
|
|
123
|
+
'text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',
|
|
124
|
+
className,
|
|
125
|
+
)}
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function AlertDialogAction({
|
|
132
|
+
className,
|
|
133
|
+
variant = 'default',
|
|
134
|
+
size = 'default',
|
|
135
|
+
...props
|
|
136
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Action> &
|
|
137
|
+
Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) {
|
|
138
|
+
return (
|
|
139
|
+
<Button variant={variant} size={size} asChild>
|
|
140
|
+
<AlertDialogPrimitive.Action
|
|
141
|
+
data-slot="alert-dialog-action"
|
|
142
|
+
className={cn(className)}
|
|
143
|
+
{...props}
|
|
144
|
+
/>
|
|
145
|
+
</Button>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function AlertDialogCancel({
|
|
150
|
+
className,
|
|
151
|
+
variant = 'outline',
|
|
152
|
+
size = 'default',
|
|
153
|
+
...props
|
|
154
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel> &
|
|
155
|
+
Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) {
|
|
156
|
+
return (
|
|
157
|
+
<Button variant={variant} size={size} asChild>
|
|
158
|
+
<AlertDialogPrimitive.Cancel
|
|
159
|
+
data-slot="alert-dialog-cancel"
|
|
160
|
+
className={cn(className)}
|
|
161
|
+
{...props}
|
|
162
|
+
/>
|
|
163
|
+
</Button>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export {
|
|
168
|
+
AlertDialog,
|
|
169
|
+
AlertDialogAction,
|
|
170
|
+
AlertDialogCancel,
|
|
171
|
+
AlertDialogContent,
|
|
172
|
+
AlertDialogDescription,
|
|
173
|
+
AlertDialogFooter,
|
|
174
|
+
AlertDialogHeader,
|
|
175
|
+
AlertDialogMedia,
|
|
176
|
+
AlertDialogOverlay,
|
|
177
|
+
AlertDialogPortal,
|
|
178
|
+
AlertDialogTitle,
|
|
179
|
+
AlertDialogTrigger,
|
|
180
|
+
}
|
|
@@ -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 '@/shared/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,143 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { Dialog as DialogPrimitive } from 'radix-ui'
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/shared/lib/utils'
|
|
5
|
+
import { Button } from '@/shared/components/ui/button'
|
|
6
|
+
import { XIcon } from 'lucide-react'
|
|
7
|
+
|
|
8
|
+
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
|
9
|
+
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
13
|
+
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
17
|
+
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
|
21
|
+
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function DialogOverlay({
|
|
25
|
+
className,
|
|
26
|
+
...props
|
|
27
|
+
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
28
|
+
return (
|
|
29
|
+
<DialogPrimitive.Overlay
|
|
30
|
+
data-slot="dialog-overlay"
|
|
31
|
+
className={cn(
|
|
32
|
+
'fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0',
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function DialogContent({
|
|
41
|
+
className,
|
|
42
|
+
children,
|
|
43
|
+
showCloseButton = true,
|
|
44
|
+
...props
|
|
45
|
+
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
46
|
+
showCloseButton?: boolean
|
|
47
|
+
}) {
|
|
48
|
+
return (
|
|
49
|
+
<DialogPortal>
|
|
50
|
+
<DialogOverlay />
|
|
51
|
+
<DialogPrimitive.Content
|
|
52
|
+
data-slot="dialog-content"
|
|
53
|
+
className={cn(
|
|
54
|
+
'fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
|
|
55
|
+
className,
|
|
56
|
+
)}
|
|
57
|
+
{...props}
|
|
58
|
+
>
|
|
59
|
+
{children}
|
|
60
|
+
{showCloseButton && (
|
|
61
|
+
<DialogPrimitive.Close data-slot="dialog-close" asChild>
|
|
62
|
+
<Button variant="ghost" className="absolute top-2 right-2" size="icon-sm">
|
|
63
|
+
<XIcon />
|
|
64
|
+
<span className="sr-only">Close</span>
|
|
65
|
+
</Button>
|
|
66
|
+
</DialogPrimitive.Close>
|
|
67
|
+
)}
|
|
68
|
+
</DialogPrimitive.Content>
|
|
69
|
+
</DialogPortal>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
74
|
+
return (
|
|
75
|
+
<div data-slot="dialog-header" className={cn('flex flex-col gap-2', className)} {...props} />
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function DialogFooter({
|
|
80
|
+
className,
|
|
81
|
+
showCloseButton = false,
|
|
82
|
+
children,
|
|
83
|
+
...props
|
|
84
|
+
}: React.ComponentProps<'div'> & {
|
|
85
|
+
showCloseButton?: boolean
|
|
86
|
+
}) {
|
|
87
|
+
return (
|
|
88
|
+
<div
|
|
89
|
+
data-slot="dialog-footer"
|
|
90
|
+
className={cn(
|
|
91
|
+
'-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',
|
|
92
|
+
className,
|
|
93
|
+
)}
|
|
94
|
+
{...props}
|
|
95
|
+
>
|
|
96
|
+
{children}
|
|
97
|
+
{showCloseButton && (
|
|
98
|
+
<DialogPrimitive.Close asChild>
|
|
99
|
+
<Button variant="outline">Close</Button>
|
|
100
|
+
</DialogPrimitive.Close>
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
107
|
+
return (
|
|
108
|
+
<DialogPrimitive.Title
|
|
109
|
+
data-slot="dialog-title"
|
|
110
|
+
className={cn('font-heading text-base leading-none font-medium', className)}
|
|
111
|
+
{...props}
|
|
112
|
+
/>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function DialogDescription({
|
|
117
|
+
className,
|
|
118
|
+
...props
|
|
119
|
+
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
120
|
+
return (
|
|
121
|
+
<DialogPrimitive.Description
|
|
122
|
+
data-slot="dialog-description"
|
|
123
|
+
className={cn(
|
|
124
|
+
'text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',
|
|
125
|
+
className,
|
|
126
|
+
)}
|
|
127
|
+
{...props}
|
|
128
|
+
/>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
Dialog,
|
|
134
|
+
DialogClose,
|
|
135
|
+
DialogContent,
|
|
136
|
+
DialogDescription,
|
|
137
|
+
DialogFooter,
|
|
138
|
+
DialogHeader,
|
|
139
|
+
DialogOverlay,
|
|
140
|
+
DialogPortal,
|
|
141
|
+
DialogTitle,
|
|
142
|
+
DialogTrigger,
|
|
143
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/shared/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,20 @@
|
|
|
1
|
+
import { useEffect, type RefObject } from 'react'
|
|
2
|
+
|
|
3
|
+
export function useClickOutside(ref: RefObject<HTMLElement | null>, handler: () => void) {
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
const listener = (event: MouseEvent | TouchEvent) => {
|
|
6
|
+
if (!ref.current || ref.current.contains(event.target as Node)) {
|
|
7
|
+
return
|
|
8
|
+
}
|
|
9
|
+
handler()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
document.addEventListener('mousedown', listener)
|
|
13
|
+
document.addEventListener('touchstart', listener)
|
|
14
|
+
|
|
15
|
+
return () => {
|
|
16
|
+
document.removeEventListener('mousedown', listener)
|
|
17
|
+
document.removeEventListener('touchstart', listener)
|
|
18
|
+
}
|
|
19
|
+
}, [ref, handler])
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
export function useCopyToClipboard(timeout = 2000) {
|
|
4
|
+
const [copied, setCopied] = useState(false)
|
|
5
|
+
|
|
6
|
+
const copy = useCallback(
|
|
7
|
+
async (text: string) => {
|
|
8
|
+
try {
|
|
9
|
+
await navigator.clipboard.writeText(text)
|
|
10
|
+
setCopied(true)
|
|
11
|
+
setTimeout(() => setCopied(false), timeout)
|
|
12
|
+
return true
|
|
13
|
+
} catch {
|
|
14
|
+
setCopied(false)
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
[timeout],
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
return { copied, copy }
|
|
22
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
export function useDebounce<T>(value: T, delay: number): T {
|
|
4
|
+
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const timer = setTimeout(() => {
|
|
8
|
+
setDebouncedValue(value)
|
|
9
|
+
}, delay)
|
|
10
|
+
|
|
11
|
+
return () => {
|
|
12
|
+
clearTimeout(timer)
|
|
13
|
+
}
|
|
14
|
+
}, [value, delay])
|
|
15
|
+
|
|
16
|
+
return debouncedValue
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
export function useMediaQuery(query: string): boolean {
|
|
4
|
+
const [matches, setMatches] = useState(false)
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const media = window.matchMedia(query)
|
|
8
|
+
const listener = (e: MediaQueryListEvent) => setMatches(e.matches)
|
|
9
|
+
|
|
10
|
+
setMatches(media.matches)
|
|
11
|
+
media.addEventListener('change', listener)
|
|
12
|
+
|
|
13
|
+
return () => media.removeEventListener('change', listener)
|
|
14
|
+
}, [query])
|
|
15
|
+
|
|
16
|
+
return matches
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 预设断点
|
|
20
|
+
export const useIsMobile = () => useMediaQuery('(max-width: 768px)')
|
|
21
|
+
export const useIsTablet = () => useMediaQuery('(max-width: 1024px)')
|
|
22
|
+
export const useIsDesktop = () => useMediaQuery('(min-width: 1025px)')
|
|
23
|
+
export const useIsDark = () => useMediaQuery('(prefers-color-scheme: dark)')
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
@import "shadcn/tailwind.css";
|
|
4
|
+
@import "@fontsource-variable/inter";
|
|
5
|
+
|
|
6
|
+
@custom-variant dark (&:is(.dark *));
|
|
7
|
+
|
|
8
|
+
@layer base {
|
|
9
|
+
/* Scrollbar */
|
|
10
|
+
* {
|
|
11
|
+
scrollbar-width: thin;
|
|
12
|
+
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
|
|
13
|
+
@apply border-border outline-ring/50;
|
|
14
|
+
}
|
|
15
|
+
::-webkit-scrollbar {
|
|
16
|
+
width: 6px;
|
|
17
|
+
height: 6px;
|
|
18
|
+
}
|
|
19
|
+
::-webkit-scrollbar-thumb {
|
|
20
|
+
background: rgba(0, 0, 0, 0.2);
|
|
21
|
+
border-radius: 3px;
|
|
22
|
+
}
|
|
23
|
+
::-webkit-scrollbar-track {
|
|
24
|
+
background: transparent;
|
|
25
|
+
}
|
|
26
|
+
.dark * {
|
|
27
|
+
scrollbar-color: rgba(255, 255, 255, 0.15) transparent;
|
|
28
|
+
}
|
|
29
|
+
.dark ::-webkit-scrollbar-thumb {
|
|
30
|
+
background: rgba(255, 255, 255, 0.15);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* 文本渲染 */
|
|
34
|
+
html {
|
|
35
|
+
-webkit-font-smoothing: antialiased;
|
|
36
|
+
-moz-osx-font-smoothing: grayscale;
|
|
37
|
+
text-rendering: optimizeLegibility;
|
|
38
|
+
@apply font-sans;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* 选中色 */
|
|
42
|
+
::selection {
|
|
43
|
+
background: rgba(59, 130, 246, 0.2);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* 触摸优化 */
|
|
47
|
+
body {
|
|
48
|
+
-webkit-tap-highlight-color: transparent;
|
|
49
|
+
@apply bg-background text-foreground;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* 链接和图片禁止拖拽 + 长按菜单 + 点击高亮 */
|
|
54
|
+
a,
|
|
55
|
+
img {
|
|
56
|
+
-webkit-user-drag: none;
|
|
57
|
+
-webkit-tap-highlight-color: transparent;
|
|
58
|
+
-webkit-touch-callout: none;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* input number 去掉上下箭头 */
|
|
62
|
+
input[type='number']::-webkit-inner-spin-button,
|
|
63
|
+
input[type='number']::-webkit-outer-spin-button {
|
|
64
|
+
-webkit-appearance: none;
|
|
65
|
+
margin: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* textarea 禁止拖拽调整大小 */
|
|
69
|
+
textarea {
|
|
70
|
+
resize: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* details/summary 去掉默认三角 */
|
|
74
|
+
summary {
|
|
75
|
+
list-style: none;
|
|
76
|
+
}
|
|
77
|
+
summary::marker {
|
|
78
|
+
display: none;
|
|
79
|
+
content: '';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* focus 焦点环 */
|
|
83
|
+
a:focus-visible {
|
|
84
|
+
outline: 2px solid var(--color-primary, #3b82f6);
|
|
85
|
+
outline-offset: 2px;
|
|
86
|
+
border-radius: 2px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@theme inline {
|
|
90
|
+
--font-heading: var(--font-sans);
|
|
91
|
+
--font-sans: 'Inter Variable', sans-serif;
|
|
92
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
93
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
94
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
95
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
96
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
97
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
98
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
99
|
+
--color-sidebar: var(--sidebar);
|
|
100
|
+
--color-chart-5: var(--chart-5);
|
|
101
|
+
--color-chart-4: var(--chart-4);
|
|
102
|
+
--color-chart-3: var(--chart-3);
|
|
103
|
+
--color-chart-2: var(--chart-2);
|
|
104
|
+
--color-chart-1: var(--chart-1);
|
|
105
|
+
--color-ring: var(--ring);
|
|
106
|
+
--color-input: var(--input);
|
|
107
|
+
--color-border: var(--border);
|
|
108
|
+
--color-destructive: var(--destructive);
|
|
109
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
110
|
+
--color-accent: var(--accent);
|
|
111
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
112
|
+
--color-muted: var(--muted);
|
|
113
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
114
|
+
--color-secondary: var(--secondary);
|
|
115
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
116
|
+
--color-primary: var(--primary);
|
|
117
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
118
|
+
--color-popover: var(--popover);
|
|
119
|
+
--color-card-foreground: var(--card-foreground);
|
|
120
|
+
--color-card: var(--card);
|
|
121
|
+
--color-foreground: var(--foreground);
|
|
122
|
+
--color-background: var(--background);
|
|
123
|
+
--radius-sm: calc(var(--radius) * 0.6);
|
|
124
|
+
--radius-md: calc(var(--radius) * 0.8);
|
|
125
|
+
--radius-lg: var(--radius);
|
|
126
|
+
--radius-xl: calc(var(--radius) * 1.4);
|
|
127
|
+
--radius-2xl: calc(var(--radius) * 1.8);
|
|
128
|
+
--radius-3xl: calc(var(--radius) * 2.2);
|
|
129
|
+
--radius-4xl: calc(var(--radius) * 2.6);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
:root {
|
|
133
|
+
--background: oklch(1 0 0);
|
|
134
|
+
--foreground: oklch(0.145 0 0);
|
|
135
|
+
--card: oklch(1 0 0);
|
|
136
|
+
--card-foreground: oklch(0.145 0 0);
|
|
137
|
+
--popover: oklch(1 0 0);
|
|
138
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
139
|
+
--primary: oklch(0.205 0 0);
|
|
140
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
141
|
+
--secondary: oklch(0.97 0 0);
|
|
142
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
143
|
+
--muted: oklch(0.97 0 0);
|
|
144
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
145
|
+
--accent: oklch(0.97 0 0);
|
|
146
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
147
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
148
|
+
--border: oklch(0.922 0 0);
|
|
149
|
+
--input: oklch(0.922 0 0);
|
|
150
|
+
--ring: oklch(0.708 0 0);
|
|
151
|
+
--chart-1: oklch(0.87 0 0);
|
|
152
|
+
--chart-2: oklch(0.556 0 0);
|
|
153
|
+
--chart-3: oklch(0.439 0 0);
|
|
154
|
+
--chart-4: oklch(0.371 0 0);
|
|
155
|
+
--chart-5: oklch(0.269 0 0);
|
|
156
|
+
--radius: 0.625rem;
|
|
157
|
+
--sidebar: oklch(0.985 0 0);
|
|
158
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
159
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
160
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
161
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
162
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
163
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
164
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.dark {
|
|
168
|
+
--background: oklch(0.145 0 0);
|
|
169
|
+
--foreground: oklch(0.985 0 0);
|
|
170
|
+
--card: oklch(0.205 0 0);
|
|
171
|
+
--card-foreground: oklch(0.985 0 0);
|
|
172
|
+
--popover: oklch(0.205 0 0);
|
|
173
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
174
|
+
--primary: oklch(0.922 0 0);
|
|
175
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
176
|
+
--secondary: oklch(0.269 0 0);
|
|
177
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
178
|
+
--muted: oklch(0.269 0 0);
|
|
179
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
180
|
+
--accent: oklch(0.269 0 0);
|
|
181
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
182
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
183
|
+
--border: oklch(1 0 0 / 10%);
|
|
184
|
+
--input: oklch(1 0 0 / 15%);
|
|
185
|
+
--ring: oklch(0.556 0 0);
|
|
186
|
+
--chart-1: oklch(0.87 0 0);
|
|
187
|
+
--chart-2: oklch(0.556 0 0);
|
|
188
|
+
--chart-3: oklch(0.439 0 0);
|
|
189
|
+
--chart-4: oklch(0.371 0 0);
|
|
190
|
+
--chart-5: oklch(0.269 0 0);
|
|
191
|
+
--sidebar: oklch(0.205 0 0);
|
|
192
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
193
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
194
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
195
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
196
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
197
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
198
|
+
--sidebar-ring: oklch(0.556 0 0);
|
|
199
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
1
2
|
import { defineConfig, loadEnv, lazyPlugins } from 'vite-plus'
|
|
2
3
|
import react, { reactCompilerPreset } from '@vitejs/plugin-react'
|
|
3
4
|
|
|
@@ -7,12 +8,12 @@ const env = loadEnv(mode, process.cwd(), '')
|
|
|
7
8
|
// https://vite.dev/config/
|
|
8
9
|
export default defineConfig({
|
|
9
10
|
fmt: { semi: false, singleQuote: true },
|
|
11
|
+
lint: { options: { typeAware: true, typeCheck: true } },
|
|
10
12
|
staged: {
|
|
11
13
|
'*': 'vp check --fix',
|
|
12
14
|
},
|
|
13
|
-
lint: { options: { typeAware: true, typeCheck: true } },
|
|
14
15
|
plugins: [
|
|
15
|
-
|
|
16
|
+
tailwindcss(),
|
|
16
17
|
react(),
|
|
17
18
|
// lazyPlugins 返回类型与 Vite plugins 字段类型不兼容
|
|
18
19
|
// 上游 issue: vitejs/vite#22085,官方确认为已知问题
|
|
@@ -38,6 +39,7 @@ export default defineConfig({
|
|
|
38
39
|
if (id.includes('react-dom')) return 'vendor-react-dom'
|
|
39
40
|
if (id.includes('react-router')) return 'vendor-router'
|
|
40
41
|
if (id.includes('@tanstack')) return 'vendor-query'
|
|
42
|
+
if (id.includes('radix-ui')) return 'vendor-radix'
|
|
41
43
|
if (id.includes('zustand')) return 'vendor-state'
|
|
42
44
|
if (id.includes('jotai')) return 'vendor-atom'
|
|
43
45
|
return 'vendor'
|
|
File without changes
|
|
File without changes
|