@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.
Files changed (124) hide show
  1. package/dist/chunk-HDGMSYQS.js +26461 -0
  2. package/dist/chunk-HDGMSYQS.js.map +1 -0
  3. package/dist/chunk-PR4QN5HX.js +39 -0
  4. package/dist/chunk-PR4QN5HX.js.map +1 -0
  5. package/dist/form.d.ts +175 -0
  6. package/dist/form.js +5207 -0
  7. package/dist/form.js.map +1 -0
  8. package/dist/index.d.ts +1462 -0
  9. package/dist/index.js +81862 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/input-CZvh825j.d.ts +24 -0
  12. package/dist/qr-code-styling-3Y6LZH6V.js +1123 -0
  13. package/dist/qr-code-styling-3Y6LZH6V.js.map +1 -0
  14. package/package.json +79 -0
  15. package/src/components/form/checkbox-group-field.tsx +101 -0
  16. package/src/components/form/date-field.tsx +79 -0
  17. package/src/components/form/date-range-field.tsx +106 -0
  18. package/src/components/form/form-context.ts +10 -0
  19. package/src/components/form/form.tsx +54 -0
  20. package/src/components/form/number-field.tsx +69 -0
  21. package/src/components/form/select-field.tsx +76 -0
  22. package/src/components/form/submit-button.tsx +28 -0
  23. package/src/components/form/text-field.tsx +107 -0
  24. package/src/components/layout/dashboard-header.tsx +54 -0
  25. package/src/components/layout/dashboard-panel.tsx +34 -0
  26. package/src/components/theme-provider.tsx +403 -0
  27. package/src/components/ui/accordion.tsx +69 -0
  28. package/src/components/ui/alert-dialog.tsx +169 -0
  29. package/src/components/ui/alert.tsx +80 -0
  30. package/src/components/ui/animated-theme-toggler.tsx +265 -0
  31. package/src/components/ui/app-store-buttons.tsx +182 -0
  32. package/src/components/ui/aspect-ratio.tsx +23 -0
  33. package/src/components/ui/autocomplete.tsx +296 -0
  34. package/src/components/ui/avatar-group.tsx +95 -0
  35. package/src/components/ui/avatar.tsx +285 -0
  36. package/src/components/ui/badge-group.tsx +160 -0
  37. package/src/components/ui/badge.tsx +172 -0
  38. package/src/components/ui/breadcrumb.tsx +112 -0
  39. package/src/components/ui/button.tsx +77 -0
  40. package/src/components/ui/calendar.tsx +137 -0
  41. package/src/components/ui/card.tsx +244 -0
  42. package/src/components/ui/carousel.tsx +258 -0
  43. package/src/components/ui/chart.tsx +379 -0
  44. package/src/components/ui/checkbox-group.tsx +16 -0
  45. package/src/components/ui/checkbox.tsx +82 -0
  46. package/src/components/ui/collapsible.tsx +45 -0
  47. package/src/components/ui/combobox.tsx +411 -0
  48. package/src/components/ui/command.tsx +264 -0
  49. package/src/components/ui/context-menu.tsx +271 -0
  50. package/src/components/ui/credit-card.tsx +214 -0
  51. package/src/components/ui/dialog.tsx +196 -0
  52. package/src/components/ui/drawer.tsx +135 -0
  53. package/src/components/ui/empty.tsx +127 -0
  54. package/src/components/ui/featured-icon.tsx +149 -0
  55. package/src/components/ui/field.tsx +88 -0
  56. package/src/components/ui/fieldset.tsx +29 -0
  57. package/src/components/ui/form.tsx +17 -0
  58. package/src/components/ui/frame.tsx +82 -0
  59. package/src/components/ui/generic-empty.tsx +142 -0
  60. package/src/components/ui/group.tsx +97 -0
  61. package/src/components/ui/horizontal-scroll-fader.tsx +228 -0
  62. package/src/components/ui/input-group.tsx +102 -0
  63. package/src/components/ui/input-otp.tsx +96 -0
  64. package/src/components/ui/input.tsx +66 -0
  65. package/src/components/ui/item.tsx +198 -0
  66. package/src/components/ui/kbd.tsx +30 -0
  67. package/src/components/ui/label.tsx +28 -0
  68. package/src/components/ui/menu.tsx +312 -0
  69. package/src/components/ui/menubar.tsx +93 -0
  70. package/src/components/ui/meter.tsx +67 -0
  71. package/src/components/ui/multi-select.tsx +308 -0
  72. package/src/components/ui/navigation-menu.tsx +143 -0
  73. package/src/components/ui/number-field.tsx +160 -0
  74. package/src/components/ui/pagination-controls.tsx +74 -0
  75. package/src/components/ui/pagination.tsx +149 -0
  76. package/src/components/ui/popover.tsx +119 -0
  77. package/src/components/ui/preview-card.tsx +55 -0
  78. package/src/components/ui/progress.tsx +289 -0
  79. package/src/components/ui/qr-code.tsx +150 -0
  80. package/src/components/ui/radio-group.tsx +103 -0
  81. package/src/components/ui/resizable.tsx +56 -0
  82. package/src/components/ui/scroll-area.tsx +90 -0
  83. package/src/components/ui/scroller.tsx +38 -0
  84. package/src/components/ui/section-header.tsx +118 -0
  85. package/src/components/ui/select.tsx +181 -0
  86. package/src/components/ui/separator.tsx +23 -0
  87. package/src/components/ui/sheet.tsx +224 -0
  88. package/src/components/ui/sidebar.tsx +744 -0
  89. package/src/components/ui/skeleton.tsx +16 -0
  90. package/src/components/ui/slider.tsx +108 -0
  91. package/src/components/ui/smooth-scroll.tsx +143 -0
  92. package/src/components/ui/social-button.tsx +247 -0
  93. package/src/components/ui/spinner-on-demand.tsx +32 -0
  94. package/src/components/ui/spinner.tsx +18 -0
  95. package/src/components/ui/stat.tsx +187 -0
  96. package/src/components/ui/stepper.tsx +167 -0
  97. package/src/components/ui/switch.tsx +56 -0
  98. package/src/components/ui/table.tsx +126 -0
  99. package/src/components/ui/tabs.tsx +90 -0
  100. package/src/components/ui/tag.tsx +229 -0
  101. package/src/components/ui/target-countdown.tsx +46 -0
  102. package/src/components/ui/text-editor.tsx +313 -0
  103. package/src/components/ui/textarea.tsx +51 -0
  104. package/src/components/ui/timeline.tsx +116 -0
  105. package/src/components/ui/toast.tsx +268 -0
  106. package/src/components/ui/toggle-group.tsx +101 -0
  107. package/src/components/ui/toggle.tsx +45 -0
  108. package/src/components/ui/toolbar.tsx +89 -0
  109. package/src/components/ui/tooltip.tsx +102 -0
  110. package/src/components/ui/vertical-scroll-fader.tsx +250 -0
  111. package/src/components/ui/video-player.tsx +275 -0
  112. package/src/components/upload/avatar-upload-base.tsx +131 -0
  113. package/src/components/upload/image-upload-base.tsx +112 -0
  114. package/src/form.ts +17 -0
  115. package/src/index.ts +125 -0
  116. package/src/lib/hooks/use-callback-ref.ts +15 -0
  117. package/src/lib/hooks/use-first-render.ts +11 -0
  118. package/src/lib/hooks/use-hover.ts +53 -0
  119. package/src/lib/hooks/use-is-tab-active.ts +17 -0
  120. package/src/lib/hooks/use-media-query.ts +164 -0
  121. package/src/lib/utils/css.ts +6 -0
  122. package/src/styles.css +300 -0
  123. package/src/types/helpers.ts +24 -0
  124. 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
+ };