@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.
- package/package.json +72 -0
- package/src/components/ui/avatar.tsx +384 -0
- package/src/components/ui/badge.tsx +49 -0
- package/src/components/ui/banner.tsx +364 -0
- package/src/components/ui/breadcrumb.tsx +120 -0
- package/src/components/ui/button.tsx +66 -0
- package/src/components/ui/calendar.tsx +220 -0
- package/src/components/ui/card.tsx +136 -0
- package/src/components/ui/chart.tsx +378 -0
- package/src/components/ui/checkbox.tsx +160 -0
- package/src/components/ui/coach-mark.tsx +361 -0
- package/src/components/ui/collapsible.tsx +33 -0
- package/src/components/ui/command.tsx +232 -0
- package/src/components/ui/date-picker-field.tsx +186 -0
- package/src/components/ui/dialog.tsx +171 -0
- package/src/components/ui/drag-handle-grip.tsx +10 -0
- package/src/components/ui/drawer.tsx +134 -0
- package/src/components/ui/dropdown-menu.tsx +422 -0
- package/src/components/ui/field.tsx +238 -0
- package/src/components/ui/form.tsx +137 -0
- package/src/components/ui/input-group.tsx +156 -0
- package/src/components/ui/input-mask.tsx +135 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/kbd.tsx +55 -0
- package/src/components/ui/label.tsx +25 -0
- package/src/components/ui/payment-card-fields.tsx +65 -0
- package/src/components/ui/popover.tsx +46 -0
- package/src/components/ui/radio-group.tsx +217 -0
- package/src/components/ui/select.tsx +191 -0
- package/src/components/ui/selection-tile-grid.tsx +246 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +147 -0
- package/src/components/ui/sidebar.tsx +716 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +39 -0
- package/src/components/ui/status-badge.tsx +109 -0
- package/src/components/ui/table.tsx +117 -0
- package/src/components/ui/tabs.tsx +90 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tip.tsx +21 -0
- package/src/components/ui/toggle-group.tsx +89 -0
- package/src/components/ui/toggle-switch.tsx +31 -0
- package/src/components/ui/toggle.tsx +48 -0
- package/src/components/ui/tooltip.tsx +59 -0
- package/src/components/ui/view-segmented-control.tsx +160 -0
- package/src/globals.css +1795 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/hooks/use-app-theme.ts +172 -0
- package/src/hooks/use-coach-mark.ts +342 -0
- package/src/hooks/use-mobile.ts +31 -0
- package/src/hooks/use-mod-key-label.ts +29 -0
- package/src/index.ts +55 -0
- package/src/lib/compose-refs.ts +15 -0
- package/src/lib/date-filter.ts +67 -0
- package/src/lib/utils.ts +6 -0
- package/src/theme/apply-windows-contrast-theme.ts +29 -0
- package/src/theme/windows-contrast-theme.json +147 -0
- package/src/theme.css +1130 -0
- 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 }
|