@cntyclub/ui-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-HDGMSYQS.js +26461 -0
- package/dist/chunk-HDGMSYQS.js.map +1 -0
- package/dist/chunk-PR4QN5HX.js +39 -0
- package/dist/chunk-PR4QN5HX.js.map +1 -0
- package/dist/form.d.ts +175 -0
- package/dist/form.js +5207 -0
- package/dist/form.js.map +1 -0
- package/dist/index.d.ts +1462 -0
- package/dist/index.js +81862 -0
- package/dist/index.js.map +1 -0
- package/dist/input-CZvh825j.d.ts +24 -0
- package/dist/qr-code-styling-3Y6LZH6V.js +1123 -0
- package/dist/qr-code-styling-3Y6LZH6V.js.map +1 -0
- package/package.json +79 -0
- package/src/components/form/checkbox-group-field.tsx +101 -0
- package/src/components/form/date-field.tsx +79 -0
- package/src/components/form/date-range-field.tsx +106 -0
- package/src/components/form/form-context.ts +10 -0
- package/src/components/form/form.tsx +54 -0
- package/src/components/form/number-field.tsx +69 -0
- package/src/components/form/select-field.tsx +76 -0
- package/src/components/form/submit-button.tsx +28 -0
- package/src/components/form/text-field.tsx +107 -0
- package/src/components/layout/dashboard-header.tsx +54 -0
- package/src/components/layout/dashboard-panel.tsx +34 -0
- package/src/components/theme-provider.tsx +403 -0
- package/src/components/ui/accordion.tsx +69 -0
- package/src/components/ui/alert-dialog.tsx +169 -0
- package/src/components/ui/alert.tsx +80 -0
- package/src/components/ui/animated-theme-toggler.tsx +265 -0
- package/src/components/ui/app-store-buttons.tsx +182 -0
- package/src/components/ui/aspect-ratio.tsx +23 -0
- package/src/components/ui/autocomplete.tsx +296 -0
- package/src/components/ui/avatar-group.tsx +95 -0
- package/src/components/ui/avatar.tsx +285 -0
- package/src/components/ui/badge-group.tsx +160 -0
- package/src/components/ui/badge.tsx +172 -0
- package/src/components/ui/breadcrumb.tsx +112 -0
- package/src/components/ui/button.tsx +77 -0
- package/src/components/ui/calendar.tsx +137 -0
- package/src/components/ui/card.tsx +244 -0
- package/src/components/ui/carousel.tsx +258 -0
- package/src/components/ui/chart.tsx +379 -0
- package/src/components/ui/checkbox-group.tsx +16 -0
- package/src/components/ui/checkbox.tsx +82 -0
- package/src/components/ui/collapsible.tsx +45 -0
- package/src/components/ui/combobox.tsx +411 -0
- package/src/components/ui/command.tsx +264 -0
- package/src/components/ui/context-menu.tsx +271 -0
- package/src/components/ui/credit-card.tsx +214 -0
- package/src/components/ui/dialog.tsx +196 -0
- package/src/components/ui/drawer.tsx +135 -0
- package/src/components/ui/empty.tsx +127 -0
- package/src/components/ui/featured-icon.tsx +149 -0
- package/src/components/ui/field.tsx +88 -0
- package/src/components/ui/fieldset.tsx +29 -0
- package/src/components/ui/form.tsx +17 -0
- package/src/components/ui/frame.tsx +82 -0
- package/src/components/ui/generic-empty.tsx +142 -0
- package/src/components/ui/group.tsx +97 -0
- package/src/components/ui/horizontal-scroll-fader.tsx +228 -0
- package/src/components/ui/input-group.tsx +102 -0
- package/src/components/ui/input-otp.tsx +96 -0
- package/src/components/ui/input.tsx +66 -0
- package/src/components/ui/item.tsx +198 -0
- package/src/components/ui/kbd.tsx +30 -0
- package/src/components/ui/label.tsx +28 -0
- package/src/components/ui/menu.tsx +312 -0
- package/src/components/ui/menubar.tsx +93 -0
- package/src/components/ui/meter.tsx +67 -0
- package/src/components/ui/multi-select.tsx +308 -0
- package/src/components/ui/navigation-menu.tsx +143 -0
- package/src/components/ui/number-field.tsx +160 -0
- package/src/components/ui/pagination-controls.tsx +74 -0
- package/src/components/ui/pagination.tsx +149 -0
- package/src/components/ui/popover.tsx +119 -0
- package/src/components/ui/preview-card.tsx +55 -0
- package/src/components/ui/progress.tsx +289 -0
- package/src/components/ui/qr-code.tsx +150 -0
- package/src/components/ui/radio-group.tsx +103 -0
- package/src/components/ui/resizable.tsx +56 -0
- package/src/components/ui/scroll-area.tsx +90 -0
- package/src/components/ui/scroller.tsx +38 -0
- package/src/components/ui/section-header.tsx +118 -0
- package/src/components/ui/select.tsx +181 -0
- package/src/components/ui/separator.tsx +23 -0
- package/src/components/ui/sheet.tsx +224 -0
- package/src/components/ui/sidebar.tsx +744 -0
- package/src/components/ui/skeleton.tsx +16 -0
- package/src/components/ui/slider.tsx +108 -0
- package/src/components/ui/smooth-scroll.tsx +143 -0
- package/src/components/ui/social-button.tsx +247 -0
- package/src/components/ui/spinner-on-demand.tsx +32 -0
- package/src/components/ui/spinner.tsx +18 -0
- package/src/components/ui/stat.tsx +187 -0
- package/src/components/ui/stepper.tsx +167 -0
- package/src/components/ui/switch.tsx +56 -0
- package/src/components/ui/table.tsx +126 -0
- package/src/components/ui/tabs.tsx +90 -0
- package/src/components/ui/tag.tsx +229 -0
- package/src/components/ui/target-countdown.tsx +46 -0
- package/src/components/ui/text-editor.tsx +313 -0
- package/src/components/ui/textarea.tsx +51 -0
- package/src/components/ui/timeline.tsx +116 -0
- package/src/components/ui/toast.tsx +268 -0
- package/src/components/ui/toggle-group.tsx +101 -0
- package/src/components/ui/toggle.tsx +45 -0
- package/src/components/ui/toolbar.tsx +89 -0
- package/src/components/ui/tooltip.tsx +102 -0
- package/src/components/ui/vertical-scroll-fader.tsx +250 -0
- package/src/components/ui/video-player.tsx +275 -0
- package/src/components/upload/avatar-upload-base.tsx +131 -0
- package/src/components/upload/image-upload-base.tsx +112 -0
- package/src/form.ts +17 -0
- package/src/index.ts +125 -0
- package/src/lib/hooks/use-callback-ref.ts +15 -0
- package/src/lib/hooks/use-first-render.ts +11 -0
- package/src/lib/hooks/use-hover.ts +53 -0
- package/src/lib/hooks/use-is-tab-active.ts +17 -0
- package/src/lib/hooks/use-media-query.ts +164 -0
- package/src/lib/utils/css.ts +6 -0
- package/src/styles.css +300 -0
- package/src/types/helpers.ts +24 -0
- package/src/types/react.d.ts +7 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils/css";
|
|
4
|
+
|
|
5
|
+
function AspectRatio({
|
|
6
|
+
ratio = 1,
|
|
7
|
+
className,
|
|
8
|
+
style,
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<"div"> & {
|
|
11
|
+
ratio?: number;
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
className={cn("relative w-full overflow-hidden", className)}
|
|
16
|
+
data-slot="aspect-ratio"
|
|
17
|
+
style={{ aspectRatio: ratio, ...style }}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { AspectRatio };
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Autocomplete as AutocompletePrimitive } from "@base-ui/react/autocomplete";
|
|
4
|
+
import { ChevronsUpDownIcon, XIcon } from "lucide-react";
|
|
5
|
+
import { Input } from "./input";
|
|
6
|
+
import { ScrollArea } from "./scroll-area";
|
|
7
|
+
import { cn } from "../../lib/utils/css";
|
|
8
|
+
|
|
9
|
+
const Autocomplete = AutocompletePrimitive.Root;
|
|
10
|
+
|
|
11
|
+
function AutocompleteInput({
|
|
12
|
+
className,
|
|
13
|
+
showTrigger = false,
|
|
14
|
+
showClear = false,
|
|
15
|
+
startAddon,
|
|
16
|
+
size,
|
|
17
|
+
...props
|
|
18
|
+
}: Omit<AutocompletePrimitive.Input.Props, "size"> & {
|
|
19
|
+
showTrigger?: boolean;
|
|
20
|
+
showClear?: boolean;
|
|
21
|
+
startAddon?: React.ReactNode;
|
|
22
|
+
size?: "sm" | "default" | "lg" | number;
|
|
23
|
+
ref?: React.Ref<HTMLInputElement>;
|
|
24
|
+
}) {
|
|
25
|
+
const sizeValue = (size ?? "default") as "sm" | "default" | "lg" | number;
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="relative not-has-[>*.w-full]:w-fit w-full text-foreground has-disabled:opacity-64">
|
|
29
|
+
{startAddon && (
|
|
30
|
+
<div
|
|
31
|
+
aria-hidden="true"
|
|
32
|
+
className="[&_svg]:-mx-0.5 pointer-events-none absolute inset-y-0 start-px z-10 flex items-center ps-[calc(--spacing(3)-1px)] opacity-80 has-[+[data-size=sm]]:ps-[calc(--spacing(2.5)-1px)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4"
|
|
33
|
+
data-slot="autocomplete-start-addon"
|
|
34
|
+
>
|
|
35
|
+
{startAddon}
|
|
36
|
+
</div>
|
|
37
|
+
)}
|
|
38
|
+
<AutocompletePrimitive.Input
|
|
39
|
+
className={cn(
|
|
40
|
+
startAddon &&
|
|
41
|
+
"data-[size=sm]:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(7.5)-1px)] *:data-[slot=autocomplete-input]:ps-[calc(--spacing(8.5)-1px)] sm:data-[size=sm]:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(7)-1px)] sm:*:data-[slot=autocomplete-input]:ps-[calc(--spacing(8)-1px)]",
|
|
42
|
+
sizeValue === "sm"
|
|
43
|
+
? "has-[+[data-slot=autocomplete-trigger],+[data-slot=autocomplete-clear]]:*:data-[slot=autocomplete-input]:pe-6.5"
|
|
44
|
+
: "has-[+[data-slot=autocomplete-trigger],+[data-slot=autocomplete-clear]]:*:data-[slot=autocomplete-input]:pe-7",
|
|
45
|
+
className,
|
|
46
|
+
)}
|
|
47
|
+
data-slot="autocomplete-input"
|
|
48
|
+
render={<Input nativeInput size={sizeValue} />}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
{showTrigger && (
|
|
52
|
+
<AutocompleteTrigger
|
|
53
|
+
className={cn(
|
|
54
|
+
"-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-colors pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=autocomplete-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
55
|
+
sizeValue === "sm" ? "end-0" : "end-0.5",
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
<ChevronsUpDownIcon />
|
|
59
|
+
</AutocompleteTrigger>
|
|
60
|
+
)}
|
|
61
|
+
{showClear && (
|
|
62
|
+
<AutocompleteClear
|
|
63
|
+
className={cn(
|
|
64
|
+
"-translate-y-1/2 absolute top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-colors pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 has-[+[data-slot=autocomplete-clear]]:hidden sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
65
|
+
sizeValue === "sm" ? "end-0" : "end-0.5",
|
|
66
|
+
)}
|
|
67
|
+
>
|
|
68
|
+
<XIcon />
|
|
69
|
+
</AutocompleteClear>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function AutocompletePopup({
|
|
76
|
+
className,
|
|
77
|
+
children,
|
|
78
|
+
sideOffset = 4,
|
|
79
|
+
...props
|
|
80
|
+
}: AutocompletePrimitive.Popup.Props & {
|
|
81
|
+
sideOffset?: number;
|
|
82
|
+
}) {
|
|
83
|
+
return (
|
|
84
|
+
<AutocompletePrimitive.Portal>
|
|
85
|
+
<AutocompletePrimitive.Positioner
|
|
86
|
+
className="z-50 select-none"
|
|
87
|
+
data-slot="autocomplete-positioner"
|
|
88
|
+
sideOffset={sideOffset}
|
|
89
|
+
>
|
|
90
|
+
<AutocompletePrimitive.Popup
|
|
91
|
+
className={cn(
|
|
92
|
+
"relative flex max-h-[min(var(--available-height),23rem)] w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) flex-col rounded-lg border bg-popover not-dark:bg-clip-padding text-foreground shadow-lg/5 transition-opacity duration-150 ease-out before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/6%)] data-ending-style:opacity-0 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
|
|
93
|
+
className,
|
|
94
|
+
)}
|
|
95
|
+
data-slot="autocomplete-popup"
|
|
96
|
+
{...props}
|
|
97
|
+
>
|
|
98
|
+
{children}
|
|
99
|
+
</AutocompletePrimitive.Popup>
|
|
100
|
+
</AutocompletePrimitive.Positioner>
|
|
101
|
+
</AutocompletePrimitive.Portal>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function AutocompleteItem({
|
|
106
|
+
className,
|
|
107
|
+
children,
|
|
108
|
+
...props
|
|
109
|
+
}: AutocompletePrimitive.Item.Props) {
|
|
110
|
+
return (
|
|
111
|
+
<AutocompletePrimitive.Item
|
|
112
|
+
className={cn(
|
|
113
|
+
"flex min-h-8 cursor-default select-none items-center rounded-sm px-2 py-1 text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm",
|
|
114
|
+
className,
|
|
115
|
+
)}
|
|
116
|
+
data-slot="autocomplete-item"
|
|
117
|
+
{...props}
|
|
118
|
+
>
|
|
119
|
+
{children}
|
|
120
|
+
</AutocompletePrimitive.Item>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function AutocompleteSeparator({
|
|
125
|
+
className,
|
|
126
|
+
...props
|
|
127
|
+
}: AutocompletePrimitive.Separator.Props) {
|
|
128
|
+
return (
|
|
129
|
+
<AutocompletePrimitive.Separator
|
|
130
|
+
className={cn("mx-2 my-1 h-px bg-border last:hidden", className)}
|
|
131
|
+
data-slot="autocomplete-separator"
|
|
132
|
+
{...props}
|
|
133
|
+
/>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function AutocompleteGroup({
|
|
138
|
+
className,
|
|
139
|
+
...props
|
|
140
|
+
}: AutocompletePrimitive.Group.Props) {
|
|
141
|
+
return (
|
|
142
|
+
<AutocompletePrimitive.Group
|
|
143
|
+
className={cn("[[role=group]+&]:mt-1.5", className)}
|
|
144
|
+
data-slot="autocomplete-group"
|
|
145
|
+
{...props}
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function AutocompleteGroupLabel({
|
|
151
|
+
className,
|
|
152
|
+
...props
|
|
153
|
+
}: AutocompletePrimitive.GroupLabel.Props) {
|
|
154
|
+
return (
|
|
155
|
+
<AutocompletePrimitive.GroupLabel
|
|
156
|
+
className={cn(
|
|
157
|
+
"px-2 py-1.5 font-medium text-muted-foreground text-xs",
|
|
158
|
+
className,
|
|
159
|
+
)}
|
|
160
|
+
data-slot="autocomplete-group-label"
|
|
161
|
+
{...props}
|
|
162
|
+
/>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function AutocompleteEmpty({
|
|
167
|
+
className,
|
|
168
|
+
...props
|
|
169
|
+
}: AutocompletePrimitive.Empty.Props) {
|
|
170
|
+
return (
|
|
171
|
+
<AutocompletePrimitive.Empty
|
|
172
|
+
className={cn(
|
|
173
|
+
"not-empty:p-2 text-center text-base text-muted-foreground sm:text-sm",
|
|
174
|
+
className,
|
|
175
|
+
)}
|
|
176
|
+
data-slot="autocomplete-empty"
|
|
177
|
+
{...props}
|
|
178
|
+
/>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function AutocompleteRow({
|
|
183
|
+
className,
|
|
184
|
+
...props
|
|
185
|
+
}: AutocompletePrimitive.Row.Props) {
|
|
186
|
+
return (
|
|
187
|
+
<AutocompletePrimitive.Row
|
|
188
|
+
className={className}
|
|
189
|
+
data-slot="autocomplete-row"
|
|
190
|
+
{...props}
|
|
191
|
+
/>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function AutocompleteValue({ ...props }: AutocompletePrimitive.Value.Props) {
|
|
196
|
+
return (
|
|
197
|
+
<AutocompletePrimitive.Value data-slot="autocomplete-value" {...props} />
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function AutocompleteList({
|
|
202
|
+
className,
|
|
203
|
+
...props
|
|
204
|
+
}: AutocompletePrimitive.List.Props) {
|
|
205
|
+
return (
|
|
206
|
+
<ScrollArea scrollbarGutter scrollFade className="grow">
|
|
207
|
+
<AutocompletePrimitive.List
|
|
208
|
+
className={cn(
|
|
209
|
+
"not-empty:scroll-py-1 not-empty:p-1 in-data-has-overflow-y:pe-3",
|
|
210
|
+
className,
|
|
211
|
+
)}
|
|
212
|
+
data-slot="autocomplete-list"
|
|
213
|
+
{...props}
|
|
214
|
+
/>
|
|
215
|
+
</ScrollArea>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function AutocompleteClear({
|
|
220
|
+
className,
|
|
221
|
+
...props
|
|
222
|
+
}: AutocompletePrimitive.Clear.Props) {
|
|
223
|
+
return (
|
|
224
|
+
<AutocompletePrimitive.Clear
|
|
225
|
+
className={cn(
|
|
226
|
+
"-translate-y-1/2 absolute end-0.5 top-1/2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-80 outline-none transition-[color,background-color,box-shadow,opacity] pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:opacity-100 sm:size-7 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
227
|
+
className,
|
|
228
|
+
)}
|
|
229
|
+
data-slot="autocomplete-clear"
|
|
230
|
+
{...props}
|
|
231
|
+
>
|
|
232
|
+
<XIcon />
|
|
233
|
+
</AutocompletePrimitive.Clear>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function AutocompleteStatus({
|
|
238
|
+
className,
|
|
239
|
+
...props
|
|
240
|
+
}: AutocompletePrimitive.Status.Props) {
|
|
241
|
+
return (
|
|
242
|
+
<AutocompletePrimitive.Status
|
|
243
|
+
className={cn(
|
|
244
|
+
"px-3 py-2 font-medium text-muted-foreground text-xs empty:m-0 empty:p-0",
|
|
245
|
+
className,
|
|
246
|
+
)}
|
|
247
|
+
data-slot="autocomplete-status"
|
|
248
|
+
{...props}
|
|
249
|
+
/>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function AutocompleteCollection({
|
|
254
|
+
...props
|
|
255
|
+
}: AutocompletePrimitive.Collection.Props) {
|
|
256
|
+
return (
|
|
257
|
+
<AutocompletePrimitive.Collection
|
|
258
|
+
data-slot="autocomplete-collection"
|
|
259
|
+
{...props}
|
|
260
|
+
/>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function AutocompleteTrigger({
|
|
265
|
+
className,
|
|
266
|
+
...props
|
|
267
|
+
}: AutocompletePrimitive.Trigger.Props) {
|
|
268
|
+
return (
|
|
269
|
+
<AutocompletePrimitive.Trigger
|
|
270
|
+
className={className}
|
|
271
|
+
data-slot="autocomplete-trigger"
|
|
272
|
+
{...props}
|
|
273
|
+
/>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const useAutocompleteFilter = AutocompletePrimitive.useFilter;
|
|
278
|
+
|
|
279
|
+
export {
|
|
280
|
+
Autocomplete,
|
|
281
|
+
AutocompleteInput,
|
|
282
|
+
AutocompleteTrigger,
|
|
283
|
+
AutocompletePopup,
|
|
284
|
+
AutocompleteItem,
|
|
285
|
+
AutocompleteSeparator,
|
|
286
|
+
AutocompleteGroup,
|
|
287
|
+
AutocompleteGroupLabel,
|
|
288
|
+
AutocompleteEmpty,
|
|
289
|
+
AutocompleteValue,
|
|
290
|
+
AutocompleteList,
|
|
291
|
+
AutocompleteClear,
|
|
292
|
+
AutocompleteStatus,
|
|
293
|
+
AutocompleteRow,
|
|
294
|
+
AutocompleteCollection,
|
|
295
|
+
useAutocompleteFilter,
|
|
296
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils/css";
|
|
7
|
+
import { Avatar, avatarVariants } from "./avatar";
|
|
8
|
+
|
|
9
|
+
type AvatarSize = NonNullable<VariantProps<typeof avatarVariants>["size"]>;
|
|
10
|
+
|
|
11
|
+
const avatarGroupVariants = cva(
|
|
12
|
+
// Each child gets a ring in the page color so overlapping avatars stay
|
|
13
|
+
// visually separated; hover lifts one to the front.
|
|
14
|
+
"isolate flex items-center *:relative *:rounded-full *:ring-2 *:ring-background *:transition-transform hover:*:z-10",
|
|
15
|
+
{
|
|
16
|
+
defaultVariants: { overlap: "md" },
|
|
17
|
+
variants: {
|
|
18
|
+
overlap: {
|
|
19
|
+
sm: "*:not-first:-ml-1.5",
|
|
20
|
+
md: "*:not-first:-ml-2.5",
|
|
21
|
+
lg: "*:not-first:-ml-3.5",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
interface AvatarGroupItem {
|
|
28
|
+
src?: string;
|
|
29
|
+
/** Initials shown when there is no image. */
|
|
30
|
+
initials?: React.ReactNode;
|
|
31
|
+
alt?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface AvatarGroupProps
|
|
35
|
+
extends React.ComponentProps<"div">,
|
|
36
|
+
VariantProps<typeof avatarGroupVariants> {
|
|
37
|
+
/** Data-driven avatars. Omit to compose `<Avatar>` children yourself. */
|
|
38
|
+
items?: AvatarGroupItem[];
|
|
39
|
+
/** Cap the visible avatars; the rest collapse into a "+N" chip. */
|
|
40
|
+
max?: number;
|
|
41
|
+
size?: AvatarSize;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A row of overlapping avatars with an overflow "+N" chip. Pass `items` for the
|
|
46
|
+
* data-driven path, or compose `<Avatar>` children directly for full control.
|
|
47
|
+
*/
|
|
48
|
+
function AvatarGroup({
|
|
49
|
+
className,
|
|
50
|
+
items,
|
|
51
|
+
max,
|
|
52
|
+
size = "sm",
|
|
53
|
+
overlap,
|
|
54
|
+
children,
|
|
55
|
+
...props
|
|
56
|
+
}: AvatarGroupProps) {
|
|
57
|
+
const shown = items && max ? items.slice(0, max) : items;
|
|
58
|
+
const overflow = items && max ? items.length - max : 0;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div
|
|
62
|
+
className={cn(avatarGroupVariants({ overlap }), className)}
|
|
63
|
+
data-slot="avatar-group"
|
|
64
|
+
role="group"
|
|
65
|
+
{...props}
|
|
66
|
+
>
|
|
67
|
+
{items
|
|
68
|
+
? shown?.map((item, i) => (
|
|
69
|
+
<Avatar
|
|
70
|
+
alt={item.alt}
|
|
71
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: order is stable
|
|
72
|
+
key={i}
|
|
73
|
+
initials={item.initials}
|
|
74
|
+
size={size}
|
|
75
|
+
src={item.src}
|
|
76
|
+
/>
|
|
77
|
+
))
|
|
78
|
+
: children}
|
|
79
|
+
{overflow > 0 ? (
|
|
80
|
+
<span
|
|
81
|
+
aria-label={`${overflow} more`}
|
|
82
|
+
className={cn(
|
|
83
|
+
avatarVariants({ size }),
|
|
84
|
+
"bg-muted font-medium text-muted-foreground",
|
|
85
|
+
)}
|
|
86
|
+
data-slot="avatar-group-overflow"
|
|
87
|
+
>
|
|
88
|
+
+{overflow}
|
|
89
|
+
</span>
|
|
90
|
+
) : null}
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export { AvatarGroup, avatarGroupVariants };
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
+
import { CheckIcon, PlusIcon, UserRoundIcon } from "lucide-react";
|
|
6
|
+
import type * as React from "react";
|
|
7
|
+
|
|
8
|
+
import { cn } from "../../lib/utils/css";
|
|
9
|
+
|
|
10
|
+
const avatarVariants = cva(
|
|
11
|
+
"relative inline-flex shrink-0 select-none items-center justify-center overflow-hidden rounded-full bg-muted align-middle font-medium",
|
|
12
|
+
{
|
|
13
|
+
defaultVariants: { size: "sm" },
|
|
14
|
+
variants: {
|
|
15
|
+
size: {
|
|
16
|
+
xs: "size-6 text-[0.625rem]",
|
|
17
|
+
sm: "size-8 text-xs",
|
|
18
|
+
md: "size-10 text-sm",
|
|
19
|
+
lg: "size-12 text-base",
|
|
20
|
+
xl: "size-14 text-lg",
|
|
21
|
+
"2xl": "size-16 text-xl",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
type AvatarSize = NonNullable<VariantProps<typeof avatarVariants>["size"]>;
|
|
28
|
+
|
|
29
|
+
interface AvatarProps
|
|
30
|
+
extends AvatarPrimitive.Root.Props,
|
|
31
|
+
VariantProps<typeof avatarVariants> {
|
|
32
|
+
/** Image URL. When omitted or it fails to load, the fallback is shown. */
|
|
33
|
+
src?: string;
|
|
34
|
+
alt?: string;
|
|
35
|
+
/** Initials shown when there is no image. */
|
|
36
|
+
initials?: React.ReactNode;
|
|
37
|
+
/** Custom fallback node (overrides initials and the placeholder icon). */
|
|
38
|
+
placeholder?: React.ReactNode;
|
|
39
|
+
/** Icon component used as the fallback when nothing else is provided. */
|
|
40
|
+
placeholderIcon?: React.ElementType;
|
|
41
|
+
/** A presence dot in the bottom-right corner. */
|
|
42
|
+
status?: "online" | "offline";
|
|
43
|
+
/** Shows a verified tick in the bottom-right corner. */
|
|
44
|
+
verified?: boolean;
|
|
45
|
+
/** A node (e.g. <AvatarCompanyIcon />) shown in the top-right corner. */
|
|
46
|
+
badge?: React.ReactNode;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function Avatar({
|
|
50
|
+
className,
|
|
51
|
+
size,
|
|
52
|
+
src,
|
|
53
|
+
alt,
|
|
54
|
+
initials,
|
|
55
|
+
placeholder,
|
|
56
|
+
placeholderIcon: PlaceholderIcon = UserRoundIcon,
|
|
57
|
+
status,
|
|
58
|
+
verified,
|
|
59
|
+
badge,
|
|
60
|
+
children,
|
|
61
|
+
...props
|
|
62
|
+
}: AvatarProps) {
|
|
63
|
+
const fallback = initials ? (
|
|
64
|
+
initials
|
|
65
|
+
) : (
|
|
66
|
+
(placeholder ?? <PlaceholderIcon className="size-[55%] text-muted-foreground" />)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const root = (
|
|
70
|
+
<AvatarPrimitive.Root
|
|
71
|
+
className={cn(avatarVariants({ size }), className)}
|
|
72
|
+
data-slot="avatar"
|
|
73
|
+
{...props}
|
|
74
|
+
>
|
|
75
|
+
{children ?? (
|
|
76
|
+
<>
|
|
77
|
+
{src ? <AvatarImage alt={alt} src={src} /> : null}
|
|
78
|
+
<AvatarFallback>{fallback}</AvatarFallback>
|
|
79
|
+
</>
|
|
80
|
+
)}
|
|
81
|
+
</AvatarPrimitive.Root>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (!(status || verified || badge)) return root;
|
|
85
|
+
|
|
86
|
+
// Overlays live in a non-clipping wrapper so the rounded avatar's
|
|
87
|
+
// overflow-hidden doesn't cut them off at the corners.
|
|
88
|
+
return (
|
|
89
|
+
<span
|
|
90
|
+
className="relative inline-flex shrink-0 align-middle"
|
|
91
|
+
data-slot="avatar-root"
|
|
92
|
+
>
|
|
93
|
+
{root}
|
|
94
|
+
{badge ? (
|
|
95
|
+
<span className="absolute end-0 top-0 flex" data-slot="avatar-badge">
|
|
96
|
+
{badge}
|
|
97
|
+
</span>
|
|
98
|
+
) : null}
|
|
99
|
+
{status ? (
|
|
100
|
+
<span
|
|
101
|
+
className="absolute end-[6%] bottom-[6%] h-1/4 w-1/4 rounded-full ring-2 ring-background data-[status=offline]:bg-muted-foreground/40 data-[status=online]:bg-success"
|
|
102
|
+
data-slot="avatar-status"
|
|
103
|
+
data-status={status}
|
|
104
|
+
/>
|
|
105
|
+
) : null}
|
|
106
|
+
{verified ? (
|
|
107
|
+
<span
|
|
108
|
+
className="absolute end-0 bottom-0 flex h-1/3 w-1/3 items-center justify-center rounded-full bg-brand text-background ring-2 ring-background"
|
|
109
|
+
data-slot="avatar-verified"
|
|
110
|
+
>
|
|
111
|
+
<CheckIcon className="size-2/3" strokeWidth={3} />
|
|
112
|
+
</span>
|
|
113
|
+
) : null}
|
|
114
|
+
</span>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
|
|
119
|
+
return (
|
|
120
|
+
<AvatarPrimitive.Image
|
|
121
|
+
className={cn("size-full object-cover", className)}
|
|
122
|
+
data-slot="avatar-image"
|
|
123
|
+
{...props}
|
|
124
|
+
/>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function AvatarFallback({
|
|
129
|
+
className,
|
|
130
|
+
...props
|
|
131
|
+
}: AvatarPrimitive.Fallback.Props) {
|
|
132
|
+
return (
|
|
133
|
+
<AvatarPrimitive.Fallback
|
|
134
|
+
className={cn(
|
|
135
|
+
"flex size-full items-center justify-center rounded-[inherit] bg-muted text-muted-foreground",
|
|
136
|
+
className,
|
|
137
|
+
)}
|
|
138
|
+
data-slot="avatar-fallback"
|
|
139
|
+
{...props}
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** A small company/brand logo badge, meant for the Avatar `badge` prop. */
|
|
145
|
+
function AvatarCompanyIcon({
|
|
146
|
+
className,
|
|
147
|
+
size = "md",
|
|
148
|
+
alt,
|
|
149
|
+
...props
|
|
150
|
+
}: React.ComponentProps<"img"> & { size?: "sm" | "md" | "lg" }) {
|
|
151
|
+
const sizes = { lg: "size-5", md: "size-4", sm: "size-3.5" } as const;
|
|
152
|
+
return (
|
|
153
|
+
// biome-ignore lint/a11y/useAltText: alt forwarded via props
|
|
154
|
+
<img
|
|
155
|
+
alt={alt}
|
|
156
|
+
className={cn(
|
|
157
|
+
"rounded-[4px] object-cover ring-2 ring-background",
|
|
158
|
+
sizes[size],
|
|
159
|
+
className,
|
|
160
|
+
)}
|
|
161
|
+
data-slot="avatar-company-icon"
|
|
162
|
+
{...props}
|
|
163
|
+
/>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** A dashed "add" button sized to match avatars — for avatar groups. */
|
|
168
|
+
function AvatarAddButton({
|
|
169
|
+
className,
|
|
170
|
+
size = "sm",
|
|
171
|
+
...props
|
|
172
|
+
}: React.ComponentProps<"button"> & { size?: AvatarSize }) {
|
|
173
|
+
return (
|
|
174
|
+
<button
|
|
175
|
+
className={cn(
|
|
176
|
+
avatarVariants({ size }),
|
|
177
|
+
"border border-input border-dashed bg-transparent text-muted-foreground outline-none transition-colors hover:border-transparent hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
178
|
+
className,
|
|
179
|
+
)}
|
|
180
|
+
data-slot="avatar-add-button"
|
|
181
|
+
type="button"
|
|
182
|
+
{...props}
|
|
183
|
+
>
|
|
184
|
+
<PlusIcon className="size-1/2" />
|
|
185
|
+
</button>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const labelGroupText = {
|
|
190
|
+
lg: { subtitle: "text-sm", title: "text-base" },
|
|
191
|
+
md: { subtitle: "text-sm", title: "text-sm" },
|
|
192
|
+
sm: { subtitle: "text-xs", title: "text-sm" },
|
|
193
|
+
} as const;
|
|
194
|
+
|
|
195
|
+
/** An avatar paired with a title and subtitle. */
|
|
196
|
+
function AvatarLabelGroup({
|
|
197
|
+
className,
|
|
198
|
+
size = "md",
|
|
199
|
+
src,
|
|
200
|
+
alt,
|
|
201
|
+
initials,
|
|
202
|
+
status,
|
|
203
|
+
verified,
|
|
204
|
+
title,
|
|
205
|
+
subtitle,
|
|
206
|
+
...props
|
|
207
|
+
}: React.ComponentProps<"div"> & {
|
|
208
|
+
size?: "sm" | "md" | "lg";
|
|
209
|
+
src?: string;
|
|
210
|
+
alt?: string;
|
|
211
|
+
initials?: string;
|
|
212
|
+
status?: "online" | "offline";
|
|
213
|
+
verified?: boolean;
|
|
214
|
+
title: React.ReactNode;
|
|
215
|
+
subtitle?: React.ReactNode;
|
|
216
|
+
}) {
|
|
217
|
+
return (
|
|
218
|
+
<div
|
|
219
|
+
className={cn("flex items-center gap-3", className)}
|
|
220
|
+
data-slot="avatar-label-group"
|
|
221
|
+
{...props}
|
|
222
|
+
>
|
|
223
|
+
<Avatar
|
|
224
|
+
alt={alt}
|
|
225
|
+
initials={initials}
|
|
226
|
+
size={size}
|
|
227
|
+
src={src}
|
|
228
|
+
status={status}
|
|
229
|
+
verified={verified}
|
|
230
|
+
/>
|
|
231
|
+
<div className="flex min-w-0 flex-col">
|
|
232
|
+
<span
|
|
233
|
+
className={cn(
|
|
234
|
+
"truncate font-medium text-foreground leading-tight",
|
|
235
|
+
labelGroupText[size].title,
|
|
236
|
+
)}
|
|
237
|
+
>
|
|
238
|
+
{title}
|
|
239
|
+
</span>
|
|
240
|
+
{subtitle ? (
|
|
241
|
+
<span
|
|
242
|
+
className={cn(
|
|
243
|
+
"truncate text-muted-foreground leading-tight",
|
|
244
|
+
labelGroupText[size].subtitle,
|
|
245
|
+
)}
|
|
246
|
+
>
|
|
247
|
+
{subtitle}
|
|
248
|
+
</span>
|
|
249
|
+
) : null}
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const profilePhotoSizes = { lg: "size-20", md: "size-16", sm: "size-14" } as const;
|
|
256
|
+
|
|
257
|
+
/** A larger, framed profile photo with optional verified tick. */
|
|
258
|
+
function AvatarProfilePhoto({
|
|
259
|
+
className,
|
|
260
|
+
size = "md",
|
|
261
|
+
...props
|
|
262
|
+
}: Omit<AvatarProps, "size"> & { size?: "sm" | "md" | "lg" }) {
|
|
263
|
+
return (
|
|
264
|
+
<Avatar
|
|
265
|
+
className={cn(
|
|
266
|
+
"ring-1 ring-black/8 ring-inset dark:ring-white/12",
|
|
267
|
+
profilePhotoSizes[size],
|
|
268
|
+
className,
|
|
269
|
+
)}
|
|
270
|
+
data-slot="avatar-profile-photo"
|
|
271
|
+
{...props}
|
|
272
|
+
/>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export {
|
|
277
|
+
Avatar,
|
|
278
|
+
AvatarImage,
|
|
279
|
+
AvatarFallback,
|
|
280
|
+
AvatarCompanyIcon,
|
|
281
|
+
AvatarAddButton,
|
|
282
|
+
AvatarLabelGroup,
|
|
283
|
+
AvatarProfilePhoto,
|
|
284
|
+
avatarVariants,
|
|
285
|
+
};
|