@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,289 @@
1
+ "use client";
2
+
3
+ import { Progress as ProgressPrimitive } from "@base-ui/react/progress";
4
+ import type * as React from "react";
5
+
6
+ import { cn } from "../../lib/utils/css";
7
+
8
+ function Progress({
9
+ className,
10
+ children,
11
+ ...props
12
+ }: ProgressPrimitive.Root.Props) {
13
+ return (
14
+ <ProgressPrimitive.Root
15
+ className={cn("flex w-full flex-col gap-2", className)}
16
+ data-slot="progress"
17
+ {...props}
18
+ >
19
+ {children ? (
20
+ children
21
+ ) : (
22
+ <ProgressTrack>
23
+ <ProgressIndicator />
24
+ </ProgressTrack>
25
+ )}
26
+ </ProgressPrimitive.Root>
27
+ );
28
+ }
29
+
30
+ function ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {
31
+ return (
32
+ <ProgressPrimitive.Label
33
+ className={cn("font-medium text-sm", className)}
34
+ data-slot="progress-label"
35
+ {...props}
36
+ />
37
+ );
38
+ }
39
+
40
+ function ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {
41
+ return (
42
+ <ProgressPrimitive.Track
43
+ className={cn(
44
+ "block h-1.5 w-full overflow-hidden rounded-full bg-input",
45
+ className,
46
+ )}
47
+ data-slot="progress-track"
48
+ {...props}
49
+ />
50
+ );
51
+ }
52
+
53
+ function ProgressIndicator({
54
+ className,
55
+ ...props
56
+ }: ProgressPrimitive.Indicator.Props) {
57
+ return (
58
+ <ProgressPrimitive.Indicator
59
+ className={cn("bg-primary transition-all duration-500", className)}
60
+ data-slot="progress-indicator"
61
+ {...props}
62
+ />
63
+ );
64
+ }
65
+
66
+ function ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {
67
+ return (
68
+ <ProgressPrimitive.Value
69
+ className={cn("text-sm tabular-nums", className)}
70
+ data-slot="progress-value"
71
+ {...props}
72
+ />
73
+ );
74
+ }
75
+
76
+ /**
77
+ * A tooltip-like value bubble that floats above or below the track,
78
+ * following the progress percentage. Render it next to a ProgressTrack
79
+ * inside a `relative` wrapper.
80
+ */
81
+ function ProgressFloatingValue({
82
+ className,
83
+ side = "top",
84
+ value,
85
+ max = 100,
86
+ children,
87
+ style,
88
+ ...props
89
+ }: React.ComponentProps<"div"> & {
90
+ side?: "top" | "bottom";
91
+ value: number;
92
+ max?: number;
93
+ }) {
94
+ const percent = Math.min(100, Math.max(0, (value / max) * 100));
95
+ return (
96
+ <div
97
+ className={cn(
98
+ "absolute -translate-x-1/2 transition-[left] duration-500",
99
+ side === "top" ? "bottom-full mb-2" : "top-full mt-2",
100
+ className,
101
+ )}
102
+ data-side={side}
103
+ data-slot="progress-floating-value"
104
+ style={{
105
+ left: `clamp(1.25rem, ${percent}%, calc(100% - 1.25rem))`,
106
+ ...style,
107
+ }}
108
+ {...props}
109
+ >
110
+ <div className="rounded-md border bg-popover px-2 py-1 font-semibold text-popover-foreground text-xs tabular-nums shadow-md/5">
111
+ {children ?? `${Math.round(percent)}%`}
112
+ </div>
113
+ </div>
114
+ );
115
+ }
116
+
117
+ const progressCircleSizes = {
118
+ sm: { label: "text-[0.625rem]", px: 64, stroke: 6, value: "text-sm" },
119
+ md: { label: "text-xs", px: 120, stroke: 10, value: "text-2xl" },
120
+ lg: { label: "text-sm", px: 160, stroke: 12, value: "text-3xl" },
121
+ } as const;
122
+
123
+ type ProgressCircleSize = keyof typeof progressCircleSizes;
124
+
125
+ interface ProgressCircleProps extends ProgressPrimitive.Root.Props {
126
+ size?: ProgressCircleSize;
127
+ /** A short caption rendered above the value, inside the circle. */
128
+ label?: React.ReactNode;
129
+ /** Hide the centered percentage. */
130
+ showValue?: boolean;
131
+ }
132
+
133
+ /** A circular progress ring with an optional centered value and label. */
134
+ function ProgressCircle({
135
+ className,
136
+ size = "md",
137
+ label,
138
+ showValue = true,
139
+ value,
140
+ max = 100,
141
+ ...props
142
+ }: ProgressCircleProps) {
143
+ const { px, stroke, value: valueClass, label: labelClass } =
144
+ progressCircleSizes[size];
145
+ const radius = (px - stroke) / 2;
146
+ const percent = Math.min(100, Math.max(0, ((value ?? 0) / max) * 100));
147
+
148
+ return (
149
+ <ProgressPrimitive.Root
150
+ className={cn(
151
+ "relative inline-flex items-center justify-center",
152
+ className,
153
+ )}
154
+ data-slot="progress-circle"
155
+ max={max}
156
+ value={value}
157
+ {...props}
158
+ >
159
+ <svg
160
+ className="-rotate-90"
161
+ fill="none"
162
+ height={px}
163
+ viewBox={`0 0 ${px} ${px}`}
164
+ width={px}
165
+ >
166
+ <circle
167
+ className="text-input"
168
+ cx={px / 2}
169
+ cy={px / 2}
170
+ r={radius}
171
+ stroke="currentColor"
172
+ strokeWidth={stroke}
173
+ />
174
+ <circle
175
+ className="text-primary transition-[stroke-dashoffset] duration-500"
176
+ cx={px / 2}
177
+ cy={px / 2}
178
+ pathLength={100}
179
+ r={radius}
180
+ stroke="currentColor"
181
+ strokeDasharray={100}
182
+ strokeDashoffset={100 - percent}
183
+ strokeLinecap="round"
184
+ strokeWidth={stroke}
185
+ />
186
+ </svg>
187
+ {showValue || label ? (
188
+ <div className="absolute inset-0 flex flex-col items-center justify-center">
189
+ {label ? (
190
+ <span className={cn("text-muted-foreground", labelClass)}>
191
+ {label}
192
+ </span>
193
+ ) : null}
194
+ {showValue ? (
195
+ <ProgressPrimitive.Value
196
+ className={cn(
197
+ "font-semibold text-foreground tabular-nums",
198
+ valueClass,
199
+ )}
200
+ data-slot="progress-circle-value"
201
+ />
202
+ ) : null}
203
+ </div>
204
+ ) : null}
205
+ </ProgressPrimitive.Root>
206
+ );
207
+ }
208
+
209
+ /** A half-circle (gauge) progress arc with an optional value and label. */
210
+ function ProgressHalfCircle({
211
+ className,
212
+ size = "md",
213
+ label,
214
+ showValue = true,
215
+ value,
216
+ max = 100,
217
+ ...props
218
+ }: ProgressCircleProps) {
219
+ const { px, stroke, value: valueClass, label: labelClass } =
220
+ progressCircleSizes[size];
221
+ const radius = (px - stroke) / 2;
222
+ const height = px / 2 + stroke / 2;
223
+ const arc = `M ${stroke / 2} ${px / 2} A ${radius} ${radius} 0 0 1 ${px - stroke / 2} ${px / 2}`;
224
+ const percent = Math.min(100, Math.max(0, ((value ?? 0) / max) * 100));
225
+
226
+ return (
227
+ <ProgressPrimitive.Root
228
+ className={cn("relative inline-flex justify-center", className)}
229
+ data-slot="progress-half-circle"
230
+ max={max}
231
+ value={value}
232
+ {...props}
233
+ >
234
+ <svg
235
+ fill="none"
236
+ height={height}
237
+ viewBox={`0 0 ${px} ${height}`}
238
+ width={px}
239
+ >
240
+ <path
241
+ className="text-input"
242
+ d={arc}
243
+ stroke="currentColor"
244
+ strokeLinecap="round"
245
+ strokeWidth={stroke}
246
+ />
247
+ <path
248
+ className="text-primary transition-[stroke-dashoffset] duration-500"
249
+ d={arc}
250
+ pathLength={100}
251
+ stroke="currentColor"
252
+ strokeDasharray={100}
253
+ strokeDashoffset={100 - percent}
254
+ strokeLinecap="round"
255
+ strokeWidth={stroke}
256
+ />
257
+ </svg>
258
+ {showValue || label ? (
259
+ <div className="absolute inset-x-0 bottom-0 flex flex-col items-center">
260
+ {label ? (
261
+ <span className={cn("text-muted-foreground", labelClass)}>
262
+ {label}
263
+ </span>
264
+ ) : null}
265
+ {showValue ? (
266
+ <ProgressPrimitive.Value
267
+ className={cn(
268
+ "font-semibold text-foreground tabular-nums",
269
+ valueClass,
270
+ )}
271
+ data-slot="progress-half-circle-value"
272
+ />
273
+ ) : null}
274
+ </div>
275
+ ) : null}
276
+ </ProgressPrimitive.Root>
277
+ );
278
+ }
279
+
280
+ export {
281
+ Progress,
282
+ ProgressLabel,
283
+ ProgressTrack,
284
+ ProgressIndicator,
285
+ ProgressValue,
286
+ ProgressFloatingValue,
287
+ ProgressCircle,
288
+ ProgressHalfCircle,
289
+ };
@@ -0,0 +1,150 @@
1
+ "use client";
2
+
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import type { Options as QRCodeStylingOptions } from "qr-code-styling";
5
+ import * as React from "react";
6
+
7
+ import { cn } from "../../lib/utils/css";
8
+
9
+ const qrCodeVariants = cva(
10
+ "relative inline-flex shrink-0 items-center justify-center text-foreground [&_canvas]:size-full [&_svg]:size-full",
11
+ {
12
+ defaultVariants: { size: "md" },
13
+ variants: {
14
+ size: {
15
+ sm: "size-24",
16
+ md: "size-40",
17
+ lg: "size-60",
18
+ },
19
+ },
20
+ },
21
+ );
22
+
23
+ interface QRCodeProps
24
+ extends Omit<React.ComponentProps<"div">, "children">,
25
+ VariantProps<typeof qrCodeVariants> {
26
+ /** The text or URL encoded in the QR code. */
27
+ value: string;
28
+ /**
29
+ * Extra qr-code-styling options merged over the themed defaults — e.g. a
30
+ * center image, custom dot colors or shapes. See the qr-code-styling docs.
31
+ */
32
+ options?: Partial<QRCodeStylingOptions>;
33
+ }
34
+
35
+ /**
36
+ * A themeable QR code rendered with qr-code-styling. Dots follow the
37
+ * foreground color (and re-tint when the theme flips) unless overridden
38
+ * via `options`.
39
+ */
40
+ function QRCode({ className, size, value, options, ...props }: QRCodeProps) {
41
+ const containerRef = React.useRef<HTMLDivElement>(null);
42
+
43
+ React.useEffect(() => {
44
+ const container = containerRef.current;
45
+ if (!container) return;
46
+
47
+ let disposed = false;
48
+ let observer: MutationObserver | undefined;
49
+
50
+ const {
51
+ dotsOptions,
52
+ cornersSquareOptions,
53
+ cornersDotOptions,
54
+ imageOptions,
55
+ ...rest
56
+ } = options ?? {};
57
+
58
+ // The container's computed color is the resolved foreground token, so the
59
+ // code stays on-theme without hard-coding hex values.
60
+ const themedShapes = () => {
61
+ const color = getComputedStyle(container).color;
62
+ return {
63
+ cornersDotOptions: { color, ...cornersDotOptions },
64
+ cornersSquareOptions: {
65
+ color,
66
+ type: "extra-rounded" as const,
67
+ ...cornersSquareOptions,
68
+ },
69
+ dotsOptions: { color, type: "rounded" as const, ...dotsOptions },
70
+ };
71
+ };
72
+
73
+ // qr-code-styling touches the DOM at import time, so load it lazily to
74
+ // keep the component SSR-safe.
75
+ import("qr-code-styling").then(({ default: QRCodeStyling }) => {
76
+ if (disposed) return;
77
+ const edge = container.clientWidth || 160;
78
+ const qr = new QRCodeStyling({
79
+ backgroundOptions: { color: "transparent" },
80
+ data: value,
81
+ height: edge,
82
+ imageOptions: {
83
+ crossOrigin: "anonymous",
84
+ imageSize: 0.4,
85
+ margin: 4,
86
+ ...imageOptions,
87
+ },
88
+ margin: 0,
89
+ qrOptions: { errorCorrectionLevel: "Q" },
90
+ type: "svg",
91
+ width: edge,
92
+ ...themedShapes(),
93
+ ...rest,
94
+ });
95
+ container.replaceChildren();
96
+ qr.append(container);
97
+
98
+ observer = new MutationObserver(() => qr.update(themedShapes()));
99
+ observer.observe(document.documentElement, {
100
+ attributeFilter: ["data-theme", "class"],
101
+ attributes: true,
102
+ });
103
+ });
104
+
105
+ return () => {
106
+ disposed = true;
107
+ observer?.disconnect();
108
+ container.replaceChildren();
109
+ };
110
+ }, [value, size, options]);
111
+
112
+ return (
113
+ <div
114
+ aria-label={`QR code for ${value}`}
115
+ className={cn(qrCodeVariants({ size }), className)}
116
+ data-slot="qr-code"
117
+ ref={containerRef}
118
+ role="img"
119
+ {...props}
120
+ />
121
+ );
122
+ }
123
+
124
+ /**
125
+ * An animated scan-line overlay for a QR code. Render it inside a `relative`
126
+ * wrapper around the `<QRCode />`.
127
+ */
128
+ function QRCodeGradientScan({
129
+ className,
130
+ ...props
131
+ }: React.ComponentProps<"div">) {
132
+ return (
133
+ <div
134
+ aria-hidden="true"
135
+ className={cn(
136
+ "pointer-events-none absolute inset-0 overflow-hidden rounded-lg",
137
+ className,
138
+ )}
139
+ data-slot="qr-code-gradient-scan"
140
+ {...props}
141
+ >
142
+ <div className="absolute inset-x-0 top-0 h-1/2 animate-qr-scan will-change-transform">
143
+ <div className="absolute inset-0 bg-gradient-to-b from-transparent via-brand/20 to-transparent" />
144
+ <div className="absolute inset-x-0 top-1/2 h-px bg-brand/80 shadow-[0_0_10px_1px_var(--brand)]" />
145
+ </div>
146
+ </div>
147
+ );
148
+ }
149
+
150
+ export { QRCode, QRCodeGradientScan, qrCodeVariants };
@@ -0,0 +1,103 @@
1
+ "use client";
2
+
3
+ import { Radio as RadioPrimitive } from "@base-ui/react/radio";
4
+ import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
5
+
6
+ import { cn } from "../../lib/utils/css";
7
+
8
+ function RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {
9
+ return (
10
+ <RadioGroupPrimitive
11
+ className={cn("flex flex-col gap-3", className)}
12
+ data-slot="radio-group"
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ const radioSizes = {
19
+ sm: {
20
+ box: "size-4.5 sm:size-4",
21
+ indicator: "size-4.5 sm:size-4",
22
+ dot: "before:size-2 sm:before:size-1.5",
23
+ check: "size-3.5 sm:size-3",
24
+ },
25
+ md: {
26
+ box: "size-5",
27
+ indicator: "size-5",
28
+ dot: "before:size-2",
29
+ check: "size-4",
30
+ },
31
+ } as const;
32
+
33
+ interface RadioProps extends RadioPrimitive.Root.Props {
34
+ /** Visual size of the radio. Defaults to "sm". */
35
+ size?: keyof typeof radioSizes;
36
+ /** Indicator style: the classic dot or a checkbox-style tick. Defaults to "dot". */
37
+ variant?: "dot" | "check";
38
+ }
39
+
40
+ function Radio({
41
+ className,
42
+ size = "sm",
43
+ variant = "dot",
44
+ ...props
45
+ }: RadioProps) {
46
+ const sizes = radioSizes[size];
47
+ return (
48
+ <RadioPrimitive.Root
49
+ className={cn(
50
+ "relative inline-flex shrink-0 items-center justify-center border border-input bg-background not-dark:bg-clip-padding shadow-xs/5 outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/6%)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/48 data-disabled:opacity-64 dark:not-data-checked:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] [[data-disabled],[data-checked],[aria-invalid]]:shadow-none",
51
+ variant === "check"
52
+ ? "rounded-[4px] before:rounded-[3px]"
53
+ : "rounded-full before:rounded-full",
54
+ sizes.box,
55
+ className,
56
+ )}
57
+ data-slot="radio"
58
+ {...props}
59
+ >
60
+ {variant === "check" ? (
61
+ <RadioPrimitive.Indicator
62
+ className="-inset-px absolute flex items-center justify-center rounded-[4px] text-primary-foreground transition-opacity duration-150 ease-out data-checked:bg-primary data-checked:opacity-100 data-unchecked:opacity-0"
63
+ data-slot="radio-indicator"
64
+ keepMounted
65
+ >
66
+ {/* biome-ignore lint/a11y/noSvgWithoutTitle: decorative indicator */}
67
+ <svg
68
+ className={sizes.check}
69
+ fill="none"
70
+ height="24"
71
+ stroke="currentColor"
72
+ strokeLinecap="round"
73
+ strokeLinejoin="round"
74
+ strokeWidth="3"
75
+ viewBox="0 0 24 24"
76
+ width="24"
77
+ xmlns="http://www.w3.org/2000/svg"
78
+ >
79
+ {/* Same draw-in motion as the Checkbox: the tick strokes itself in
80
+ a beat after the fill fades in. */}
81
+ <path
82
+ className="[stroke-dasharray:1] [stroke-dashoffset:1] [transition:stroke-dashoffset_250ms_cubic-bezier(0.65,0,0.35,1)_110ms] in-[[data-checked]]:[stroke-dashoffset:0]"
83
+ d="M5.252 12.7 10.2 18.63 18.748 5.37"
84
+ pathLength={1}
85
+ />
86
+ </svg>
87
+ </RadioPrimitive.Indicator>
88
+ ) : (
89
+ <RadioPrimitive.Indicator
90
+ className={cn(
91
+ "-inset-px absolute flex origin-center items-center justify-center rounded-full transition-[background-color,box-shadow] duration-150 ease-out before:rounded-full before:bg-primary-foreground before:transition-[transform,opacity] before:duration-200 before:ease-[cubic-bezier(0.34,1.56,0.64,1)] data-checked:bg-primary data-checked:before:scale-100 data-checked:before:opacity-100 data-unchecked:before:scale-0 data-unchecked:before:opacity-0",
92
+ sizes.indicator,
93
+ sizes.dot,
94
+ )}
95
+ data-slot="radio-indicator"
96
+ keepMounted
97
+ />
98
+ )}
99
+ </RadioPrimitive.Root>
100
+ );
101
+ }
102
+
103
+ export { RadioGroup, Radio, Radio as RadioGroupItem };
@@ -0,0 +1,56 @@
1
+ "use client";
2
+
3
+ import { GripVerticalIcon } from "lucide-react";
4
+ import type * as React from "react";
5
+ import * as ResizablePrimitive from "react-resizable-panels";
6
+
7
+ import { cn } from "../../lib/utils/css";
8
+
9
+ function ResizablePanelGroup({
10
+ className,
11
+ ...props
12
+ }: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) {
13
+ return (
14
+ <ResizablePrimitive.PanelGroup
15
+ className={cn(
16
+ "flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
17
+ className,
18
+ )}
19
+ data-slot="resizable-panel-group"
20
+ {...props}
21
+ />
22
+ );
23
+ }
24
+
25
+ function ResizablePanel({
26
+ ...props
27
+ }: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
28
+ return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />;
29
+ }
30
+
31
+ function ResizableHandle({
32
+ withHandle,
33
+ className,
34
+ ...props
35
+ }: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
36
+ withHandle?: boolean;
37
+ }) {
38
+ return (
39
+ <ResizablePrimitive.PanelResizeHandle
40
+ className={cn(
41
+ "relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90",
42
+ className,
43
+ )}
44
+ data-slot="resizable-handle"
45
+ {...props}
46
+ >
47
+ {withHandle && (
48
+ <div className="z-10 flex h-4 w-3 items-center justify-center rounded-xs border bg-border">
49
+ <GripVerticalIcon className="size-2.5" />
50
+ </div>
51
+ )}
52
+ </ResizablePrimitive.PanelResizeHandle>
53
+ );
54
+ }
55
+
56
+ export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
@@ -0,0 +1,90 @@
1
+ "use client";
2
+
3
+ import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
4
+ import * as React from "react";
5
+
6
+ import { cn } from "../../lib/utils/css";
7
+ import { useSmoothWheel } from "./smooth-scroll";
8
+
9
+ function ScrollArea({
10
+ className,
11
+ children,
12
+ scrollFade = false,
13
+ scrollbarGutter = false,
14
+ wheelToHorizontal = false,
15
+ smooth = false,
16
+ viewportClassName,
17
+ ...props
18
+ }: ScrollAreaPrimitive.Root.Props & {
19
+ scrollFade?: boolean;
20
+ scrollbarGutter?: boolean;
21
+ /**
22
+ * Translate vertical mouse-wheel scrolling into (eased) horizontal scrolling
23
+ * while the pointer is over the viewport. The page only scrolls once this
24
+ * element reaches its horizontal start/end.
25
+ */
26
+ wheelToHorizontal?: boolean;
27
+ /** Eased, momentum-style vertical wheel scrolling. */
28
+ smooth?: boolean;
29
+ viewportClassName?: string;
30
+ }) {
31
+ const viewportRef = React.useRef<HTMLDivElement>(null);
32
+
33
+ // Eased horizontal scrolling (smooth + slower than a raw scrollLeft jump).
34
+ useSmoothWheel(viewportRef, { enabled: wheelToHorizontal, horizontal: true });
35
+ // Optional eased vertical scrolling.
36
+ useSmoothWheel(viewportRef, {
37
+ enabled: smooth && !wheelToHorizontal,
38
+ horizontal: false,
39
+ });
40
+
41
+ return (
42
+ <ScrollAreaPrimitive.Root
43
+ className={cn("flex flex-col min-h-0", className)}
44
+ {...props}
45
+ >
46
+ <ScrollAreaPrimitive.Viewport
47
+ ref={viewportRef}
48
+ className={cn(
49
+ "flex grow flex-col min-h-0 rounded-[inherit] outline-none transition-shadows focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain data-has-overflow-y:overscroll-y-contain",
50
+ scrollFade &&
51
+ "mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] [--fade-size:1.5rem]",
52
+ scrollbarGutter &&
53
+ "data-has-overflow-y:pe-2.5 data-has-overflow-x:pb-2.5",
54
+ viewportClassName,
55
+ )}
56
+ data-slot="scroll-area-viewport"
57
+ >
58
+ {children}
59
+ </ScrollAreaPrimitive.Viewport>
60
+ <ScrollBar orientation="vertical" />
61
+ <ScrollBar orientation="horizontal" />
62
+ <ScrollAreaPrimitive.Corner data-slot="scroll-area-corner" />
63
+ </ScrollAreaPrimitive.Root>
64
+ );
65
+ }
66
+
67
+ function ScrollBar({
68
+ className,
69
+ orientation = "vertical",
70
+ ...props
71
+ }: ScrollAreaPrimitive.Scrollbar.Props) {
72
+ return (
73
+ <ScrollAreaPrimitive.Scrollbar
74
+ className={cn(
75
+ "m-1 flex opacity-0 transition-opacity delay-300 data-[orientation=horizontal]:h-1.5 data-[orientation=vertical]:w-1.5 data-[orientation=horizontal]:flex-col data-hovering:opacity-100 data-scrolling:opacity-100 data-hovering:delay-0 data-scrolling:delay-0 data-hovering:duration-100 data-scrolling:duration-100",
76
+ className,
77
+ )}
78
+ data-slot="scroll-area-scrollbar"
79
+ orientation={orientation}
80
+ {...props}
81
+ >
82
+ <ScrollAreaPrimitive.Thumb
83
+ className="relative flex-1 rounded-full bg-foreground/20"
84
+ data-slot="scroll-area-thumb"
85
+ />
86
+ </ScrollAreaPrimitive.Scrollbar>
87
+ );
88
+ }
89
+
90
+ export { ScrollArea, ScrollBar };