@exxatdesignux/ui 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/package.json +72 -0
  2. package/src/components/ui/avatar.tsx +384 -0
  3. package/src/components/ui/badge.tsx +49 -0
  4. package/src/components/ui/banner.tsx +364 -0
  5. package/src/components/ui/breadcrumb.tsx +120 -0
  6. package/src/components/ui/button.tsx +66 -0
  7. package/src/components/ui/calendar.tsx +220 -0
  8. package/src/components/ui/card.tsx +136 -0
  9. package/src/components/ui/chart.tsx +378 -0
  10. package/src/components/ui/checkbox.tsx +160 -0
  11. package/src/components/ui/coach-mark.tsx +361 -0
  12. package/src/components/ui/collapsible.tsx +33 -0
  13. package/src/components/ui/command.tsx +232 -0
  14. package/src/components/ui/date-picker-field.tsx +186 -0
  15. package/src/components/ui/dialog.tsx +171 -0
  16. package/src/components/ui/drag-handle-grip.tsx +10 -0
  17. package/src/components/ui/drawer.tsx +134 -0
  18. package/src/components/ui/dropdown-menu.tsx +422 -0
  19. package/src/components/ui/field.tsx +238 -0
  20. package/src/components/ui/form.tsx +137 -0
  21. package/src/components/ui/input-group.tsx +156 -0
  22. package/src/components/ui/input-mask.tsx +135 -0
  23. package/src/components/ui/input.tsx +22 -0
  24. package/src/components/ui/kbd.tsx +55 -0
  25. package/src/components/ui/label.tsx +25 -0
  26. package/src/components/ui/payment-card-fields.tsx +65 -0
  27. package/src/components/ui/popover.tsx +46 -0
  28. package/src/components/ui/radio-group.tsx +217 -0
  29. package/src/components/ui/select.tsx +191 -0
  30. package/src/components/ui/selection-tile-grid.tsx +246 -0
  31. package/src/components/ui/separator.tsx +28 -0
  32. package/src/components/ui/sheet.tsx +147 -0
  33. package/src/components/ui/sidebar.tsx +716 -0
  34. package/src/components/ui/skeleton.tsx +13 -0
  35. package/src/components/ui/sonner.tsx +39 -0
  36. package/src/components/ui/status-badge.tsx +109 -0
  37. package/src/components/ui/table.tsx +117 -0
  38. package/src/components/ui/tabs.tsx +90 -0
  39. package/src/components/ui/textarea.tsx +18 -0
  40. package/src/components/ui/tip.tsx +21 -0
  41. package/src/components/ui/toggle-group.tsx +89 -0
  42. package/src/components/ui/toggle-switch.tsx +31 -0
  43. package/src/components/ui/toggle.tsx +48 -0
  44. package/src/components/ui/tooltip.tsx +59 -0
  45. package/src/components/ui/view-segmented-control.tsx +160 -0
  46. package/src/globals.css +1795 -0
  47. package/src/hooks/.gitkeep +0 -0
  48. package/src/hooks/use-app-theme.ts +172 -0
  49. package/src/hooks/use-coach-mark.ts +342 -0
  50. package/src/hooks/use-mobile.ts +31 -0
  51. package/src/hooks/use-mod-key-label.ts +29 -0
  52. package/src/index.ts +55 -0
  53. package/src/lib/compose-refs.ts +15 -0
  54. package/src/lib/date-filter.ts +67 -0
  55. package/src/lib/utils.ts +6 -0
  56. package/src/theme/apply-windows-contrast-theme.ts +29 -0
  57. package/src/theme/windows-contrast-theme.json +147 -0
  58. package/src/theme.css +1130 -0
  59. package/src/types/react-payment-inputs.d.ts +20 -0
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@exxatdesignux/ui",
3
+ "version": "0.0.5",
4
+ "description": "Exxat shared design system (components, hooks, tokens). Monorepo setup: clone repo then pnpm bootstrap at workspace root — see github.com/ExxatDesign/Exxat-DS-Workspace README.",
5
+ "type": "module",
6
+ "private": false,
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "registry": "https://registry.npmjs.org/"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/ExxatDesign/Exxat-DS-Workspace.git",
14
+ "directory": "packages/ui"
15
+ },
16
+ "sideEffects": [
17
+ "**/*.css"
18
+ ],
19
+ "exports": {
20
+ ".": {
21
+ "types": "./src/index.ts",
22
+ "default": "./src/index.ts"
23
+ },
24
+ "./globals.css": "./src/globals.css",
25
+ "./theme.css": "./src/theme.css",
26
+ "./components/*": "./src/components/ui/*",
27
+ "./hooks/*": "./src/hooks/*",
28
+ "./lib/*": "./src/lib/*"
29
+ },
30
+ "main": "./src/index.ts",
31
+ "types": "./src/index.ts",
32
+ "files": [
33
+ "src"
34
+ ],
35
+ "dependencies": {
36
+ "@hookform/resolvers": "^5.2.2",
37
+ "@radix-ui/react-dialog": "^1.1.14",
38
+ "class-variance-authority": "^0.7.1",
39
+ "clsx": "^2.1.1",
40
+ "cmdk": "^1.1.1",
41
+ "lucide-react": "^0.577.0",
42
+ "radix-ui": "^1.4.3",
43
+ "react-day-picker": "^9.14.0",
44
+ "react-hook-form": "^7.72.0",
45
+ "react-payment-inputs": "^1.2.0",
46
+ "recharts": "^2.15.4",
47
+ "shadcn": "^4.1.0",
48
+ "sonner": "^2.0.7",
49
+ "tailwind-merge": "^3.5.0",
50
+ "tw-animate-css": "^1.4.0",
51
+ "use-mask-input": "^3.9.0",
52
+ "vaul": "^1.1.2",
53
+ "zod": "^4.3.6"
54
+ },
55
+ "devDependencies": {
56
+ "@tailwindcss/postcss": "^4.2.1",
57
+ "@types/react": "^19.2.14",
58
+ "@types/react-dom": "^19.2.3",
59
+ "postcss": "^8",
60
+ "tailwindcss": "^4.2.1",
61
+ "typescript": "^5.9.3"
62
+ },
63
+ "peerDependencies": {
64
+ "next-themes": ">=0.4",
65
+ "react": "^18 || ^19",
66
+ "react-dom": "^18 || ^19"
67
+ },
68
+ "scripts": {
69
+ "typecheck": "tsc --noEmit",
70
+ "lint": "eslint src/"
71
+ }
72
+ }
@@ -0,0 +1,384 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Check, Plus } from "lucide-react"
5
+ import { Avatar as AvatarPrimitive } from "radix-ui"
6
+ import { cva, type VariantProps } from "class-variance-authority"
7
+
8
+ import { cn } from "../../lib/utils"
9
+ import { Badge } from "./badge"
10
+
11
+ /** Maps to Shadcn Studio avatar demos (shape / ring / stack). Uses theme tokens only. */
12
+ const avatarRootVariants = cva(
13
+ "group/avatar relative flex shrink-0 overflow-hidden select-none",
14
+ {
15
+ variants: {
16
+ size: {
17
+ sm: "size-6",
18
+ default: "size-8",
19
+ lg: "size-10",
20
+ },
21
+ shape: {
22
+ circle: "rounded-full",
23
+ square: "rounded-none",
24
+ "rounded-sm": "rounded-sm",
25
+ "rounded-md": "rounded-md",
26
+ "rounded-lg": "rounded-lg",
27
+ "rounded-xl": "rounded-xl",
28
+ },
29
+ variant: {
30
+ default: "",
31
+ ring: "ring-2 ring-ring",
32
+ "ring-offset": "ring-2 ring-ring ring-offset-2 ring-offset-background",
33
+ group: "ring-2 ring-background",
34
+ },
35
+ },
36
+ defaultVariants: {
37
+ size: "default",
38
+ shape: "circle",
39
+ variant: "default",
40
+ },
41
+ }
42
+ )
43
+
44
+ const AVATAR_INSET_AFTER: Record<NonNullable<VariantProps<typeof avatarRootVariants>["shape"]>, string> = {
45
+ circle: "after:rounded-full",
46
+ square: "after:rounded-none",
47
+ "rounded-sm": "after:rounded-sm",
48
+ "rounded-md": "after:rounded-md",
49
+ "rounded-lg": "after:rounded-lg",
50
+ "rounded-xl": "after:rounded-xl",
51
+ }
52
+
53
+ const avatarImageShape = cn(
54
+ "aspect-square size-full object-cover",
55
+ "group-data-[shape=circle]/avatar:rounded-full",
56
+ "group-data-[shape=square]/avatar:rounded-none",
57
+ "group-data-[shape=rounded-sm]/avatar:rounded-sm",
58
+ "group-data-[shape=rounded-md]/avatar:rounded-md",
59
+ "group-data-[shape=rounded-lg]/avatar:rounded-lg",
60
+ "group-data-[shape=rounded-xl]/avatar:rounded-xl"
61
+ )
62
+
63
+ const avatarFallbackShape = cn(
64
+ "flex size-full items-center justify-center bg-muted text-sm text-muted-foreground",
65
+ "group-data-[shape=circle]/avatar:rounded-full",
66
+ "group-data-[shape=square]/avatar:rounded-none",
67
+ "group-data-[shape=rounded-sm]/avatar:rounded-sm",
68
+ "group-data-[shape=rounded-md]/avatar:rounded-md",
69
+ "group-data-[shape=rounded-lg]/avatar:rounded-lg",
70
+ "group-data-[shape=rounded-xl]/avatar:rounded-xl",
71
+ "group-data-[size=sm]/avatar:text-xs"
72
+ )
73
+
74
+ type AvatarProps = React.ComponentProps<typeof AvatarPrimitive.Root> &
75
+ VariantProps<typeof avatarRootVariants> & {
76
+ /**
77
+ * Subtle inner hairline on the avatar edge (token `border-border`).
78
+ * Default **false** — full-bleed circle for photos/logos (Shadcn Studio–style profile look).
79
+ */
80
+ insetBorder?: boolean
81
+ }
82
+
83
+ function Avatar({
84
+ className,
85
+ size = "default",
86
+ shape = "circle",
87
+ variant = "default",
88
+ insetBorder = false,
89
+ ...props
90
+ }: AvatarProps) {
91
+ const shapeKey = shape ?? "circle"
92
+ const insetClass =
93
+ insetBorder &&
94
+ cn(
95
+ "after:pointer-events-none after:absolute after:inset-0 after:border after:border-border after:mix-blend-darken dark:after:mix-blend-lighten",
96
+ AVATAR_INSET_AFTER[shapeKey]
97
+ )
98
+ return (
99
+ <AvatarPrimitive.Root
100
+ data-slot="avatar"
101
+ data-size={size}
102
+ data-shape={shape}
103
+ className={cn(avatarRootVariants({ size, shape, variant }), insetClass, className)}
104
+ {...props}
105
+ />
106
+ )
107
+ }
108
+
109
+ function AvatarImage({
110
+ className,
111
+ referrerPolicy = "no-referrer",
112
+ ...props
113
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
114
+ return (
115
+ <AvatarPrimitive.Image
116
+ data-slot="avatar-image"
117
+ referrerPolicy={referrerPolicy}
118
+ className={cn(avatarImageShape, className)}
119
+ {...props}
120
+ />
121
+ )
122
+ }
123
+
124
+ function AvatarFallback({
125
+ className,
126
+ ...props
127
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
128
+ return (
129
+ <AvatarPrimitive.Fallback
130
+ data-slot="avatar-fallback"
131
+ className={cn(avatarFallbackShape, className)}
132
+ {...props}
133
+ />
134
+ )
135
+ }
136
+
137
+ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
138
+ return (
139
+ <span
140
+ data-slot="avatar-badge"
141
+ className={cn(
142
+ "absolute end-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none",
143
+ "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
144
+ "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
145
+ "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
146
+ className
147
+ )}
148
+ {...props}
149
+ />
150
+ )
151
+ }
152
+
153
+ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
154
+ return (
155
+ <div
156
+ data-slot="avatar-group"
157
+ className={cn(
158
+ "group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
159
+ className
160
+ )}
161
+ {...props}
162
+ />
163
+ )
164
+ }
165
+
166
+ function AvatarGroupCount({
167
+ className,
168
+ ...props
169
+ }: React.ComponentProps<"div">) {
170
+ return (
171
+ <div
172
+ data-slot="avatar-group-count"
173
+ className={cn(
174
+ "relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
175
+ className
176
+ )}
177
+ {...props}
178
+ />
179
+ )
180
+ }
181
+
182
+ // ── Shadcn Studio–style compositions (tokens + a11y checklist: sr-only status, meaningful alt on image) ──
183
+
184
+ type AvatarShape = NonNullable<VariantProps<typeof avatarRootVariants>["shape"]>
185
+ type AvatarVariant = NonNullable<VariantProps<typeof avatarRootVariants>["variant"]>
186
+ type AvatarSize = NonNullable<VariantProps<typeof avatarRootVariants>["size"]>
187
+
188
+ type AvatarInitialsProps = Omit<AvatarProps, "children"> & {
189
+ initials: string
190
+ /** When true, initials are hidden from accessibility tree (pair with visible name). Default true. */
191
+ decorative?: boolean
192
+ /** Extra classes on `AvatarFallback` (e.g. font weight). */
193
+ fallbackClassName?: string
194
+ }
195
+
196
+ /** Initials-only chip using `--avatar-initials-bg` / `--avatar-initials-fg` (Exxat tokens). */
197
+ function AvatarInitials({
198
+ initials,
199
+ decorative = true,
200
+ fallbackClassName,
201
+ className,
202
+ ...avatarProps
203
+ }: AvatarInitialsProps) {
204
+ return (
205
+ <Avatar className={className} {...avatarProps}>
206
+ <AvatarFallback
207
+ aria-hidden={decorative ? true : undefined}
208
+ className={cn(
209
+ "border-0 bg-[var(--avatar-initials-bg)] font-semibold text-[var(--avatar-initials-fg)]",
210
+ fallbackClassName
211
+ )}
212
+ >
213
+ {initials}
214
+ </AvatarFallback>
215
+ </Avatar>
216
+ )
217
+ }
218
+
219
+ type AvatarStatusTone = "online" | "busy" | "away"
220
+
221
+ const avatarStatusDot: Record<AvatarStatusTone, string> = {
222
+ online: "bg-emerald-600 dark:bg-emerald-400",
223
+ busy: "bg-destructive",
224
+ away: "bg-amber-600 dark:bg-amber-400",
225
+ }
226
+
227
+ /** Presence dot (Studio avatars 7–9). Wraps a single `Avatar`. */
228
+ function AvatarStatus({
229
+ children,
230
+ status,
231
+ label,
232
+ className,
233
+ position = "bottom-end",
234
+ }: {
235
+ children: React.ReactNode
236
+ status: AvatarStatusTone
237
+ /** Announced to screen readers (e.g. "Busy"). */
238
+ label: string
239
+ className?: string
240
+ /** `bottom-end` matches `AvatarBadge`; `top-end` matches some Studio away demos. */
241
+ position?: "bottom-end" | "top-end"
242
+ }) {
243
+ const pos =
244
+ position === "top-end"
245
+ ? "-top-0.5 -end-0.5 bottom-auto start-auto"
246
+ : "-end-0.5 -bottom-0.5 top-auto start-auto"
247
+ return (
248
+ <span className={cn("relative inline-flex w-fit shrink-0", className)}>
249
+ {children}
250
+ <span
251
+ className={cn(
252
+ "pointer-events-none absolute z-10 size-3 rounded-full border-2 border-background",
253
+ pos,
254
+ avatarStatusDot[status]
255
+ )}
256
+ aria-hidden
257
+ />
258
+ <span className="sr-only">{label}</span>
259
+ </span>
260
+ )
261
+ }
262
+
263
+ /** Numeric badge overlay (Studio avatar 11). */
264
+ function AvatarNotificationCount({
265
+ children,
266
+ count,
267
+ className,
268
+ badgeClassName,
269
+ }: {
270
+ children: React.ReactNode
271
+ count: number | string
272
+ className?: string
273
+ badgeClassName?: string
274
+ }) {
275
+ return (
276
+ <span className={cn("relative inline-flex w-fit shrink-0", className)}>
277
+ {children}
278
+ <Badge
279
+ className={cn(
280
+ "absolute -top-2.5 -end-2.5 z-10 h-5 min-w-5 justify-center px-1 tabular-nums",
281
+ badgeClassName
282
+ )}
283
+ >
284
+ {count}
285
+ </Badge>
286
+ </span>
287
+ )
288
+ }
289
+
290
+ /** Verified check overlay (Studio avatar 12) — primary token fill. */
291
+ function AvatarVerified({
292
+ children,
293
+ label = "Verified",
294
+ className,
295
+ }: {
296
+ children: React.ReactNode
297
+ label?: string
298
+ className?: string
299
+ }) {
300
+ return (
301
+ <span className={cn("relative inline-flex w-fit shrink-0", className)}>
302
+ {children}
303
+ <span className="pointer-events-none absolute -top-1.5 -end-1.5 z-10 inline-flex size-4 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm">
304
+ <Check className="size-2.5" strokeWidth={3} aria-hidden />
305
+ <span className="sr-only">{label}</span>
306
+ </span>
307
+ </span>
308
+ )
309
+ }
310
+
311
+ /** Leo assistant mark — centralized; same icon treatment as Ask Leo sidebar. */
312
+ function AvatarLeoAssistant({
313
+ className,
314
+ size = "sm",
315
+ ...props
316
+ }: Omit<AvatarProps, "children" | "shape">) {
317
+ return (
318
+ <Avatar size={size} shape="circle" insetBorder={false} className={className} {...props}>
319
+ <AvatarFallback className="bg-brand/15 p-0">
320
+ <span className="sr-only">Leo</span>
321
+ <i
322
+ className="fa-duotone fa-solid fa-star-christmas text-xs text-brand"
323
+ aria-hidden="true"
324
+ />
325
+ </AvatarFallback>
326
+ </Avatar>
327
+ )
328
+ }
329
+
330
+ /** “Add profile” affordance (Studio avatar 10). `onClick` on the button. */
331
+ function AvatarPlusAction({
332
+ children,
333
+ actionLabel,
334
+ onClick,
335
+ className,
336
+ buttonClassName,
337
+ }: {
338
+ children: React.ReactNode
339
+ actionLabel: string
340
+ onClick?: () => void
341
+ className?: string
342
+ buttonClassName?: string
343
+ }) {
344
+ return (
345
+ <span className={cn("relative inline-flex w-fit shrink-0", className)}>
346
+ {children}
347
+ <button
348
+ type="button"
349
+ onClick={onClick}
350
+ className={cn(
351
+ "focus-visible:ring-ring/50 absolute -end-1 -bottom-1 z-10 inline-flex size-7 cursor-pointer items-center justify-center rounded-full bg-background shadow-sm ring-1 ring-border focus-visible:ring-[3px] focus-visible:outline-none",
352
+ buttonClassName
353
+ )}
354
+ >
355
+ <Plus className="size-4 text-muted-foreground" aria-hidden />
356
+ <span className="sr-only">{actionLabel}</span>
357
+ </button>
358
+ </span>
359
+ )
360
+ }
361
+
362
+ export {
363
+ Avatar,
364
+ AvatarImage,
365
+ AvatarFallback,
366
+ AvatarGroup,
367
+ AvatarGroupCount,
368
+ AvatarBadge,
369
+ AvatarInitials,
370
+ AvatarStatus,
371
+ AvatarNotificationCount,
372
+ AvatarVerified,
373
+ AvatarPlusAction,
374
+ AvatarLeoAssistant,
375
+ avatarRootVariants,
376
+ }
377
+ export type {
378
+ AvatarProps,
379
+ AvatarInitialsProps,
380
+ AvatarShape,
381
+ AvatarVariant,
382
+ AvatarSize,
383
+ AvatarStatusTone,
384
+ }
@@ -0,0 +1,49 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { Slot } from "radix-ui"
4
+
5
+ import { cn } from "../../lib/utils"
6
+
7
+ const badgeVariants = cva(
8
+ "group/badge inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-1 text-xs font-medium leading-none whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
13
+ secondary:
14
+ "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
15
+ destructive:
16
+ "bg-destructive/10 text-[var(--chip-destructive)] focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:text-red-100 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
17
+ outline:
18
+ "border-border text-foreground [a]:hover:bg-interactive-hover [a]:hover:text-muted-foreground",
19
+ ghost:
20
+ "hover:bg-interactive-hover hover:text-muted-foreground dark:hover:bg-interactive-hover-subtle",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: "default",
26
+ },
27
+ }
28
+ )
29
+
30
+ function Badge({
31
+ className,
32
+ variant = "default",
33
+ asChild = false,
34
+ ...props
35
+ }: React.ComponentProps<"span"> &
36
+ VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
37
+ const Comp = asChild ? Slot.Root : "span"
38
+
39
+ return (
40
+ <Comp
41
+ data-slot="badge"
42
+ data-variant={variant}
43
+ className={cn(badgeVariants({ variant }), className)}
44
+ {...props}
45
+ />
46
+ )
47
+ }
48
+
49
+ export { Badge, badgeVariants }