@cntyclub/ui-react 0.8.2 → 0.10.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/index.d.ts +129 -5
- package/dist/index.js +260 -5
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/src/components/ui/animated-counter.tsx +109 -0
- package/src/components/ui/confetti.tsx +196 -0
- package/src/components/ui/date-picker.tsx +154 -0
- package/src/index.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cntyclub/ui-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "React component library for the Country Club UI Kit — Base UI primitives styled with the Country Club design system (Tailwind CSS v4)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@tiptap/pm": "^3.26.1",
|
|
40
40
|
"@tiptap/react": "^3.26.1",
|
|
41
41
|
"@tiptap/starter-kit": "^3.26.1",
|
|
42
|
+
"canvas-confetti": "^1.9.3",
|
|
42
43
|
"class-variance-authority": "^0.7.1",
|
|
43
44
|
"clsx": "^2.1.1",
|
|
44
45
|
"date-fns": "^4.1.0",
|
|
@@ -64,6 +65,7 @@
|
|
|
64
65
|
"react-dom": "^19.0.0"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
68
|
+
"@types/canvas-confetti": "^1.9.0",
|
|
67
69
|
"@types/react": "^19.1.0",
|
|
68
70
|
"@types/react-dom": "^19.1.0",
|
|
69
71
|
"react": "^19.1.0",
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils/css";
|
|
7
|
+
|
|
8
|
+
interface AnimatedCounterProps extends Omit<React.ComponentProps<"span">, "children"> {
|
|
9
|
+
/** The target value to animate to. */
|
|
10
|
+
value: number;
|
|
11
|
+
/** Tween duration in ms (default 650). */
|
|
12
|
+
duration?: number;
|
|
13
|
+
/** Decimal places to render (default 0). */
|
|
14
|
+
decimals?: number;
|
|
15
|
+
/** Prefix rendered before the number, e.g. "$". */
|
|
16
|
+
prefix?: string;
|
|
17
|
+
/** Suffix rendered after the number, e.g. "%". */
|
|
18
|
+
suffix?: string;
|
|
19
|
+
/** Group digits with thousands separators (default true). */
|
|
20
|
+
separator?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A number that tweens smoothly from its previous value to the next whenever
|
|
25
|
+
* `value` changes — e.g. dashboard stats re-counting when the time range
|
|
26
|
+
* switches. Dependency-free (requestAnimationFrame), so it stays tiny and works
|
|
27
|
+
* anywhere. Respects `prefers-reduced-motion` by snapping to the final value.
|
|
28
|
+
*
|
|
29
|
+
* The animation is purely visual; the accessible text is always the final
|
|
30
|
+
* value (via `aria-label`) so screen readers never read the intermediate tween.
|
|
31
|
+
*/
|
|
32
|
+
function AnimatedCounter({
|
|
33
|
+
value,
|
|
34
|
+
duration = 650,
|
|
35
|
+
decimals = 0,
|
|
36
|
+
prefix = "",
|
|
37
|
+
suffix = "",
|
|
38
|
+
separator = true,
|
|
39
|
+
className,
|
|
40
|
+
...props
|
|
41
|
+
}: AnimatedCounterProps) {
|
|
42
|
+
const [display, setDisplay] = useState(value);
|
|
43
|
+
const fromRef = useRef(value);
|
|
44
|
+
const rafRef = useRef<number | null>(null);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const from = fromRef.current;
|
|
48
|
+
const to = value;
|
|
49
|
+
if (from === to) return;
|
|
50
|
+
|
|
51
|
+
const reduce =
|
|
52
|
+
typeof window !== "undefined" &&
|
|
53
|
+
window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
54
|
+
if (reduce || duration <= 0) {
|
|
55
|
+
fromRef.current = to;
|
|
56
|
+
setDisplay(to);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let start: number | null = null;
|
|
61
|
+
const step = (ts: number) => {
|
|
62
|
+
if (start === null) start = ts;
|
|
63
|
+
const t = Math.min(1, (ts - start) / duration);
|
|
64
|
+
// easeOutCubic — quick then settles, matching a "count up" feel.
|
|
65
|
+
const eased = 1 - Math.pow(1 - t, 3);
|
|
66
|
+
const current = from + (to - from) * eased;
|
|
67
|
+
setDisplay(current);
|
|
68
|
+
if (t < 1) {
|
|
69
|
+
rafRef.current = requestAnimationFrame(step);
|
|
70
|
+
} else {
|
|
71
|
+
fromRef.current = to;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
rafRef.current = requestAnimationFrame(step);
|
|
75
|
+
return () => {
|
|
76
|
+
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
|
|
77
|
+
};
|
|
78
|
+
}, [value, duration]);
|
|
79
|
+
|
|
80
|
+
const rounded = Number(display.toFixed(decimals));
|
|
81
|
+
const formatted = separator
|
|
82
|
+
? rounded.toLocaleString(undefined, {
|
|
83
|
+
minimumFractionDigits: decimals,
|
|
84
|
+
maximumFractionDigits: decimals,
|
|
85
|
+
})
|
|
86
|
+
: rounded.toFixed(decimals);
|
|
87
|
+
|
|
88
|
+
const finalLabel = `${prefix}${value.toLocaleString(undefined, {
|
|
89
|
+
minimumFractionDigits: decimals,
|
|
90
|
+
maximumFractionDigits: decimals,
|
|
91
|
+
})}${suffix}`;
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<span
|
|
95
|
+
className={cn("tabular-nums", className)}
|
|
96
|
+
data-slot="animated-counter"
|
|
97
|
+
aria-label={finalLabel}
|
|
98
|
+
{...props}
|
|
99
|
+
>
|
|
100
|
+
<span aria-hidden>
|
|
101
|
+
{prefix}
|
|
102
|
+
{formatted}
|
|
103
|
+
{suffix}
|
|
104
|
+
</span>
|
|
105
|
+
</span>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export { AnimatedCounter, type AnimatedCounterProps };
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import confetti from "canvas-confetti";
|
|
4
|
+
import type {
|
|
5
|
+
GlobalOptions as ConfettiGlobalOptions,
|
|
6
|
+
CreateTypes as ConfettiInstance,
|
|
7
|
+
Options as ConfettiOptions,
|
|
8
|
+
} from "canvas-confetti";
|
|
9
|
+
import {
|
|
10
|
+
createContext,
|
|
11
|
+
forwardRef,
|
|
12
|
+
useCallback,
|
|
13
|
+
useContext,
|
|
14
|
+
useEffect,
|
|
15
|
+
useImperativeHandle,
|
|
16
|
+
useMemo,
|
|
17
|
+
useRef,
|
|
18
|
+
type ReactNode,
|
|
19
|
+
} from "react";
|
|
20
|
+
import type * as React from "react";
|
|
21
|
+
|
|
22
|
+
import { Button, type ButtonProps } from "./button";
|
|
23
|
+
|
|
24
|
+
/** The brand celebration palette — soft violet / pink / peach / cream. */
|
|
25
|
+
export const CONFETTI_COLORS = ["#a786ff", "#fd8bbc", "#eca184", "#f8deb1"] as const;
|
|
26
|
+
|
|
27
|
+
function prefersReducedMotion(): boolean {
|
|
28
|
+
return (
|
|
29
|
+
typeof window !== "undefined" &&
|
|
30
|
+
!!window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SideCannonsOptions {
|
|
35
|
+
/** How long the cannons keep firing, in ms (default 3000). */
|
|
36
|
+
durationMs?: number;
|
|
37
|
+
/** Confetti colors (default {@link CONFETTI_COLORS}). */
|
|
38
|
+
colors?: readonly string[];
|
|
39
|
+
/** Particles emitted per side per frame (default 2). */
|
|
40
|
+
particleCount?: number;
|
|
41
|
+
/** z-index of the confetti canvas (default 100). */
|
|
42
|
+
zIndex?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Fires confetti from the left and right edges of the viewport for a few
|
|
47
|
+
* seconds — the "side cannons" celebration. Purely imperative, so it can be
|
|
48
|
+
* triggered from an effect (e.g. the first time a page loads) without rendering
|
|
49
|
+
* anything. No-ops on the server and when the user prefers reduced motion.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* useEffect(() => { sideCannons(); }, []);
|
|
53
|
+
*/
|
|
54
|
+
export function sideCannons(options: SideCannonsOptions = {}): void {
|
|
55
|
+
if (typeof window === "undefined" || prefersReducedMotion()) return;
|
|
56
|
+
|
|
57
|
+
const {
|
|
58
|
+
durationMs = 3000,
|
|
59
|
+
colors = CONFETTI_COLORS,
|
|
60
|
+
particleCount = 2,
|
|
61
|
+
zIndex = 100,
|
|
62
|
+
} = options;
|
|
63
|
+
|
|
64
|
+
const end = performance.now() + durationMs;
|
|
65
|
+
const shared = { particleCount, spread: 55, startVelocity: 60, colors: [...colors], zIndex };
|
|
66
|
+
|
|
67
|
+
const frame = () => {
|
|
68
|
+
if (performance.now() > end) return;
|
|
69
|
+
void confetti({ ...shared, angle: 60, origin: { x: 0, y: 0.5 } });
|
|
70
|
+
void confetti({ ...shared, angle: 120, origin: { x: 1, y: 0.5 } });
|
|
71
|
+
requestAnimationFrame(frame);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
frame();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type ConfettiApi = {
|
|
78
|
+
/** Fire the confetti instance, merging `options` over the component's. */
|
|
79
|
+
fire: (options?: ConfettiOptions) => void;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type ConfettiRef = ConfettiApi | null;
|
|
83
|
+
|
|
84
|
+
export type ConfettiProps = React.ComponentPropsWithRef<"canvas"> & {
|
|
85
|
+
/** Per-shot options merged into every `fire()` call. */
|
|
86
|
+
options?: ConfettiOptions;
|
|
87
|
+
/** Global options passed to `confetti.create` (resize / worker). */
|
|
88
|
+
globalOptions?: ConfettiGlobalOptions;
|
|
89
|
+
/** Skip the automatic fire on mount — drive it via the ref instead. */
|
|
90
|
+
manualstart?: boolean;
|
|
91
|
+
children?: ReactNode;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const ConfettiContext = createContext<ConfettiApi>({} as ConfettiApi);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A canvas-bound confetti emitter. Renders its own `<canvas>` and exposes an
|
|
98
|
+
* imperative `fire()` via ref (and to descendants through context). By default
|
|
99
|
+
* it fires once on mount; pass `manualstart` to control it yourself.
|
|
100
|
+
*
|
|
101
|
+
* For a full-screen, no-render celebration prefer {@link sideCannons}.
|
|
102
|
+
*/
|
|
103
|
+
const ConfettiComponent = forwardRef<ConfettiRef, ConfettiProps>((props, ref) => {
|
|
104
|
+
const {
|
|
105
|
+
options,
|
|
106
|
+
globalOptions = { resize: true, useWorker: true },
|
|
107
|
+
manualstart = false,
|
|
108
|
+
children,
|
|
109
|
+
...rest
|
|
110
|
+
} = props;
|
|
111
|
+
const instanceRef = useRef<ConfettiInstance | null>(null);
|
|
112
|
+
|
|
113
|
+
const canvasRef = useCallback(
|
|
114
|
+
(node: HTMLCanvasElement | null) => {
|
|
115
|
+
if (node !== null) {
|
|
116
|
+
if (instanceRef.current) return;
|
|
117
|
+
instanceRef.current = confetti.create(node, { ...globalOptions, resize: true });
|
|
118
|
+
} else if (instanceRef.current) {
|
|
119
|
+
instanceRef.current.reset();
|
|
120
|
+
instanceRef.current = null;
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
[globalOptions],
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const fire = useCallback(
|
|
127
|
+
async (opts: ConfettiOptions = {}) => {
|
|
128
|
+
if (prefersReducedMotion()) return;
|
|
129
|
+
try {
|
|
130
|
+
await instanceRef.current?.({ ...options, ...opts });
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error("Confetti error:", error);
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
[options],
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const api = useMemo<ConfettiApi>(() => ({ fire }), [fire]);
|
|
139
|
+
|
|
140
|
+
useImperativeHandle(ref, () => api, [api]);
|
|
141
|
+
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (!manualstart) void fire();
|
|
144
|
+
}, [manualstart, fire]);
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<ConfettiContext.Provider value={api}>
|
|
148
|
+
<canvas data-slot="confetti" ref={canvasRef} {...rest} />
|
|
149
|
+
{children}
|
|
150
|
+
</ConfettiContext.Provider>
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
ConfettiComponent.displayName = "Confetti";
|
|
154
|
+
|
|
155
|
+
export const Confetti = ConfettiComponent;
|
|
156
|
+
|
|
157
|
+
/** Access the nearest {@link Confetti}'s imperative `fire()` from a child. */
|
|
158
|
+
export function useConfetti(): ConfettiApi {
|
|
159
|
+
return useContext(ConfettiContext);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface ConfettiButtonProps extends ButtonProps {
|
|
163
|
+
/** Confetti options; origin defaults to the button's center. */
|
|
164
|
+
options?: ConfettiOptions & ConfettiGlobalOptions & { canvas?: HTMLCanvasElement };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* A {@link Button} that bursts confetti from its own center on click. Handy for
|
|
169
|
+
* "Claim", "Done", or other one-off celebratory actions.
|
|
170
|
+
*/
|
|
171
|
+
function ConfettiButtonComponent({ options, children, onClick, ...props }: ConfettiButtonProps) {
|
|
172
|
+
const handleClick = async (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
173
|
+
onClick?.(event);
|
|
174
|
+
if (prefersReducedMotion()) return;
|
|
175
|
+
try {
|
|
176
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
177
|
+
const x = rect.left + rect.width / 2;
|
|
178
|
+
const y = rect.top + rect.height / 2;
|
|
179
|
+
await confetti({
|
|
180
|
+
...options,
|
|
181
|
+
origin: { x: x / window.innerWidth, y: y / window.innerHeight },
|
|
182
|
+
});
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error("Confetti button error:", error);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<Button data-slot="confetti-button" onClick={handleClick} {...props}>
|
|
190
|
+
{children}
|
|
191
|
+
</Button>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
ConfettiButtonComponent.displayName = "ConfettiButton";
|
|
195
|
+
|
|
196
|
+
export const ConfettiButton = ConfettiButtonComponent;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { format } from "date-fns";
|
|
4
|
+
import { CalendarIcon } from "lucide-react";
|
|
5
|
+
import type { DateRange, Matcher } from "react-day-picker";
|
|
6
|
+
|
|
7
|
+
import { Button } from "./button";
|
|
8
|
+
import { Calendar } from "./calendar";
|
|
9
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
|
10
|
+
import { cn } from "../../lib/utils/css";
|
|
11
|
+
|
|
12
|
+
/** Build react-day-picker `disabled` matchers from optional min/max bounds. */
|
|
13
|
+
function boundsMatcher(fromDate?: Date, toDate?: Date): Matcher[] | undefined {
|
|
14
|
+
const out: Matcher[] = [];
|
|
15
|
+
if (fromDate) out.push({ before: fromDate });
|
|
16
|
+
if (toDate) out.push({ after: toDate });
|
|
17
|
+
return out.length ? out : undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface DatePickerProps {
|
|
21
|
+
/** The selected date (controlled). */
|
|
22
|
+
value?: Date;
|
|
23
|
+
onValueChange?: (date: Date | undefined) => void;
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
/** Trigger button `disabled`. */
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
/** Restrict selectable days (passed straight to react-day-picker). */
|
|
28
|
+
fromDate?: Date;
|
|
29
|
+
toDate?: Date;
|
|
30
|
+
/** date-fns format for the trigger label (default "LLL dd, y"). */
|
|
31
|
+
displayFormat?: string;
|
|
32
|
+
align?: "start" | "center" | "end";
|
|
33
|
+
className?: string;
|
|
34
|
+
id?: string;
|
|
35
|
+
"aria-label"?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A single-date picker: a button that opens a calendar in a popover. The
|
|
40
|
+
* canonical way to take a date in this system — never a native `<input
|
|
41
|
+
* type="date">`. Controlled via `value` / `onValueChange`.
|
|
42
|
+
*/
|
|
43
|
+
function DatePicker({
|
|
44
|
+
value,
|
|
45
|
+
onValueChange,
|
|
46
|
+
placeholder = "Pick a date",
|
|
47
|
+
disabled,
|
|
48
|
+
fromDate,
|
|
49
|
+
toDate,
|
|
50
|
+
displayFormat = "LLL dd, y",
|
|
51
|
+
align = "start",
|
|
52
|
+
className,
|
|
53
|
+
id,
|
|
54
|
+
...props
|
|
55
|
+
}: DatePickerProps) {
|
|
56
|
+
return (
|
|
57
|
+
<Popover>
|
|
58
|
+
<PopoverTrigger
|
|
59
|
+
render={
|
|
60
|
+
<Button
|
|
61
|
+
className={cn("w-full justify-start font-normal", !value && "text-muted-foreground", className)}
|
|
62
|
+
disabled={disabled}
|
|
63
|
+
id={id}
|
|
64
|
+
variant="outline"
|
|
65
|
+
aria-label={props["aria-label"]}
|
|
66
|
+
/>
|
|
67
|
+
}
|
|
68
|
+
>
|
|
69
|
+
<CalendarIcon aria-hidden />
|
|
70
|
+
{value ? format(value, displayFormat) : <span>{placeholder}</span>}
|
|
71
|
+
</PopoverTrigger>
|
|
72
|
+
<PopoverContent align={align} className="w-auto p-0">
|
|
73
|
+
<Calendar
|
|
74
|
+
autoFocus
|
|
75
|
+
disabled={boundsMatcher(fromDate, toDate)}
|
|
76
|
+
mode="single"
|
|
77
|
+
onSelect={(d) => onValueChange?.(d)}
|
|
78
|
+
selected={value}
|
|
79
|
+
/>
|
|
80
|
+
</PopoverContent>
|
|
81
|
+
</Popover>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface DateRangePickerProps {
|
|
86
|
+
value?: DateRange;
|
|
87
|
+
onValueChange?: (range: DateRange | undefined) => void;
|
|
88
|
+
placeholder?: string;
|
|
89
|
+
disabled?: boolean;
|
|
90
|
+
fromDate?: Date;
|
|
91
|
+
toDate?: Date;
|
|
92
|
+
numberOfMonths?: number;
|
|
93
|
+
displayFormat?: string;
|
|
94
|
+
align?: "start" | "center" | "end";
|
|
95
|
+
className?: string;
|
|
96
|
+
id?: string;
|
|
97
|
+
"aria-label"?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A date-range picker: pick a start and (optionally) end date. Controlled via
|
|
102
|
+
* `value` / `onValueChange` with react-day-picker's `{from, to}` shape.
|
|
103
|
+
*/
|
|
104
|
+
function DateRangePicker({
|
|
105
|
+
value,
|
|
106
|
+
onValueChange,
|
|
107
|
+
placeholder = "Pick a date range",
|
|
108
|
+
disabled,
|
|
109
|
+
fromDate,
|
|
110
|
+
toDate,
|
|
111
|
+
numberOfMonths = 2,
|
|
112
|
+
displayFormat = "LLL dd, y",
|
|
113
|
+
align = "start",
|
|
114
|
+
className,
|
|
115
|
+
id,
|
|
116
|
+
...props
|
|
117
|
+
}: DateRangePickerProps) {
|
|
118
|
+
const label = value?.from
|
|
119
|
+
? value.to
|
|
120
|
+
? `${format(value.from, displayFormat)} – ${format(value.to, displayFormat)}`
|
|
121
|
+
: format(value.from, displayFormat)
|
|
122
|
+
: null;
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Popover>
|
|
126
|
+
<PopoverTrigger
|
|
127
|
+
render={
|
|
128
|
+
<Button
|
|
129
|
+
className={cn("w-full justify-start font-normal", !label && "text-muted-foreground", className)}
|
|
130
|
+
disabled={disabled}
|
|
131
|
+
id={id}
|
|
132
|
+
variant="outline"
|
|
133
|
+
aria-label={props["aria-label"]}
|
|
134
|
+
/>
|
|
135
|
+
}
|
|
136
|
+
>
|
|
137
|
+
<CalendarIcon aria-hidden />
|
|
138
|
+
{label ?? <span>{placeholder}</span>}
|
|
139
|
+
</PopoverTrigger>
|
|
140
|
+
<PopoverContent align={align} className="w-auto p-0">
|
|
141
|
+
<Calendar
|
|
142
|
+
autoFocus
|
|
143
|
+
disabled={boundsMatcher(fromDate, toDate)}
|
|
144
|
+
mode="range"
|
|
145
|
+
numberOfMonths={numberOfMonths}
|
|
146
|
+
onSelect={(r) => onValueChange?.(r)}
|
|
147
|
+
selected={value}
|
|
148
|
+
/>
|
|
149
|
+
</PopoverContent>
|
|
150
|
+
</Popover>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export { DatePicker, DateRangePicker, type DatePickerProps, type DateRangePickerProps };
|
package/src/index.ts
CHANGED
|
@@ -11,11 +11,13 @@ export * from "./components/ui/aspect-ratio";
|
|
|
11
11
|
export * from "./components/ui/autocomplete";
|
|
12
12
|
export * from "./components/ui/avatar";
|
|
13
13
|
export * from "./components/ui/avatar-group";
|
|
14
|
+
export * from "./components/ui/animated-counter";
|
|
14
15
|
export * from "./components/ui/badge";
|
|
15
16
|
export * from "./components/ui/badge-group";
|
|
16
17
|
export * from "./components/ui/breadcrumb";
|
|
17
18
|
export * from "./components/ui/button";
|
|
18
19
|
export * from "./components/ui/calendar";
|
|
20
|
+
export * from "./components/ui/date-picker";
|
|
19
21
|
export * from "./components/ui/card";
|
|
20
22
|
export * from "./components/ui/carousel";
|
|
21
23
|
export * from "./components/ui/chart";
|
|
@@ -25,6 +27,7 @@ export * from "./components/ui/checkbox-group";
|
|
|
25
27
|
export * from "./components/ui/collapsible";
|
|
26
28
|
export * from "./components/ui/combobox";
|
|
27
29
|
export * from "./components/ui/command";
|
|
30
|
+
export * from "./components/ui/confetti";
|
|
28
31
|
export * from "./components/ui/credit-card";
|
|
29
32
|
export * from "./components/ui/data-table-paged";
|
|
30
33
|
export * from "./components/ui/drawer";
|