@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,271 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ContextMenu as ContextMenuPrimitive } from "@base-ui/react/context-menu";
|
|
4
|
+
import { CheckIcon, ChevronRightIcon } from "lucide-react";
|
|
5
|
+
import type * as React from "react";
|
|
6
|
+
import { cn } from "../../lib/utils/css";
|
|
7
|
+
|
|
8
|
+
function ContextMenu({ ...props }: ContextMenuPrimitive.Root.Props) {
|
|
9
|
+
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function ContextMenuPortal({ ...props }: ContextMenuPrimitive.Portal.Props) {
|
|
13
|
+
return (
|
|
14
|
+
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function ContextMenuTrigger({
|
|
19
|
+
className,
|
|
20
|
+
...props
|
|
21
|
+
}: ContextMenuPrimitive.Trigger.Props) {
|
|
22
|
+
return (
|
|
23
|
+
<ContextMenuPrimitive.Trigger
|
|
24
|
+
data-slot="context-menu-trigger"
|
|
25
|
+
className={cn("select-none", className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function ContextMenuContent({
|
|
32
|
+
className,
|
|
33
|
+
align = "start",
|
|
34
|
+
alignOffset = 4,
|
|
35
|
+
side = "right",
|
|
36
|
+
sideOffset = 0,
|
|
37
|
+
...props
|
|
38
|
+
}: ContextMenuPrimitive.Popup.Props &
|
|
39
|
+
Pick<
|
|
40
|
+
ContextMenuPrimitive.Positioner.Props,
|
|
41
|
+
"align" | "alignOffset" | "side" | "sideOffset"
|
|
42
|
+
>) {
|
|
43
|
+
return (
|
|
44
|
+
<ContextMenuPrimitive.Portal>
|
|
45
|
+
<ContextMenuPrimitive.Positioner
|
|
46
|
+
className="isolate z-50 outline-none"
|
|
47
|
+
align={align}
|
|
48
|
+
alignOffset={alignOffset}
|
|
49
|
+
side={side}
|
|
50
|
+
sideOffset={sideOffset}
|
|
51
|
+
>
|
|
52
|
+
<ContextMenuPrimitive.Popup
|
|
53
|
+
data-slot="context-menu-content"
|
|
54
|
+
className={cn(
|
|
55
|
+
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-36 rounded-lg p-1 shadow-md ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 max-h-(--available-height) origin-(--transform-origin) overflow-x-hidden overflow-y-auto outline-none",
|
|
56
|
+
className,
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
</ContextMenuPrimitive.Positioner>
|
|
61
|
+
</ContextMenuPrimitive.Portal>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function ContextMenuGroup({ ...props }: ContextMenuPrimitive.Group.Props) {
|
|
66
|
+
return (
|
|
67
|
+
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function ContextMenuLabel({
|
|
72
|
+
className,
|
|
73
|
+
inset,
|
|
74
|
+
...props
|
|
75
|
+
}: ContextMenuPrimitive.GroupLabel.Props & {
|
|
76
|
+
inset?: boolean;
|
|
77
|
+
}) {
|
|
78
|
+
return (
|
|
79
|
+
<ContextMenuPrimitive.GroupLabel
|
|
80
|
+
data-slot="context-menu-label"
|
|
81
|
+
data-inset={inset}
|
|
82
|
+
className={cn(
|
|
83
|
+
"text-muted-foreground px-1.5 py-1 text-xs font-medium data-inset:pl-7",
|
|
84
|
+
className,
|
|
85
|
+
)}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function ContextMenuItem({
|
|
92
|
+
className,
|
|
93
|
+
inset,
|
|
94
|
+
variant = "default",
|
|
95
|
+
...props
|
|
96
|
+
}: ContextMenuPrimitive.Item.Props & {
|
|
97
|
+
inset?: boolean;
|
|
98
|
+
variant?: "default" | "destructive";
|
|
99
|
+
}) {
|
|
100
|
+
return (
|
|
101
|
+
<ContextMenuPrimitive.Item
|
|
102
|
+
data-slot="context-menu-item"
|
|
103
|
+
data-inset={inset}
|
|
104
|
+
data-variant={variant}
|
|
105
|
+
className={cn(
|
|
106
|
+
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive focus:*:[svg]:text-accent-foreground gap-1.5 rounded-md px-1.5 py-1 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 group/context-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
107
|
+
className,
|
|
108
|
+
)}
|
|
109
|
+
{...props}
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function ContextMenuSub({ ...props }: ContextMenuPrimitive.SubmenuRoot.Props) {
|
|
115
|
+
return (
|
|
116
|
+
<ContextMenuPrimitive.SubmenuRoot data-slot="context-menu-sub" {...props} />
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function ContextMenuSubTrigger({
|
|
121
|
+
className,
|
|
122
|
+
inset,
|
|
123
|
+
children,
|
|
124
|
+
...props
|
|
125
|
+
}: ContextMenuPrimitive.SubmenuTrigger.Props & {
|
|
126
|
+
inset?: boolean;
|
|
127
|
+
}) {
|
|
128
|
+
return (
|
|
129
|
+
<ContextMenuPrimitive.SubmenuTrigger
|
|
130
|
+
data-slot="context-menu-sub-trigger"
|
|
131
|
+
data-inset={inset}
|
|
132
|
+
className={cn(
|
|
133
|
+
"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground gap-1.5 rounded-md px-1.5 py-1 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
134
|
+
className,
|
|
135
|
+
)}
|
|
136
|
+
{...props}
|
|
137
|
+
>
|
|
138
|
+
{children}
|
|
139
|
+
<ChevronRightIcon className="cn-rtl-flip ml-auto" />
|
|
140
|
+
</ContextMenuPrimitive.SubmenuTrigger>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function ContextMenuSubContent({
|
|
145
|
+
...props
|
|
146
|
+
}: React.ComponentProps<typeof ContextMenuContent>) {
|
|
147
|
+
return (
|
|
148
|
+
<ContextMenuContent
|
|
149
|
+
data-slot="context-menu-sub-content"
|
|
150
|
+
className="shadow-lg"
|
|
151
|
+
side="right"
|
|
152
|
+
{...props}
|
|
153
|
+
/>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function ContextMenuCheckboxItem({
|
|
158
|
+
className,
|
|
159
|
+
children,
|
|
160
|
+
checked,
|
|
161
|
+
inset,
|
|
162
|
+
...props
|
|
163
|
+
}: ContextMenuPrimitive.CheckboxItem.Props & {
|
|
164
|
+
inset?: boolean;
|
|
165
|
+
}) {
|
|
166
|
+
return (
|
|
167
|
+
<ContextMenuPrimitive.CheckboxItem
|
|
168
|
+
data-slot="context-menu-checkbox-item"
|
|
169
|
+
data-inset={inset}
|
|
170
|
+
className={cn(
|
|
171
|
+
"focus:bg-accent focus:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
172
|
+
className,
|
|
173
|
+
)}
|
|
174
|
+
checked={checked}
|
|
175
|
+
{...props}
|
|
176
|
+
>
|
|
177
|
+
<span className="absolute right-2 pointer-events-none">
|
|
178
|
+
<ContextMenuPrimitive.CheckboxItemIndicator>
|
|
179
|
+
<CheckIcon />
|
|
180
|
+
</ContextMenuPrimitive.CheckboxItemIndicator>
|
|
181
|
+
</span>
|
|
182
|
+
{children}
|
|
183
|
+
</ContextMenuPrimitive.CheckboxItem>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function ContextMenuRadioGroup({
|
|
188
|
+
...props
|
|
189
|
+
}: ContextMenuPrimitive.RadioGroup.Props) {
|
|
190
|
+
return (
|
|
191
|
+
<ContextMenuPrimitive.RadioGroup
|
|
192
|
+
data-slot="context-menu-radio-group"
|
|
193
|
+
{...props}
|
|
194
|
+
/>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function ContextMenuRadioItem({
|
|
199
|
+
className,
|
|
200
|
+
children,
|
|
201
|
+
inset,
|
|
202
|
+
...props
|
|
203
|
+
}: ContextMenuPrimitive.RadioItem.Props & {
|
|
204
|
+
inset?: boolean;
|
|
205
|
+
}) {
|
|
206
|
+
return (
|
|
207
|
+
<ContextMenuPrimitive.RadioItem
|
|
208
|
+
data-slot="context-menu-radio-item"
|
|
209
|
+
data-inset={inset}
|
|
210
|
+
className={cn(
|
|
211
|
+
"focus:bg-accent focus:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
212
|
+
className,
|
|
213
|
+
)}
|
|
214
|
+
{...props}
|
|
215
|
+
>
|
|
216
|
+
<span className="absolute right-2 pointer-events-none">
|
|
217
|
+
<ContextMenuPrimitive.RadioItemIndicator>
|
|
218
|
+
<CheckIcon />
|
|
219
|
+
</ContextMenuPrimitive.RadioItemIndicator>
|
|
220
|
+
</span>
|
|
221
|
+
{children}
|
|
222
|
+
</ContextMenuPrimitive.RadioItem>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function ContextMenuSeparator({
|
|
227
|
+
className,
|
|
228
|
+
...props
|
|
229
|
+
}: ContextMenuPrimitive.Separator.Props) {
|
|
230
|
+
return (
|
|
231
|
+
<ContextMenuPrimitive.Separator
|
|
232
|
+
data-slot="context-menu-separator"
|
|
233
|
+
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
|
234
|
+
{...props}
|
|
235
|
+
/>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function ContextMenuShortcut({
|
|
240
|
+
className,
|
|
241
|
+
...props
|
|
242
|
+
}: React.ComponentProps<"span">) {
|
|
243
|
+
return (
|
|
244
|
+
<span
|
|
245
|
+
data-slot="context-menu-shortcut"
|
|
246
|
+
className={cn(
|
|
247
|
+
"text-muted-foreground group-focus/context-menu-item:text-accent-foreground ml-auto text-xs tracking-widest",
|
|
248
|
+
className,
|
|
249
|
+
)}
|
|
250
|
+
{...props}
|
|
251
|
+
/>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export {
|
|
256
|
+
ContextMenu,
|
|
257
|
+
ContextMenuTrigger,
|
|
258
|
+
ContextMenuContent,
|
|
259
|
+
ContextMenuItem,
|
|
260
|
+
ContextMenuCheckboxItem,
|
|
261
|
+
ContextMenuRadioItem,
|
|
262
|
+
ContextMenuLabel,
|
|
263
|
+
ContextMenuSeparator,
|
|
264
|
+
ContextMenuShortcut,
|
|
265
|
+
ContextMenuGroup,
|
|
266
|
+
ContextMenuPortal,
|
|
267
|
+
ContextMenuSub,
|
|
268
|
+
ContextMenuSubContent,
|
|
269
|
+
ContextMenuSubTrigger,
|
|
270
|
+
ContextMenuRadioGroup,
|
|
271
|
+
};
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { NfcIcon } from "lucide-react";
|
|
2
|
+
import type * as React from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../../lib/utils/css";
|
|
5
|
+
|
|
6
|
+
// The card is laid out once at this design size, then transform-scaled to the
|
|
7
|
+
// requested width — so every variant stays pixel-identical at any size.
|
|
8
|
+
const BASE_WIDTH = 316;
|
|
9
|
+
const BASE_HEIGHT = 190;
|
|
10
|
+
|
|
11
|
+
type CreditCardType =
|
|
12
|
+
| "transparent"
|
|
13
|
+
| "transparent-gradient"
|
|
14
|
+
| "brand-dark"
|
|
15
|
+
| "brand-light"
|
|
16
|
+
| "gray-dark"
|
|
17
|
+
| "gray-light"
|
|
18
|
+
| "transparent-strip"
|
|
19
|
+
| "gray-strip"
|
|
20
|
+
| "gradient-strip"
|
|
21
|
+
| "salmon-strip"
|
|
22
|
+
| "gray-strip-vertical"
|
|
23
|
+
| "gradient-strip-vertical"
|
|
24
|
+
| "salmon-strip-vertical";
|
|
25
|
+
|
|
26
|
+
interface CreditCardVariantStyle {
|
|
27
|
+
card: string;
|
|
28
|
+
/** Decorative band rendered behind the content. */
|
|
29
|
+
strip?: string;
|
|
30
|
+
/** Light content on a dark or colorful face — switches the network logo. */
|
|
31
|
+
onDark: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const GLASS_FACE =
|
|
35
|
+
"border border-white/30 bg-white/10 text-white backdrop-blur-md";
|
|
36
|
+
const LIGHT_FACE =
|
|
37
|
+
"bg-[#fffefc] text-[#1c1917] ring-1 ring-[#0c0a09]/10 ring-inset";
|
|
38
|
+
const HORIZONTAL_STRIP = "inset-x-0 top-16 h-9";
|
|
39
|
+
const VERTICAL_STRIP = "inset-y-0 right-12 w-9";
|
|
40
|
+
|
|
41
|
+
const creditCardVariants: Record<CreditCardType, CreditCardVariantStyle> = {
|
|
42
|
+
"brand-dark": {
|
|
43
|
+
card: "bg-gradient-to-br from-[#a8761c] via-[#92600a] to-[#5e3f08] text-[#faf3df]",
|
|
44
|
+
onDark: true,
|
|
45
|
+
},
|
|
46
|
+
"brand-light": {
|
|
47
|
+
card: "bg-gradient-to-br from-[#ecd9a8] via-[#dcb35e] to-[#c0922e] text-[#41290a]",
|
|
48
|
+
onDark: false,
|
|
49
|
+
},
|
|
50
|
+
"gradient-strip": {
|
|
51
|
+
card: LIGHT_FACE,
|
|
52
|
+
onDark: false,
|
|
53
|
+
strip: cn(
|
|
54
|
+
HORIZONTAL_STRIP,
|
|
55
|
+
"bg-gradient-to-r from-[#d4a84b] via-[#c48a1a] to-[#92600a]",
|
|
56
|
+
),
|
|
57
|
+
},
|
|
58
|
+
"gradient-strip-vertical": {
|
|
59
|
+
card: LIGHT_FACE,
|
|
60
|
+
onDark: false,
|
|
61
|
+
strip: cn(
|
|
62
|
+
VERTICAL_STRIP,
|
|
63
|
+
"bg-gradient-to-b from-[#d4a84b] via-[#c48a1a] to-[#92600a]",
|
|
64
|
+
),
|
|
65
|
+
},
|
|
66
|
+
"gray-dark": {
|
|
67
|
+
card: "bg-gradient-to-br from-[#3b3633] via-[#1c1917] to-[#0c0a09] text-[#faf9f6]",
|
|
68
|
+
onDark: true,
|
|
69
|
+
},
|
|
70
|
+
"gray-light": {
|
|
71
|
+
card: "bg-gradient-to-br from-[#faf9f6] to-[#e7e5e4] text-[#1c1917] ring-1 ring-[#0c0a09]/10 ring-inset",
|
|
72
|
+
onDark: false,
|
|
73
|
+
},
|
|
74
|
+
"gray-strip": {
|
|
75
|
+
card: LIGHT_FACE,
|
|
76
|
+
onDark: false,
|
|
77
|
+
strip: cn(HORIZONTAL_STRIP, "bg-[#292524]"),
|
|
78
|
+
},
|
|
79
|
+
"gray-strip-vertical": {
|
|
80
|
+
card: LIGHT_FACE,
|
|
81
|
+
onDark: false,
|
|
82
|
+
strip: cn(VERTICAL_STRIP, "bg-[#292524]"),
|
|
83
|
+
},
|
|
84
|
+
"salmon-strip": {
|
|
85
|
+
card: LIGHT_FACE,
|
|
86
|
+
onDark: false,
|
|
87
|
+
strip: cn(HORIZONTAL_STRIP, "bg-gradient-to-r from-[#e8a18f] to-[#d4644f]"),
|
|
88
|
+
},
|
|
89
|
+
"salmon-strip-vertical": {
|
|
90
|
+
card: LIGHT_FACE,
|
|
91
|
+
onDark: false,
|
|
92
|
+
strip: cn(VERTICAL_STRIP, "bg-gradient-to-b from-[#e8a18f] to-[#d4644f]"),
|
|
93
|
+
},
|
|
94
|
+
transparent: {
|
|
95
|
+
card: GLASS_FACE,
|
|
96
|
+
onDark: true,
|
|
97
|
+
},
|
|
98
|
+
"transparent-gradient": {
|
|
99
|
+
card: "border border-white/25 bg-gradient-to-br from-[#d4a84b]/60 via-white/10 to-white/5 text-white backdrop-blur-md",
|
|
100
|
+
onDark: true,
|
|
101
|
+
},
|
|
102
|
+
"transparent-strip": {
|
|
103
|
+
card: GLASS_FACE,
|
|
104
|
+
onDark: true,
|
|
105
|
+
strip: cn(HORIZONTAL_STRIP, "bg-white/25"),
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/** The card-network mark — two overlapping circles in the kit's palette. */
|
|
110
|
+
function CreditCardLogo({ onDark }: { onDark: boolean }) {
|
|
111
|
+
return (
|
|
112
|
+
<span aria-hidden className="flex items-center" data-slot="credit-card-logo">
|
|
113
|
+
<span
|
|
114
|
+
className={cn(
|
|
115
|
+
"size-6 rounded-full",
|
|
116
|
+
onDark ? "bg-white/60" : "bg-[#d4a84b]",
|
|
117
|
+
)}
|
|
118
|
+
/>
|
|
119
|
+
<span
|
|
120
|
+
className={cn(
|
|
121
|
+
"-ml-2.5 size-6 rounded-full",
|
|
122
|
+
onDark ? "bg-white/30" : "bg-[#92600a]/85",
|
|
123
|
+
)}
|
|
124
|
+
/>
|
|
125
|
+
</span>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
interface CreditCardProps extends React.ComponentProps<"div"> {
|
|
130
|
+
type?: CreditCardType;
|
|
131
|
+
company?: React.ReactNode;
|
|
132
|
+
cardNumber?: string;
|
|
133
|
+
cardHolder?: string;
|
|
134
|
+
cardExpiration?: string;
|
|
135
|
+
/** Rendered width in px — the height follows the 316 × 190 aspect ratio. */
|
|
136
|
+
width?: number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function CreditCard({
|
|
140
|
+
className,
|
|
141
|
+
style,
|
|
142
|
+
type = "brand-dark",
|
|
143
|
+
company = "Country Club",
|
|
144
|
+
cardNumber = "1234 5678 9012 3456",
|
|
145
|
+
cardHolder = "OLIVIA RHYE",
|
|
146
|
+
cardExpiration = "06/28",
|
|
147
|
+
width = BASE_WIDTH,
|
|
148
|
+
...props
|
|
149
|
+
}: CreditCardProps) {
|
|
150
|
+
const scale = width / BASE_WIDTH;
|
|
151
|
+
const variant = creditCardVariants[type];
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<div
|
|
155
|
+
className={cn("relative shrink-0 select-none font-sans", className)}
|
|
156
|
+
data-slot="credit-card"
|
|
157
|
+
data-type={type}
|
|
158
|
+
style={{ height: Math.round(BASE_HEIGHT * scale), width, ...style }}
|
|
159
|
+
{...props}
|
|
160
|
+
>
|
|
161
|
+
<div
|
|
162
|
+
className={cn(
|
|
163
|
+
"absolute top-0 left-0 flex origin-top-left flex-col justify-between overflow-hidden rounded-[20px] p-5",
|
|
164
|
+
variant.card,
|
|
165
|
+
)}
|
|
166
|
+
style={{
|
|
167
|
+
height: BASE_HEIGHT,
|
|
168
|
+
transform: `scale(${scale})`,
|
|
169
|
+
width: BASE_WIDTH,
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
{variant.strip ? (
|
|
173
|
+
<span aria-hidden className={cn("absolute", variant.strip)} />
|
|
174
|
+
) : null}
|
|
175
|
+
<div className="relative flex items-start justify-between">
|
|
176
|
+
<span
|
|
177
|
+
className="font-semibold text-base tracking-tight"
|
|
178
|
+
data-slot="credit-card-company"
|
|
179
|
+
>
|
|
180
|
+
{company}
|
|
181
|
+
</span>
|
|
182
|
+
<NfcIcon aria-hidden className="size-5 opacity-70" />
|
|
183
|
+
</div>
|
|
184
|
+
<div className="relative flex flex-col gap-2.5">
|
|
185
|
+
<span
|
|
186
|
+
className="font-medium text-[0.9375rem] tabular-nums tracking-[0.12em]"
|
|
187
|
+
data-slot="credit-card-number"
|
|
188
|
+
>
|
|
189
|
+
{cardNumber}
|
|
190
|
+
</span>
|
|
191
|
+
<div className="flex items-end justify-between">
|
|
192
|
+
<div className="flex items-baseline gap-4">
|
|
193
|
+
<span
|
|
194
|
+
className="font-medium text-[0.6875rem] uppercase tracking-[0.14em]"
|
|
195
|
+
data-slot="credit-card-holder"
|
|
196
|
+
>
|
|
197
|
+
{cardHolder}
|
|
198
|
+
</span>
|
|
199
|
+
<span
|
|
200
|
+
className="font-medium text-[0.6875rem] tabular-nums tracking-[0.14em] opacity-80"
|
|
201
|
+
data-slot="credit-card-expiration"
|
|
202
|
+
>
|
|
203
|
+
{cardExpiration}
|
|
204
|
+
</span>
|
|
205
|
+
</div>
|
|
206
|
+
<CreditCardLogo onDark={variant.onDark} />
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { CreditCard, type CreditCardProps, type CreditCardType };
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
|
|
4
|
+
import { XIcon } from "lucide-react";
|
|
5
|
+
import { Button } from "./button";
|
|
6
|
+
import { ScrollArea } from "./scroll-area";
|
|
7
|
+
import { cn } from "../../lib/utils/css";
|
|
8
|
+
|
|
9
|
+
const DialogCreateHandle = DialogPrimitive.createHandle;
|
|
10
|
+
|
|
11
|
+
const Dialog = DialogPrimitive.Root;
|
|
12
|
+
|
|
13
|
+
const DialogPortal = DialogPrimitive.Portal;
|
|
14
|
+
|
|
15
|
+
function DialogTrigger(props: DialogPrimitive.Trigger.Props) {
|
|
16
|
+
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function DialogClose(props: DialogPrimitive.Close.Props) {
|
|
20
|
+
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function DialogBackdrop({
|
|
24
|
+
className,
|
|
25
|
+
...props
|
|
26
|
+
}: DialogPrimitive.Backdrop.Props) {
|
|
27
|
+
return (
|
|
28
|
+
<DialogPrimitive.Backdrop
|
|
29
|
+
className={cn(
|
|
30
|
+
"fixed inset-0 z-50 bg-black/32 backdrop-blur-sm transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0",
|
|
31
|
+
className,
|
|
32
|
+
)}
|
|
33
|
+
data-slot="dialog-backdrop"
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function DialogViewport({
|
|
40
|
+
className,
|
|
41
|
+
...props
|
|
42
|
+
}: DialogPrimitive.Viewport.Props) {
|
|
43
|
+
return (
|
|
44
|
+
<DialogPrimitive.Viewport
|
|
45
|
+
className={cn(
|
|
46
|
+
"fixed inset-0 z-50 grid grid-rows-[1fr_auto_3fr] justify-items-center p-4",
|
|
47
|
+
className,
|
|
48
|
+
)}
|
|
49
|
+
data-slot="dialog-viewport"
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function DialogPopup({
|
|
56
|
+
className,
|
|
57
|
+
children,
|
|
58
|
+
showCloseButton = true,
|
|
59
|
+
bottomStickOnMobile = true,
|
|
60
|
+
...props
|
|
61
|
+
}: DialogPrimitive.Popup.Props & {
|
|
62
|
+
showCloseButton?: boolean;
|
|
63
|
+
bottomStickOnMobile?: boolean;
|
|
64
|
+
}) {
|
|
65
|
+
return (
|
|
66
|
+
<DialogPortal>
|
|
67
|
+
<DialogBackdrop />
|
|
68
|
+
<DialogViewport
|
|
69
|
+
className={cn(
|
|
70
|
+
bottomStickOnMobile &&
|
|
71
|
+
"max-sm:grid-rows-[1fr_auto] max-sm:p-0 max-sm:pt-12",
|
|
72
|
+
)}
|
|
73
|
+
>
|
|
74
|
+
<DialogPrimitive.Popup
|
|
75
|
+
className={cn(
|
|
76
|
+
"-translate-y-[calc(1.25rem*var(--nested-dialogs))] relative row-start-2 flex max-h-full min-h-0 w-full min-w-0 max-w-lg scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl border bg-popover not-dark:bg-clip-padding text-popover-foreground opacity-[calc(1-0.1*var(--nested-dialogs))] shadow-lg/5 transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-2xl)-1px)] before:shadow-[0_1px_--theme(--color-black/6%)] data-nested:data-ending-style:translate-y-8 data-nested:data-starting-style:translate-y-8 data-nested-dialog-open:origin-top data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
|
|
77
|
+
bottomStickOnMobile &&
|
|
78
|
+
"max-sm:max-w-none max-sm:rounded-none max-sm:border-x-0 max-sm:border-t max-sm:border-b-0 max-sm:opacity-[calc(1-min(var(--nested-dialogs),1))] max-sm:data-ending-style:translate-y-4 max-sm:data-starting-style:translate-y-4 max-sm:before:hidden max-sm:before:rounded-none",
|
|
79
|
+
className,
|
|
80
|
+
)}
|
|
81
|
+
data-slot="dialog-popup"
|
|
82
|
+
{...props}
|
|
83
|
+
>
|
|
84
|
+
{children}
|
|
85
|
+
{showCloseButton && (
|
|
86
|
+
<DialogPrimitive.Close
|
|
87
|
+
aria-label="Close"
|
|
88
|
+
className="absolute end-2 top-2"
|
|
89
|
+
render={<Button size="icon" variant="ghost" />}
|
|
90
|
+
>
|
|
91
|
+
<XIcon />
|
|
92
|
+
</DialogPrimitive.Close>
|
|
93
|
+
)}
|
|
94
|
+
</DialogPrimitive.Popup>
|
|
95
|
+
</DialogViewport>
|
|
96
|
+
</DialogPortal>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
101
|
+
return (
|
|
102
|
+
<div
|
|
103
|
+
className={cn(
|
|
104
|
+
"flex flex-col gap-2 p-6 in-[[data-slot=dialog-popup]:has([data-slot=dialog-panel])]:pb-3 max-sm:pb-4",
|
|
105
|
+
className,
|
|
106
|
+
)}
|
|
107
|
+
data-slot="dialog-header"
|
|
108
|
+
{...props}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function DialogFooter({
|
|
114
|
+
className,
|
|
115
|
+
variant = "default",
|
|
116
|
+
...props
|
|
117
|
+
}: React.ComponentProps<"div"> & {
|
|
118
|
+
variant?: "default" | "bare";
|
|
119
|
+
}) {
|
|
120
|
+
return (
|
|
121
|
+
<div
|
|
122
|
+
className={cn(
|
|
123
|
+
"flex flex-col-reverse gap-2 px-6 sm:flex-row sm:justify-end sm:rounded-b-[calc(var(--radius-2xl)-1px)]",
|
|
124
|
+
variant === "default" && "border-t bg-muted/72 py-4",
|
|
125
|
+
variant === "bare" &&
|
|
126
|
+
"in-[[data-slot=dialog-popup]:has([data-slot=dialog-panel])]:pt-3 pt-4 pb-6",
|
|
127
|
+
className,
|
|
128
|
+
)}
|
|
129
|
+
data-slot="dialog-footer"
|
|
130
|
+
{...props}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
|
|
136
|
+
return (
|
|
137
|
+
<DialogPrimitive.Title
|
|
138
|
+
className={cn(
|
|
139
|
+
"font-heading font-semibold text-xl leading-none",
|
|
140
|
+
className,
|
|
141
|
+
)}
|
|
142
|
+
data-slot="dialog-title"
|
|
143
|
+
{...props}
|
|
144
|
+
/>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function DialogDescription({
|
|
149
|
+
className,
|
|
150
|
+
...props
|
|
151
|
+
}: DialogPrimitive.Description.Props) {
|
|
152
|
+
return (
|
|
153
|
+
<DialogPrimitive.Description
|
|
154
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
155
|
+
data-slot="dialog-description"
|
|
156
|
+
{...props}
|
|
157
|
+
/>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function DialogPanel({
|
|
162
|
+
className,
|
|
163
|
+
scrollFade = true,
|
|
164
|
+
...props
|
|
165
|
+
}: React.ComponentProps<"div"> & { scrollFade?: boolean }) {
|
|
166
|
+
return (
|
|
167
|
+
<ScrollArea scrollFade={scrollFade}>
|
|
168
|
+
<div
|
|
169
|
+
className={cn(
|
|
170
|
+
"p-6 in-[[data-slot=dialog-popup]:has([data-slot=dialog-header])]:pt-1 in-[[data-slot=dialog-popup]:has([data-slot=dialog-footer]:not(.border-t))]:pb-1",
|
|
171
|
+
className,
|
|
172
|
+
)}
|
|
173
|
+
data-slot="dialog-panel"
|
|
174
|
+
{...props}
|
|
175
|
+
/>
|
|
176
|
+
</ScrollArea>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export {
|
|
181
|
+
DialogCreateHandle,
|
|
182
|
+
Dialog,
|
|
183
|
+
DialogTrigger,
|
|
184
|
+
DialogPortal,
|
|
185
|
+
DialogClose,
|
|
186
|
+
DialogBackdrop,
|
|
187
|
+
DialogBackdrop as DialogOverlay,
|
|
188
|
+
DialogPopup,
|
|
189
|
+
DialogPopup as DialogContent,
|
|
190
|
+
DialogHeader,
|
|
191
|
+
DialogFooter,
|
|
192
|
+
DialogTitle,
|
|
193
|
+
DialogDescription,
|
|
194
|
+
DialogPanel,
|
|
195
|
+
DialogViewport,
|
|
196
|
+
};
|