@yimingliao/cms 0.0.86 → 0.0.88

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.
@@ -0,0 +1,358 @@
1
+ import { clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+ import { useState, useEffect } from 'react';
4
+ import { UAParser } from 'ua-parser-js';
5
+ import { Slot } from '@radix-ui/react-slot';
6
+ import { cva } from 'class-variance-authority';
7
+ import { jsx } from 'react/jsx-runtime';
8
+ import { Loader2Icon } from 'lucide-react';
9
+ import * as LabelPrimitive from '@radix-ui/react-label';
10
+
11
+ // src/client/applications/ui/utils.ts
12
+ var cn = (...inputs) => {
13
+ return twMerge(clsx(inputs));
14
+ };
15
+ function useDeviceInfo() {
16
+ const [deviceInfo, setDeviceInfo] = useState(null);
17
+ useEffect(() => {
18
+ const parser = new UAParser(navigator.userAgent);
19
+ const result = parser.getResult();
20
+ setDeviceInfo({
21
+ deviceType: result.device.type || "desktop",
22
+ platform: result.os.name || "Unknown",
23
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
24
+ language: navigator.language,
25
+ screenResolution: {
26
+ width: window.screen.width,
27
+ height: window.screen.height
28
+ },
29
+ browser: result.browser.name || "Unknown",
30
+ browserVersion: result.browser.version || "",
31
+ userAgent: navigator.userAgent
32
+ });
33
+ }, []);
34
+ return deviceInfo;
35
+ }
36
+ var buttonVariants = cva(
37
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
38
+ {
39
+ variants: {
40
+ variant: {
41
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
42
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
43
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
44
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
45
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
46
+ link: "text-primary underline-offset-4 hover:underline",
47
+ success: "bg-success text-white hover:bg-success/90 focus-visible:ring-success/20 dark:focus-visible:ring-success/40 dark:bg-success/60",
48
+ warning: "bg-warning text-white hover:bg-warning/90 focus-visible:ring-warning/20 dark:focus-visible:ring-warning/40 dark:bg-warning/60"
49
+ },
50
+ size: {
51
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
52
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
53
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
54
+ icon: "size-9",
55
+ "icon-sm": "size-8",
56
+ "icon-lg": "size-10",
57
+ xs: "h-7 rounded-md px-2 text-xs has-[>svg]:px-2 gap-1"
58
+ }
59
+ },
60
+ defaultVariants: {
61
+ variant: "default",
62
+ size: "default"
63
+ }
64
+ }
65
+ );
66
+ function Button({
67
+ className,
68
+ variant,
69
+ size,
70
+ asChild = false,
71
+ ...props
72
+ }) {
73
+ const Comp = asChild ? Slot : "button";
74
+ return /* @__PURE__ */ jsx(
75
+ Comp,
76
+ {
77
+ "data-slot": "button",
78
+ className: cn(buttonVariants({ variant, size, className })),
79
+ ...props
80
+ }
81
+ );
82
+ }
83
+ function Card({ className, ...props }) {
84
+ return /* @__PURE__ */ jsx(
85
+ "div",
86
+ {
87
+ "data-slot": "card",
88
+ className: cn(
89
+ "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
90
+ className
91
+ ),
92
+ ...props
93
+ }
94
+ );
95
+ }
96
+ function CardHeader({ className, ...props }) {
97
+ return /* @__PURE__ */ jsx(
98
+ "div",
99
+ {
100
+ "data-slot": "card-header",
101
+ className: cn(
102
+ "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
103
+ className
104
+ ),
105
+ ...props
106
+ }
107
+ );
108
+ }
109
+ function CardTitle({ className, ...props }) {
110
+ return /* @__PURE__ */ jsx(
111
+ "div",
112
+ {
113
+ "data-slot": "card-title",
114
+ className: cn("leading-none font-semibold", className),
115
+ ...props
116
+ }
117
+ );
118
+ }
119
+ function CardDescription({ className, ...props }) {
120
+ return /* @__PURE__ */ jsx(
121
+ "div",
122
+ {
123
+ "data-slot": "card-description",
124
+ className: cn("text-muted-foreground text-sm", className),
125
+ ...props
126
+ }
127
+ );
128
+ }
129
+ function CardAction({ className, ...props }) {
130
+ return /* @__PURE__ */ jsx(
131
+ "div",
132
+ {
133
+ "data-slot": "card-action",
134
+ className: cn(
135
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
136
+ className
137
+ ),
138
+ ...props
139
+ }
140
+ );
141
+ }
142
+ function CardContent({ className, ...props }) {
143
+ return /* @__PURE__ */ jsx(
144
+ "div",
145
+ {
146
+ "data-slot": "card-content",
147
+ className: cn("px-6", className),
148
+ ...props
149
+ }
150
+ );
151
+ }
152
+ function CardFooter({ className, ...props }) {
153
+ return /* @__PURE__ */ jsx(
154
+ "div",
155
+ {
156
+ "data-slot": "card-footer",
157
+ className: cn("flex items-center px-6 [.border-t]:pt-6", className),
158
+ ...props
159
+ }
160
+ );
161
+ }
162
+ function Input({ className, type, ...props }) {
163
+ return /* @__PURE__ */ jsx(
164
+ "input",
165
+ {
166
+ type,
167
+ "data-slot": "input",
168
+ className: cn(
169
+ "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
170
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
171
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
172
+ className
173
+ ),
174
+ ...props
175
+ }
176
+ );
177
+ }
178
+ function Textarea({ className, ...props }) {
179
+ return /* @__PURE__ */ jsx(
180
+ "textarea",
181
+ {
182
+ "data-slot": "textarea",
183
+ className: cn(
184
+ // "min-h-16",
185
+ // "field-sizing-content",
186
+ "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
187
+ className
188
+ ),
189
+ ...props
190
+ }
191
+ );
192
+ }
193
+ function InputGroup({ className, ...props }) {
194
+ return /* @__PURE__ */ jsx(
195
+ "div",
196
+ {
197
+ "data-slot": "input-group",
198
+ role: "group",
199
+ className: cn(
200
+ "group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-md border shadow-xs transition-[color,box-shadow] outline-none",
201
+ "h-9 min-w-0 has-[>textarea]:h-auto",
202
+ // Variants based on alignment.
203
+ "has-[>[data-align=inline-start]]:[&>input]:pl-2",
204
+ "has-[>[data-align=inline-end]]:[&>input]:pr-2",
205
+ "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
206
+ "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
207
+ // Focus state.
208
+ "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]",
209
+ // Error state.
210
+ "has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
211
+ className
212
+ ),
213
+ ...props
214
+ }
215
+ );
216
+ }
217
+ var inputGroupAddonVariants = cva(
218
+ "text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
219
+ {
220
+ variants: {
221
+ align: {
222
+ "inline-start": "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
223
+ "inline-end": "order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
224
+ "block-start": "order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5",
225
+ "block-end": "order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5"
226
+ }
227
+ },
228
+ defaultVariants: {
229
+ align: "inline-start"
230
+ }
231
+ }
232
+ );
233
+ function InputGroupAddon({
234
+ className,
235
+ align = "inline-start",
236
+ ...props
237
+ }) {
238
+ return /* @__PURE__ */ jsx(
239
+ "div",
240
+ {
241
+ role: "group",
242
+ "data-slot": "input-group-addon",
243
+ "data-align": align,
244
+ className: cn(inputGroupAddonVariants({ align }), className),
245
+ onClick: (e) => {
246
+ if (e.target.closest("button")) {
247
+ return;
248
+ }
249
+ e.currentTarget.parentElement?.querySelector("input")?.focus();
250
+ },
251
+ ...props
252
+ }
253
+ );
254
+ }
255
+ var inputGroupButtonVariants = cva(
256
+ "text-sm shadow-none flex gap-2 items-center",
257
+ {
258
+ variants: {
259
+ size: {
260
+ xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
261
+ sm: "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
262
+ "icon-xs": "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
263
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0"
264
+ }
265
+ },
266
+ defaultVariants: {
267
+ size: "xs"
268
+ }
269
+ }
270
+ );
271
+ function InputGroupButton({
272
+ className,
273
+ type = "button",
274
+ variant = "ghost",
275
+ size = "xs",
276
+ ...props
277
+ }) {
278
+ return /* @__PURE__ */ jsx(
279
+ Button,
280
+ {
281
+ type,
282
+ "data-size": size,
283
+ variant,
284
+ className: cn(inputGroupButtonVariants({ size }), className),
285
+ ...props
286
+ }
287
+ );
288
+ }
289
+ function InputGroupText({ className, ...props }) {
290
+ return /* @__PURE__ */ jsx(
291
+ "span",
292
+ {
293
+ className: cn(
294
+ "text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
295
+ className
296
+ ),
297
+ ...props
298
+ }
299
+ );
300
+ }
301
+ function InputGroupInput({
302
+ className,
303
+ ...props
304
+ }) {
305
+ return /* @__PURE__ */ jsx(
306
+ Input,
307
+ {
308
+ "data-slot": "input-group-control",
309
+ className: cn(
310
+ "flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
311
+ className
312
+ ),
313
+ ...props
314
+ }
315
+ );
316
+ }
317
+ function InputGroupTextarea({
318
+ className,
319
+ ...props
320
+ }) {
321
+ return /* @__PURE__ */ jsx(
322
+ Textarea,
323
+ {
324
+ "data-slot": "input-group-control",
325
+ className: cn(
326
+ "flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
327
+ className
328
+ ),
329
+ ...props
330
+ }
331
+ );
332
+ }
333
+ function Spinner({ className, ...props }) {
334
+ return /* @__PURE__ */ jsx(
335
+ Loader2Icon,
336
+ {
337
+ role: "status",
338
+ "aria-label": "Loading",
339
+ className: cn("size-4 animate-spin", className),
340
+ ...props
341
+ }
342
+ );
343
+ }
344
+ function Label({ className, ...props }) {
345
+ return /* @__PURE__ */ jsx(
346
+ LabelPrimitive.Root,
347
+ {
348
+ "data-slot": "label",
349
+ className: cn(
350
+ "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",
351
+ className
352
+ ),
353
+ ...props
354
+ }
355
+ );
356
+ }
357
+
358
+ export { Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, Label, Spinner, Textarea, cn, useDeviceInfo };
@@ -6,10 +6,9 @@ import * as _tanstack_query_core from '@tanstack/query-core';
6
6
  import * as react_jsx_runtime from 'react/jsx-runtime';
7
7
  import { c as createVerifyAction } from '../create-verify-action-DBwWOXPO.js';
8
8
  import * as React$1 from 'react';
9
- import { ComponentProps } from 'react';
9
+ import { ComponentProps, ReactNode, HTMLAttributes } from 'react';
10
+ import { L as LabelProps, B as ButtonProps$1 } from '../label-BF4qxS03.js';
10
11
  import { LucideIcon } from 'lucide-react';
11
- import * as class_variance_authority_types from 'class-variance-authority/types';
12
- import { VariantProps } from 'class-variance-authority';
13
12
  import { ClassValue } from 'clsx';
14
13
  import '../types-J25u1G6t.js';
15
14
  import '../types-0oS1A2K5.js';
@@ -22,6 +21,9 @@ import 'nodemailer';
22
21
  import 'intor';
23
22
  import 'keyv';
24
23
  import 'zod/v4/core';
24
+ import 'class-variance-authority/types';
25
+ import 'class-variance-authority';
26
+ import '@radix-ui/react-label';
25
27
 
26
28
  interface UIStates {
27
29
  isLoading?: boolean;
@@ -349,39 +351,21 @@ declare function createAdminInitializer({ useAdmin, useQuery, verifyAction, }: {
349
351
 
350
352
  declare function Form({ onSubmit, className, ...props }: ComponentProps<"form">): react_jsx_runtime.JSX.Element;
351
353
 
352
- declare const buttonVariants: (props?: ({
353
- variant?: "link" | "default" | "success" | "destructive" | "outline" | "secondary" | "ghost" | "warning" | null | undefined;
354
- size?: "lg" | "sm" | "default" | "icon" | "icon-sm" | "icon-lg" | "xs" | null | undefined;
355
- } & class_variance_authority_types.ClassProp) | undefined) => string;
356
- type ButtonProps$1 = React$1.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
357
- asChild?: boolean;
358
- };
359
- declare function Button$1({ className, variant, size, asChild, ...props }: ButtonProps$1): react_jsx_runtime.JSX.Element;
360
-
361
- declare function Card({ className, ...props }: React$1.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
362
- declare function CardHeader({ className, ...props }: React$1.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
363
- declare function CardTitle({ className, ...props }: React$1.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
364
- declare function CardDescription({ className, ...props }: React$1.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
365
- declare function CardAction({ className, ...props }: React$1.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
366
- declare function CardContent({ className, ...props }: React$1.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
367
- declare function CardFooter({ className, ...props }: React$1.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
368
-
369
- declare function InputGroup({ className, ...props }: React$1.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
370
- declare const inputGroupAddonVariants: (props?: ({
371
- align?: "inline-start" | "inline-end" | "block-start" | "block-end" | null | undefined;
372
- } & class_variance_authority_types.ClassProp) | undefined) => string;
373
- declare function InputGroupAddon({ className, align, ...props }: React$1.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>): react_jsx_runtime.JSX.Element;
374
- declare const inputGroupButtonVariants: (props?: ({
375
- size?: "sm" | "icon-sm" | "xs" | "icon-xs" | null | undefined;
376
- } & class_variance_authority_types.ClassProp) | undefined) => string;
377
- declare function InputGroupButton({ className, type, variant, size, ...props }: Omit<React$1.ComponentProps<typeof Button$1>, "size"> & VariantProps<typeof inputGroupButtonVariants>): react_jsx_runtime.JSX.Element;
378
- declare function InputGroupText({ className, ...props }: React$1.ComponentProps<"span">): react_jsx_runtime.JSX.Element;
379
- declare function InputGroupInput({ className, ...props }: React$1.ComponentProps<"input">): react_jsx_runtime.JSX.Element;
380
- declare function InputGroupTextarea({ className, ...props }: React$1.ComponentProps<"textarea">): react_jsx_runtime.JSX.Element;
381
-
382
- declare function Spinner({ className, ...props }: ComponentProps<"svg">): react_jsx_runtime.JSX.Element;
354
+ interface FieldProps extends LabelProps {
355
+ label: ReactNode;
356
+ isRequired?: boolean;
357
+ hint?: ReactNode;
358
+ labelChildren?: ReactNode;
359
+ children: ReactNode;
360
+ }
361
+ declare function Field({ label, isRequired, hint, labelChildren, children, ...props }: FieldProps): react_jsx_runtime.JSX.Element;
383
362
 
384
- declare function Textarea({ className, ...props }: React$1.ComponentProps<"textarea">): react_jsx_runtime.JSX.Element;
363
+ interface FieldBodyProps extends HTMLAttributes<HTMLDivElement>, UIStates {
364
+ isEmpty?: boolean;
365
+ backgroundClassName?: string;
366
+ childrenClassName?: string;
367
+ }
368
+ declare function FieldBody({ isLoading, isDisabled, isEmpty, className, backgroundClassName, childrenClassName, children, ...props }: FieldBodyProps): react_jsx_runtime.JSX.Element;
385
369
 
386
370
  interface ButtonProps extends ButtonProps$1, UIStates {
387
371
  icon?: LucideIcon;
@@ -394,8 +378,10 @@ interface InputProps<T = Record<string, unknown>> extends ComponentProps<"input"
394
378
  }
395
379
  declare function Input<T>({ fieldName, setFormData, isLoading, isDisabled, isError, children, ...props }: InputProps<T>): react_jsx_runtime.JSX.Element;
396
380
 
381
+ declare function PasswordInput<T>({ ...props }: InputProps<T>): react_jsx_runtime.JSX.Element;
382
+
397
383
  declare const cn: (...inputs: ClassValue[]) => string;
398
384
 
399
385
  declare function useDeviceInfo(): DeviceInfo | null;
400
386
 
401
- export { AdminProvider, Button, type ButtonProps, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Form, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, type InputProps, type ShowToastOption, Spinner, Textarea, cn, createAdminInitializer, createRequestInterceptor, createResponseInterceptor, createSmartFetch, createUseCommand, createUseQuery, handleToast, useAdmin, useDeviceInfo };
387
+ export { AdminProvider, Button, type ButtonProps, Field, FieldBody, Form, Input, type InputProps, PasswordInput, type ShowToastOption, cn, createAdminInitializer, createRequestInterceptor, createResponseInterceptor, createSmartFetch, createUseCommand, createUseQuery, handleToast, useAdmin, useDeviceInfo };
@@ -1,16 +1,14 @@
1
+ import { cn, Label, Spinner, Button, InputGroup, InputGroupAddon, InputGroupInput, InputGroupButton } from '../chunk-OQGJBZXQ.js';
2
+ export { cn, useDeviceInfo } from '../chunk-OQGJBZXQ.js';
1
3
  import { ensureArray } from '../chunk-OAENV763.js';
2
4
  import { toast } from 'sonner';
3
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
6
  import { useMutation, useQuery } from '@tanstack/react-query';
5
- import * as React6 from 'react';
7
+ import * as React from 'react';
6
8
  import { createContext, useState, useContext, useEffect } from 'react';
7
- import { clsx } from 'clsx';
8
- import { twMerge } from 'tailwind-merge';
9
- import { UAParser } from 'ua-parser-js';
9
+ import { Asterisk, Eye, EyeOff } from 'lucide-react';
10
+ import { useTranslator } from 'intor/react';
10
11
  import { useRouter } from 'next/navigation';
11
- import { Slot } from '@radix-ui/react-slot';
12
- import { cva } from 'class-variance-authority';
13
- import { Loader2Icon } from 'lucide-react';
14
12
 
15
13
  // src/client/infrastructure/fetch/smart-fetch.ts
16
14
  function createSmartFetch({
@@ -242,30 +240,6 @@ function createAdminInitializer({
242
240
  return null;
243
241
  };
244
242
  }
245
- var cn = (...inputs) => {
246
- return twMerge(clsx(inputs));
247
- };
248
- function useDeviceInfo() {
249
- const [deviceInfo, setDeviceInfo] = useState(null);
250
- useEffect(() => {
251
- const parser = new UAParser(navigator.userAgent);
252
- const result = parser.getResult();
253
- setDeviceInfo({
254
- deviceType: result.device.type || "desktop",
255
- platform: result.os.name || "Unknown",
256
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
257
- language: navigator.language,
258
- screenResolution: {
259
- width: window.screen.width,
260
- height: window.screen.height
261
- },
262
- browser: result.browser.name || "Unknown",
263
- browserVersion: result.browser.version || "",
264
- userAgent: navigator.userAgent
265
- });
266
- }, []);
267
- return deviceInfo;
268
- }
269
243
  function Form({
270
244
  onSubmit,
271
245
  className,
@@ -278,311 +252,78 @@ function Form({
278
252
  };
279
253
  return /* @__PURE__ */ jsx("form", { className: cn(className), onSubmit: handleSubmit, ...props });
280
254
  }
281
- var buttonVariants = cva(
282
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
283
- {
284
- variants: {
285
- variant: {
286
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
287
- destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
288
- outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
289
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
290
- ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
291
- link: "text-primary underline-offset-4 hover:underline",
292
- success: "bg-success text-white hover:bg-success/90 focus-visible:ring-success/20 dark:focus-visible:ring-success/40 dark:bg-success/60",
293
- warning: "bg-warning text-white hover:bg-warning/90 focus-visible:ring-warning/20 dark:focus-visible:ring-warning/40 dark:bg-warning/60"
294
- },
295
- size: {
296
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
297
- sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
298
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
299
- icon: "size-9",
300
- "icon-sm": "size-8",
301
- "icon-lg": "size-10",
302
- xs: "h-7 rounded-md px-2 text-xs has-[>svg]:px-2 gap-1"
303
- }
304
- },
305
- defaultVariants: {
306
- variant: "default",
307
- size: "default"
308
- }
309
- }
310
- );
311
- function Button({
312
- className,
313
- variant,
314
- size,
315
- asChild = false,
255
+ function Field({
256
+ label,
257
+ isRequired = false,
258
+ hint,
259
+ labelChildren,
260
+ children,
316
261
  ...props
317
262
  }) {
318
- const Comp = asChild ? Slot : "button";
319
- return /* @__PURE__ */ jsx(
320
- Comp,
321
- {
322
- "data-slot": "button",
323
- className: cn(buttonVariants({ variant, size, className })),
324
- ...props
325
- }
326
- );
327
- }
328
- function Card({ className, ...props }) {
329
- return /* @__PURE__ */ jsx(
330
- "div",
331
- {
332
- "data-slot": "card",
333
- className: cn(
334
- "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
335
- className
336
- ),
337
- ...props
338
- }
339
- );
340
- }
341
- function CardHeader({ className, ...props }) {
342
- return /* @__PURE__ */ jsx(
343
- "div",
344
- {
345
- "data-slot": "card-header",
346
- className: cn(
347
- "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
348
- className
349
- ),
350
- ...props
351
- }
352
- );
353
- }
354
- function CardTitle({ className, ...props }) {
355
- return /* @__PURE__ */ jsx(
356
- "div",
357
- {
358
- "data-slot": "card-title",
359
- className: cn("leading-none font-semibold", className),
360
- ...props
361
- }
362
- );
363
- }
364
- function CardDescription({ className, ...props }) {
365
- return /* @__PURE__ */ jsx(
366
- "div",
367
- {
368
- "data-slot": "card-description",
369
- className: cn("text-muted-foreground text-sm", className),
370
- ...props
371
- }
372
- );
373
- }
374
- function CardAction({ className, ...props }) {
375
- return /* @__PURE__ */ jsx(
376
- "div",
377
- {
378
- "data-slot": "card-action",
379
- className: cn(
380
- "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
381
- className
382
- ),
383
- ...props
384
- }
385
- );
386
- }
387
- function CardContent({ className, ...props }) {
388
- return /* @__PURE__ */ jsx(
389
- "div",
390
- {
391
- "data-slot": "card-content",
392
- className: cn("px-6", className),
393
- ...props
394
- }
395
- );
396
- }
397
- function CardFooter({ className, ...props }) {
398
- return /* @__PURE__ */ jsx(
399
- "div",
400
- {
401
- "data-slot": "card-footer",
402
- className: cn("flex items-center px-6 [.border-t]:pt-6", className),
403
- ...props
404
- }
405
- );
406
- }
407
- function Input({ className, type, ...props }) {
408
- return /* @__PURE__ */ jsx(
409
- "input",
410
- {
411
- type,
412
- "data-slot": "input",
413
- className: cn(
414
- "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
415
- "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
416
- "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
417
- className
418
- ),
419
- ...props
420
- }
421
- );
422
- }
423
- function Textarea({ className, ...props }) {
424
- return /* @__PURE__ */ jsx(
425
- "textarea",
426
- {
427
- "data-slot": "textarea",
428
- className: cn(
429
- // "min-h-16",
430
- // "field-sizing-content",
431
- "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
432
- className
433
- ),
434
- ...props
435
- }
436
- );
437
- }
438
- function InputGroup({ className, ...props }) {
439
- return /* @__PURE__ */ jsx(
440
- "div",
441
- {
442
- "data-slot": "input-group",
443
- role: "group",
444
- className: cn(
445
- "group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-md border shadow-xs transition-[color,box-shadow] outline-none",
446
- "h-9 min-w-0 has-[>textarea]:h-auto",
447
- // Variants based on alignment.
448
- "has-[>[data-align=inline-start]]:[&>input]:pl-2",
449
- "has-[>[data-align=inline-end]]:[&>input]:pr-2",
450
- "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
451
- "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
452
- // Focus state.
453
- "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]",
454
- // Error state.
455
- "has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
456
- className
457
- ),
458
- ...props
459
- }
460
- );
263
+ return /* @__PURE__ */ jsxs("div", { className: "grid w-full items-center gap-3", children: [
264
+ /* @__PURE__ */ jsxs("span", { className: "flex gap-2", children: [
265
+ /* @__PURE__ */ jsxs(Label, { className: "flex gap-1 truncate", ...props, children: [
266
+ label,
267
+ isRequired && /* @__PURE__ */ jsx(Asterisk, { className: "text-destructive size-3" }),
268
+ hint && /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: hint })
269
+ ] }),
270
+ /* @__PURE__ */ jsx("span", { children: labelChildren })
271
+ ] }),
272
+ children
273
+ ] });
461
274
  }
462
- var inputGroupAddonVariants = cva(
463
- "text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
464
- {
465
- variants: {
466
- align: {
467
- "inline-start": "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
468
- "inline-end": "order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
469
- "block-start": "order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5",
470
- "block-end": "order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5"
471
- }
472
- },
473
- defaultVariants: {
474
- align: "inline-start"
475
- }
476
- }
477
- );
478
- function InputGroupAddon({
275
+ function FieldBody({
276
+ // ui states
277
+ isLoading = false,
278
+ isDisabled = false,
279
+ isEmpty = false,
280
+ // base
479
281
  className,
480
- align = "inline-start",
282
+ backgroundClassName,
283
+ childrenClassName,
284
+ children,
481
285
  ...props
482
286
  }) {
483
- return /* @__PURE__ */ jsx(
287
+ const { t } = useTranslator();
288
+ return /* @__PURE__ */ jsxs(
484
289
  "div",
485
290
  {
486
- role: "group",
487
- "data-slot": "input-group-addon",
488
- "data-align": align,
489
- className: cn(inputGroupAddonVariants({ align }), className),
490
- onClick: (e) => {
491
- if (e.target.closest("button")) {
492
- return;
493
- }
494
- e.currentTarget.parentElement?.querySelector("input")?.focus();
495
- },
496
- ...props
497
- }
498
- );
499
- }
500
- var inputGroupButtonVariants = cva(
501
- "text-sm shadow-none flex gap-2 items-center",
502
- {
503
- variants: {
504
- size: {
505
- xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
506
- sm: "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
507
- "icon-xs": "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
508
- "icon-sm": "size-8 p-0 has-[>svg]:p-0"
509
- }
510
- },
511
- defaultVariants: {
512
- size: "xs"
513
- }
514
- }
515
- );
516
- function InputGroupButton({
517
- className,
518
- type = "button",
519
- variant = "ghost",
520
- size = "xs",
521
- ...props
522
- }) {
523
- return /* @__PURE__ */ jsx(
524
- Button,
525
- {
526
- type,
527
- "data-size": size,
528
- variant,
529
- className: cn(inputGroupButtonVariants({ size }), className),
530
- ...props
531
- }
532
- );
533
- }
534
- function InputGroupText({ className, ...props }) {
535
- return /* @__PURE__ */ jsx(
536
- "span",
537
- {
538
- className: cn(
539
- "text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
540
- className
541
- ),
542
- ...props
543
- }
544
- );
545
- }
546
- function InputGroupInput({
547
- className,
548
- ...props
549
- }) {
550
- return /* @__PURE__ */ jsx(
551
- Input,
552
- {
553
- "data-slot": "input-group-control",
554
- className: cn(
555
- "flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
556
- className
557
- ),
558
- ...props
559
- }
560
- );
561
- }
562
- function InputGroupTextarea({
563
- className,
564
- ...props
565
- }) {
566
- return /* @__PURE__ */ jsx(
567
- Textarea,
568
- {
569
- "data-slot": "input-group-control",
570
291
  className: cn(
571
- "flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
572
- className
292
+ className,
293
+ "relative",
294
+ "min-h-9 w-full min-w-0",
295
+ "flex items-center",
296
+ "text-sm"
573
297
  ),
574
- ...props
575
- }
576
- );
577
- }
578
- function Spinner({ className, ...props }) {
579
- return /* @__PURE__ */ jsx(
580
- Loader2Icon,
581
- {
582
- role: "status",
583
- "aria-label": "Loading",
584
- className: cn("size-4 animate-spin", className),
585
- ...props
298
+ ...props,
299
+ children: [
300
+ /* @__PURE__ */ jsx(
301
+ "div",
302
+ {
303
+ className: cn(
304
+ "absolute size-full",
305
+ "dark:bg-input/30 bg-foreground/2 rounded-md",
306
+ backgroundClassName
307
+ )
308
+ }
309
+ ),
310
+ isLoading && /* @__PURE__ */ jsx("div", { className: "px-3", children: /* @__PURE__ */ jsx(Spinner, {}) }),
311
+ !isLoading && (!children || isEmpty) && /* @__PURE__ */ jsx("div", { className: "flex-center h-9 px-3 opacity-50", children: /* @__PURE__ */ jsx("p", { className: "opacity-50", children: t("ui.no-data.text") }) }),
312
+ !isLoading && children && !isEmpty && /* @__PURE__ */ jsx(
313
+ "div",
314
+ {
315
+ className: cn(
316
+ "relative size-full",
317
+ "flex items-center gap-3",
318
+ "px-3 py-2",
319
+ "break-all",
320
+ (isDisabled || isLoading) && "opacity-50",
321
+ childrenClassName
322
+ ),
323
+ children
324
+ }
325
+ )
326
+ ]
586
327
  }
587
328
  );
588
329
  }
@@ -614,13 +355,13 @@ function Button2({
614
355
  onClick: props.onClick ?? handleClick,
615
356
  ...props,
616
357
  children: isLoading ? /* @__PURE__ */ jsx(Spinner, {}) : /* @__PURE__ */ jsxs(Fragment, { children: [
617
- icon && React6.createElement(icon),
358
+ icon && React.createElement(icon),
618
359
  children
619
360
  ] })
620
361
  }
621
362
  );
622
363
  }
623
- function Input2({
364
+ function Input({
624
365
  // form context
625
366
  fieldName,
626
367
  setFormData,
@@ -648,5 +389,18 @@ function Input2({
648
389
  children
649
390
  ] });
650
391
  }
392
+ function PasswordInput({ ...props }) {
393
+ const [showPassword, setShowPassword] = useState(false);
394
+ return /* @__PURE__ */ jsx(Input, { type: showPassword ? "text" : "password", ...props, children: /* @__PURE__ */ jsx(InputGroupAddon, { align: "inline-end", children: /* @__PURE__ */ jsx(
395
+ InputGroupButton,
396
+ {
397
+ "aria-label": showPassword ? "Hide password" : "Show password",
398
+ title: showPassword ? "Hide password" : "Show password",
399
+ size: "icon-xs",
400
+ onClick: () => setShowPassword((prev) => !prev),
401
+ children: showPassword ? /* @__PURE__ */ jsx(Eye, {}) : /* @__PURE__ */ jsx(EyeOff, {})
402
+ }
403
+ ) }) });
404
+ }
651
405
 
652
- export { AdminProvider, Button2 as Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Form, Input2 as Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, Spinner, Textarea, cn, createAdminInitializer, createRequestInterceptor, createResponseInterceptor, createSmartFetch, createUseCommand, createUseQuery, handleToast, useAdmin, useDeviceInfo };
406
+ export { AdminProvider, Button2 as Button, Field, FieldBody, Form, Input, PasswordInput, createAdminInitializer, createRequestInterceptor, createResponseInterceptor, createSmartFetch, createUseCommand, createUseQuery, handleToast, useAdmin };
@@ -0,0 +1,37 @@
1
+ import { a as Button } from '../../label-BF4qxS03.js';
2
+ export { B as ButtonProps, b as Label, L as LabelProps } from '../../label-BF4qxS03.js';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import * as React from 'react';
5
+ import { ComponentProps } from 'react';
6
+ import * as class_variance_authority_types from 'class-variance-authority/types';
7
+ import { VariantProps } from 'class-variance-authority';
8
+ import '@radix-ui/react-label';
9
+
10
+ declare function Card({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
11
+ declare function CardHeader({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
12
+ declare function CardTitle({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
13
+ declare function CardDescription({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
14
+ declare function CardAction({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
15
+ declare function CardContent({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
16
+ declare function CardFooter({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
17
+
18
+ declare function Input({ className, type, ...props }: React.ComponentProps<"input">): react_jsx_runtime.JSX.Element;
19
+
20
+ declare function InputGroup({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
21
+ declare const inputGroupAddonVariants: (props?: ({
22
+ align?: "inline-start" | "inline-end" | "block-start" | "block-end" | null | undefined;
23
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
24
+ declare function InputGroupAddon({ className, align, ...props }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>): react_jsx_runtime.JSX.Element;
25
+ declare const inputGroupButtonVariants: (props?: ({
26
+ size?: "sm" | "icon-sm" | "xs" | "icon-xs" | null | undefined;
27
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
28
+ declare function InputGroupButton({ className, type, variant, size, ...props }: Omit<React.ComponentProps<typeof Button>, "size"> & VariantProps<typeof inputGroupButtonVariants>): react_jsx_runtime.JSX.Element;
29
+ declare function InputGroupText({ className, ...props }: React.ComponentProps<"span">): react_jsx_runtime.JSX.Element;
30
+ declare function InputGroupInput({ className, ...props }: React.ComponentProps<"input">): react_jsx_runtime.JSX.Element;
31
+ declare function InputGroupTextarea({ className, ...props }: React.ComponentProps<"textarea">): react_jsx_runtime.JSX.Element;
32
+
33
+ declare function Spinner({ className, ...props }: ComponentProps<"svg">): react_jsx_runtime.JSX.Element;
34
+
35
+ declare function Textarea({ className, ...props }: React.ComponentProps<"textarea">): react_jsx_runtime.JSX.Element;
36
+
37
+ export { Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, Spinner, Textarea };
@@ -0,0 +1 @@
1
+ export { Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, Label, Spinner, Textarea } from '../../chunk-OQGJBZXQ.js';
@@ -0,0 +1,19 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as class_variance_authority_types from 'class-variance-authority/types';
3
+ import { VariantProps } from 'class-variance-authority';
4
+ import * as React from 'react';
5
+ import * as LabelPrimitive from '@radix-ui/react-label';
6
+
7
+ declare const buttonVariants: (props?: ({
8
+ variant?: "link" | "default" | "success" | "destructive" | "outline" | "secondary" | "ghost" | "warning" | null | undefined;
9
+ size?: "lg" | "sm" | "default" | "icon" | "icon-sm" | "icon-lg" | "xs" | null | undefined;
10
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
11
+ type ButtonProps = React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
12
+ asChild?: boolean;
13
+ };
14
+ declare function Button({ className, variant, size, asChild, ...props }: ButtonProps): react_jsx_runtime.JSX.Element;
15
+
16
+ type LabelProps = React.ComponentProps<typeof LabelPrimitive.Root>;
17
+ declare function Label({ className, ...props }: LabelProps): react_jsx_runtime.JSX.Element;
18
+
19
+ export { type ButtonProps as B, type LabelProps as L, Button as a, Label as b };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yimingliao/cms",
3
- "version": "0.0.86",
3
+ "version": "0.0.88",
4
4
  "author": "Yiming Liao",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -15,6 +15,11 @@
15
15
  "import": "./dist/client/index.js",
16
16
  "require": "./dist/client/index.js"
17
17
  },
18
+ "./client/shadcn": {
19
+ "types": "./dist/client/shadcn/index.d.ts",
20
+ "import": "./dist/client/shadcn/index.js",
21
+ "require": "./dist/client/shadcn/index.js"
22
+ },
18
23
  "./server": {
19
24
  "types": "./dist/server/index.d.ts",
20
25
  "import": "./dist/server/index.js",
@@ -44,6 +49,7 @@
44
49
  },
45
50
  "dependencies": {
46
51
  "@keyv/redis": "^5.1.6",
52
+ "@radix-ui/react-label": "^2.1.8",
47
53
  "@radix-ui/react-slot": "^1.2.4",
48
54
  "argon2": "^0.44.0",
49
55
  "class-variance-authority": "^0.7.1",
@@ -80,6 +86,8 @@
80
86
  "intor": "^2.5.0",
81
87
  "knip": "^5.86.0",
82
88
  "next": "^16.1.6",
89
+ "prettier": "^3.8.1",
90
+ "prettier-plugin-tailwindcss": "^0.7.2",
83
91
  "prisma": "6.5.0",
84
92
  "react": "^19.2.4",
85
93
  "tailwind-merge": "^3.5.0",