@opencosmos/ui 1.3.1
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/.claude/CLAUDE.md +239 -0
- package/README.md +161 -0
- package/dist/cli.mjs +151 -0
- package/dist/dates.d.mts +20 -0
- package/dist/dates.d.ts +20 -0
- package/dist/dates.js +240 -0
- package/dist/dates.js.map +1 -0
- package/dist/dates.mjs +203 -0
- package/dist/dates.mjs.map +1 -0
- package/dist/dnd.d.mts +126 -0
- package/dist/dnd.d.ts +126 -0
- package/dist/dnd.js +274 -0
- package/dist/dnd.js.map +1 -0
- package/dist/dnd.mjs +250 -0
- package/dist/dnd.mjs.map +1 -0
- package/dist/fontThemes-Dh8mtXES.d.mts +868 -0
- package/dist/fontThemes-Dh8mtXES.d.ts +868 -0
- package/dist/forms.d.mts +38 -0
- package/dist/forms.d.ts +38 -0
- package/dist/forms.js +198 -0
- package/dist/forms.js.map +1 -0
- package/dist/forms.mjs +159 -0
- package/dist/forms.mjs.map +1 -0
- package/dist/hooks-1b8WaQf1.d.mts +225 -0
- package/dist/hooks-CKW8vE9H.d.ts +225 -0
- package/dist/hooks.d.mts +3 -0
- package/dist/hooks.d.ts +3 -0
- package/dist/hooks.js +971 -0
- package/dist/hooks.js.map +1 -0
- package/dist/hooks.mjs +943 -0
- package/dist/hooks.mjs.map +1 -0
- package/dist/index-DscTIrZ2.d.mts +29 -0
- package/dist/index-DscTIrZ2.d.ts +29 -0
- package/dist/index.d.mts +3382 -0
- package/dist/index.d.ts +3382 -0
- package/dist/index.js +15146 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +14802 -0
- package/dist/index.mjs.map +1 -0
- package/dist/providers-CXPDMsl7.d.mts +30 -0
- package/dist/providers-Dn_Msjvz.d.ts +30 -0
- package/dist/providers.d.mts +3 -0
- package/dist/providers.d.ts +3 -0
- package/dist/providers.js +1885 -0
- package/dist/providers.js.map +1 -0
- package/dist/providers.mjs +1859 -0
- package/dist/providers.mjs.map +1 -0
- package/dist/tables.d.mts +10 -0
- package/dist/tables.d.ts +10 -0
- package/dist/tables.js +248 -0
- package/dist/tables.js.map +1 -0
- package/dist/tables.mjs +218 -0
- package/dist/tables.mjs.map +1 -0
- package/dist/tokens.d.mts +1065 -0
- package/dist/tokens.d.ts +1065 -0
- package/dist/tokens.js +2637 -0
- package/dist/tokens.js.map +1 -0
- package/dist/tokens.mjs +2555 -0
- package/dist/tokens.mjs.map +1 -0
- package/dist/utils-CIIM7dAC.d.ts +986 -0
- package/dist/utils-Cs04sxth.d.mts +986 -0
- package/dist/utils.d.mts +4 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +874 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.mjs +806 -0
- package/dist/utils.mjs.map +1 -0
- package/dist/validation-Bj1ye-v_.d.mts +114 -0
- package/dist/validation-Bj1ye-v_.d.ts +114 -0
- package/dist/webgl.d.mts +104 -0
- package/dist/webgl.d.ts +104 -0
- package/dist/webgl.js +226 -0
- package/dist/webgl.js.map +1 -0
- package/dist/webgl.mjs +195 -0
- package/dist/webgl.mjs.map +1 -0
- package/package.json +267 -0
- package/src/cli.ts +206 -0
- package/src/component-registry.ts +183 -0
- package/src/components/actions/Button.test.tsx +61 -0
- package/src/components/actions/Button.tsx +70 -0
- package/src/components/actions/Link.tsx +78 -0
- package/src/components/actions/Magnetic.tsx +68 -0
- package/src/components/actions/Toggle.test.tsx +40 -0
- package/src/components/actions/Toggle.tsx +47 -0
- package/src/components/actions/ToggleGroup.tsx +70 -0
- package/src/components/actions/index.ts +5 -0
- package/src/components/backgrounds/FaultyTerminal.tsx +426 -0
- package/src/components/backgrounds/OrbBackground.tsx +424 -0
- package/src/components/backgrounds/WarpBackground.tsx +358 -0
- package/src/components/backgrounds/index.ts +3 -0
- package/src/components/blocks/Hero.tsx +142 -0
- package/src/components/blocks/social/OpenGraphCard.tsx +243 -0
- package/src/components/cursor/SplashCursor.tsx +1315 -0
- package/src/components/cursor/TargetCursor.tsx +187 -0
- package/src/components/cursor/index.ts +2 -0
- package/src/components/data-display/AspectImage.tsx +73 -0
- package/src/components/data-display/Avatar.test.tsx +35 -0
- package/src/components/data-display/Avatar.tsx +55 -0
- package/src/components/data-display/Badge.test.tsx +43 -0
- package/src/components/data-display/Badge.tsx +84 -0
- package/src/components/data-display/Brand.tsx +123 -0
- package/src/components/data-display/Calendar.tsx +70 -0
- package/src/components/data-display/Card.test.tsx +92 -0
- package/src/components/data-display/Card.tsx +115 -0
- package/src/components/data-display/Code.tsx +210 -0
- package/src/components/data-display/CollapsibleCodeBlock.tsx +238 -0
- package/src/components/data-display/DataTable.tsx +119 -0
- package/src/components/data-display/DescriptionList.tsx +41 -0
- package/src/components/data-display/GitHubIcon.tsx +44 -0
- package/src/components/data-display/Heading.test.tsx +36 -0
- package/src/components/data-display/Heading.tsx +83 -0
- package/src/components/data-display/StatCard.tsx +195 -0
- package/src/components/data-display/Table.tsx +133 -0
- package/src/components/data-display/Text.test.tsx +48 -0
- package/src/components/data-display/Text.tsx +144 -0
- package/src/components/data-display/Timeline.tsx +194 -0
- package/src/components/data-display/TreeView.tsx +226 -0
- package/src/components/data-display/Typewriter.tsx +119 -0
- package/src/components/data-display/VariableWeightText.tsx +130 -0
- package/src/components/data-display/index.ts +19 -0
- package/src/components/feedback/Alert.test.tsx +44 -0
- package/src/components/feedback/Alert.tsx +65 -0
- package/src/components/feedback/EmptyState.tsx +113 -0
- package/src/components/feedback/Progress.test.tsx +60 -0
- package/src/components/feedback/Progress.tsx +30 -0
- package/src/components/feedback/ProgressBar.tsx +158 -0
- package/src/components/feedback/Skeleton.test.tsx +39 -0
- package/src/components/feedback/Skeleton.tsx +45 -0
- package/src/components/feedback/Sonner.tsx +28 -0
- package/src/components/feedback/Spinner.test.tsx +33 -0
- package/src/components/feedback/Spinner.tsx +99 -0
- package/src/components/feedback/Stepper.tsx +307 -0
- package/src/components/feedback/Toast/Toast.tsx +243 -0
- package/src/components/feedback/Toast/index.ts +2 -0
- package/src/components/feedback/index.ts +9 -0
- package/src/components/forms/Checkbox.test.tsx +40 -0
- package/src/components/forms/Checkbox.tsx +31 -0
- package/src/components/forms/ColorPicker.tsx +118 -0
- package/src/components/forms/Combobox.tsx +96 -0
- package/src/components/forms/DragDrop.tsx +440 -0
- package/src/components/forms/FileUpload.tsx +252 -0
- package/src/components/forms/FilterButton.tsx +65 -0
- package/src/components/forms/Form.tsx +197 -0
- package/src/components/forms/Input.test.tsx +46 -0
- package/src/components/forms/Input.tsx +43 -0
- package/src/components/forms/InputOTP.tsx +81 -0
- package/src/components/forms/Label.test.tsx +20 -0
- package/src/components/forms/Label.tsx +25 -0
- package/src/components/forms/RadioGroup.tsx +51 -0
- package/src/components/forms/SearchBar.tsx +215 -0
- package/src/components/forms/Select.test.tsx +118 -0
- package/src/components/forms/Select.tsx +274 -0
- package/src/components/forms/Slider.tsx +29 -0
- package/src/components/forms/Switch.test.tsx +76 -0
- package/src/components/forms/Switch.tsx +30 -0
- package/src/components/forms/TextField.tsx +152 -0
- package/src/components/forms/Textarea.test.tsx +41 -0
- package/src/components/forms/Textarea.tsx +29 -0
- package/src/components/forms/ThemeSwitcher.tsx +290 -0
- package/src/components/forms/ThemeToggle.tsx +151 -0
- package/src/components/forms/index.ts +19 -0
- package/src/components/layout/Accordion.test.tsx +66 -0
- package/src/components/layout/Accordion.tsx +64 -0
- package/src/components/layout/AspectRatio.tsx +7 -0
- package/src/components/layout/Carousel.tsx +277 -0
- package/src/components/layout/Collapsible.test.tsx +40 -0
- package/src/components/layout/Collapsible.tsx +31 -0
- package/src/components/layout/Container.test.tsx +45 -0
- package/src/components/layout/Container.tsx +99 -0
- package/src/components/layout/CustomizerPanel.tsx +400 -0
- package/src/components/layout/DatePicker.tsx +57 -0
- package/src/components/layout/Footer/Footer.tsx +175 -0
- package/src/components/layout/Footer/index.ts +2 -0
- package/src/components/layout/GlassSurface.tsx +82 -0
- package/src/components/layout/Grid.test.tsx +31 -0
- package/src/components/layout/Grid.tsx +130 -0
- package/src/components/layout/Header/Header.tsx +450 -0
- package/src/components/layout/Header/index.ts +2 -0
- package/src/components/layout/PageLayout.tsx +180 -0
- package/src/components/layout/PageTemplate.tsx +158 -0
- package/src/components/layout/Resizable.tsx +48 -0
- package/src/components/layout/ScrollArea.tsx +53 -0
- package/src/components/layout/Separator.test.tsx +28 -0
- package/src/components/layout/Separator.tsx +29 -0
- package/src/components/layout/Sidebar.tsx +171 -0
- package/src/components/layout/Stack.test.tsx +41 -0
- package/src/components/layout/Stack.tsx +89 -0
- package/src/components/layout/glass-surface.css +60 -0
- package/src/components/layout/index.ts +18 -0
- package/src/components/motion/AnimatedBeam.tsx +159 -0
- package/src/components/navigation/Breadcrumb.test.tsx +57 -0
- package/src/components/navigation/Breadcrumb.tsx +119 -0
- package/src/components/navigation/Breadcrumbs.tsx +221 -0
- package/src/components/navigation/Command.tsx +159 -0
- package/src/components/navigation/Menubar.tsx +115 -0
- package/src/components/navigation/NavLink.tsx +55 -0
- package/src/components/navigation/NavigationMenu.tsx +125 -0
- package/src/components/navigation/Pagination.tsx +121 -0
- package/src/components/navigation/SecondaryNav.tsx +100 -0
- package/src/components/navigation/Tabs.test.tsx +47 -0
- package/src/components/navigation/Tabs.tsx +60 -0
- package/src/components/navigation/TertiaryNav.tsx +90 -0
- package/src/components/navigation/index.ts +10 -0
- package/src/components/overlays/AlertDialog.test.tsx +69 -0
- package/src/components/overlays/AlertDialog.tsx +166 -0
- package/src/components/overlays/ContextMenu.tsx +243 -0
- package/src/components/overlays/Dialog.test.tsx +79 -0
- package/src/components/overlays/Dialog.tsx +158 -0
- package/src/components/overlays/Drawer.tsx +128 -0
- package/src/components/overlays/Dropdown.tsx +253 -0
- package/src/components/overlays/DropdownMenu.tsx +242 -0
- package/src/components/overlays/HoverCard.tsx +32 -0
- package/src/components/overlays/Modal.tsx +250 -0
- package/src/components/overlays/NotificationCenter.tsx +364 -0
- package/src/components/overlays/Popover.test.tsx +40 -0
- package/src/components/overlays/Popover.tsx +46 -0
- package/src/components/overlays/Sheet.tsx +163 -0
- package/src/components/overlays/Tooltip.test.tsx +33 -0
- package/src/components/overlays/Tooltip.tsx +32 -0
- package/src/components/overlays/index.ts +12 -0
- package/src/dates.ts +2 -0
- package/src/dnd.ts +1 -0
- package/src/forms.ts +1 -0
- package/src/globals.css +187 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/useForm.ts +247 -0
- package/src/hooks/useMotionPreference.test.ts +102 -0
- package/src/hooks/useMotionPreference.ts +78 -0
- package/src/hooks/useTheme.ts +58 -0
- package/src/hooks.ts +9 -0
- package/src/index.ts +168 -0
- package/src/lib/animations.ts +356 -0
- package/src/lib/breadcrumbs.ts +94 -0
- package/src/lib/colors.ts +493 -0
- package/src/lib/store/customizer.ts +482 -0
- package/src/lib/store/index.ts +3 -0
- package/src/lib/store/theme.ts +55 -0
- package/src/lib/syntax-parser/index.ts +50 -0
- package/src/lib/syntax-parser/patterns.ts +64 -0
- package/src/lib/syntax-parser/tokenizer.ts +117 -0
- package/src/lib/syntax-parser/types.ts +27 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/validation.ts +204 -0
- package/src/lib/webgl/Color.ts +11 -0
- package/src/lib/webgl/Mesh.ts +41 -0
- package/src/lib/webgl/Program.ts +118 -0
- package/src/lib/webgl/Renderer.ts +51 -0
- package/src/lib/webgl/Triangle.ts +27 -0
- package/src/lib/webgl/Vec3.ts +18 -0
- package/src/lib/webgl/index.ts +13 -0
- package/src/nativewind-env.d.ts +1 -0
- package/src/providers/ThemeProvider.tsx +461 -0
- package/src/providers/index.ts +1 -0
- package/src/providers.ts +7 -0
- package/src/tables.ts +1 -0
- package/src/test/setup.ts +39 -0
- package/src/theme.css +158 -0
- package/src/tokens.ts +7 -0
- package/src/utils.ts +12 -0
- package/src/webgl.ts +1 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
interface TargetCursorProps {
|
|
5
|
+
color?: string;
|
|
6
|
+
outerSize?: number;
|
|
7
|
+
innerSize?: number;
|
|
8
|
+
outerAlpha?: number;
|
|
9
|
+
innerScale?: number;
|
|
10
|
+
outerScale?: number;
|
|
11
|
+
clickScale?: number;
|
|
12
|
+
blendMode?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function TargetCursor({
|
|
16
|
+
color = '255, 255, 255',
|
|
17
|
+
outerSize = 40,
|
|
18
|
+
innerSize = 8,
|
|
19
|
+
outerAlpha = 0.4,
|
|
20
|
+
innerScale = 0.7,
|
|
21
|
+
outerScale = 4,
|
|
22
|
+
clickScale = 0.7,
|
|
23
|
+
blendMode = true
|
|
24
|
+
}: TargetCursorProps) {
|
|
25
|
+
const cursorOuterRef = useRef<HTMLDivElement>(null);
|
|
26
|
+
const cursorInnerRef = useRef<HTMLDivElement>(null);
|
|
27
|
+
const requestRef = useRef<number | null>(null);
|
|
28
|
+
const previousTimeRef = useRef<number | null>(null);
|
|
29
|
+
|
|
30
|
+
// Mouse position
|
|
31
|
+
const endX = useRef(0);
|
|
32
|
+
const endY = useRef(0);
|
|
33
|
+
// Current interpolated position for outer circle
|
|
34
|
+
const _x = useRef(0);
|
|
35
|
+
const _y = useRef(0);
|
|
36
|
+
|
|
37
|
+
const [isActive, setIsActive] = useState(false);
|
|
38
|
+
const [isActiveClickable, setIsActiveClickable] = useState(false);
|
|
39
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
40
|
+
|
|
41
|
+
// Initial positioning
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const onMouseMove = (e: MouseEvent) => {
|
|
44
|
+
endX.current = e.clientX;
|
|
45
|
+
endY.current = e.clientY;
|
|
46
|
+
|
|
47
|
+
// Direct DOM update for performance
|
|
48
|
+
if (cursorInnerRef.current) {
|
|
49
|
+
cursorInnerRef.current.style.top = `${e.clientY}px`;
|
|
50
|
+
cursorInnerRef.current.style.left = `${e.clientX}px`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!isVisible) {
|
|
54
|
+
_x.current = e.clientX;
|
|
55
|
+
_y.current = e.clientY;
|
|
56
|
+
setIsVisible(true);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check for clickable targets using DOM traversal (lighter than getComputedStyle)
|
|
60
|
+
// Note: getComputedStyle causes reflow, so avoid if possible.
|
|
61
|
+
// Using tagName and common patterns is preferred for performance.
|
|
62
|
+
const target = e.target as HTMLElement;
|
|
63
|
+
// Simple heuristics
|
|
64
|
+
if (
|
|
65
|
+
target.tagName.toLowerCase() === 'a' ||
|
|
66
|
+
target.tagName.toLowerCase() === 'button' ||
|
|
67
|
+
target.closest('a') ||
|
|
68
|
+
target.closest('button') ||
|
|
69
|
+
target.onclick ||
|
|
70
|
+
target.getAttribute('role') === 'button'
|
|
71
|
+
) {
|
|
72
|
+
setIsActiveClickable(true);
|
|
73
|
+
} else {
|
|
74
|
+
setIsActiveClickable(false);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const onMouseDown = () => setIsActive(true);
|
|
79
|
+
const onMouseUp = () => setIsActive(false);
|
|
80
|
+
const onMouseEnter = () => setIsVisible(true);
|
|
81
|
+
const onMouseLeave = () => setIsVisible(false);
|
|
82
|
+
|
|
83
|
+
window.addEventListener('mousemove', onMouseMove);
|
|
84
|
+
window.addEventListener('mousedown', onMouseDown);
|
|
85
|
+
window.addEventListener('mouseup', onMouseUp);
|
|
86
|
+
window.addEventListener('mouseenter', onMouseEnter);
|
|
87
|
+
window.addEventListener('mouseleave', onMouseLeave);
|
|
88
|
+
|
|
89
|
+
return () => {
|
|
90
|
+
window.removeEventListener('mousemove', onMouseMove);
|
|
91
|
+
window.removeEventListener('mousedown', onMouseDown);
|
|
92
|
+
window.removeEventListener('mouseup', onMouseUp);
|
|
93
|
+
window.removeEventListener('mouseenter', onMouseEnter);
|
|
94
|
+
window.removeEventListener('mouseleave', onMouseLeave);
|
|
95
|
+
};
|
|
96
|
+
}, [isVisible]);
|
|
97
|
+
|
|
98
|
+
// Animation Loop
|
|
99
|
+
const animateCursor = (time: number) => {
|
|
100
|
+
if (previousTimeRef.current !== undefined) {
|
|
101
|
+
_x.current += (endX.current - _x.current) / 8;
|
|
102
|
+
_y.current += (endY.current - _y.current) / 8;
|
|
103
|
+
|
|
104
|
+
if (cursorOuterRef.current) {
|
|
105
|
+
// translate3d for position, translate(-50%, -50%) for centering
|
|
106
|
+
cursorOuterRef.current.style.transform = `translate3d(${_x.current}px, ${_y.current}px, 0) translate(-50%, -50%)`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
previousTimeRef.current = time;
|
|
110
|
+
requestRef.current = requestAnimationFrame(animateCursor);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
requestRef.current = requestAnimationFrame(animateCursor);
|
|
115
|
+
return () => cancelAnimationFrame(requestRef.current!);
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (isVisible) {
|
|
120
|
+
const style = document.createElement('style');
|
|
121
|
+
style.innerHTML = `body, a, button, input, [role="button"] { cursor: none !important; }`;
|
|
122
|
+
document.head.appendChild(style);
|
|
123
|
+
return () => {
|
|
124
|
+
if (document.head.contains(style)) {
|
|
125
|
+
document.head.removeChild(style);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}, [isVisible]);
|
|
130
|
+
|
|
131
|
+
// Inline styles for dynamic props
|
|
132
|
+
const styles = {
|
|
133
|
+
cursorInner: {
|
|
134
|
+
width: innerSize,
|
|
135
|
+
height: innerSize,
|
|
136
|
+
backgroundColor: `rgba(${color}, 1)`,
|
|
137
|
+
// transform handles centering and scale
|
|
138
|
+
transform: `translate(-50%, -50%) scale(${isActive || isActiveClickable ? innerScale : 1})`,
|
|
139
|
+
transition: 'transform 0.15s ease-out, opacity 0.15s ease-out',
|
|
140
|
+
},
|
|
141
|
+
cursorOuter: {
|
|
142
|
+
width: outerSize,
|
|
143
|
+
height: outerSize,
|
|
144
|
+
backgroundColor: `rgba(${color}, ${outerAlpha})`,
|
|
145
|
+
transition: 'width 0.3s, height 0.3s, background-color 0.3s',
|
|
146
|
+
...(isActiveClickable && {
|
|
147
|
+
width: outerSize * 1.5,
|
|
148
|
+
height: outerSize * 1.5,
|
|
149
|
+
backgroundColor: `rgba(${color}, ${outerAlpha - 0.1})`,
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
if (!isVisible) return null;
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<>
|
|
158
|
+
<div
|
|
159
|
+
ref={cursorOuterRef}
|
|
160
|
+
style={{
|
|
161
|
+
position: 'fixed',
|
|
162
|
+
borderRadius: '50%',
|
|
163
|
+
pointerEvents: 'none',
|
|
164
|
+
zIndex: 9999,
|
|
165
|
+
left: 0,
|
|
166
|
+
top: 0,
|
|
167
|
+
willChange: 'transform',
|
|
168
|
+
mixBlendMode: blendMode ? 'difference' : 'normal',
|
|
169
|
+
...styles.cursorOuter
|
|
170
|
+
}}
|
|
171
|
+
/>
|
|
172
|
+
<div
|
|
173
|
+
ref={cursorInnerRef}
|
|
174
|
+
style={{
|
|
175
|
+
position: 'fixed',
|
|
176
|
+
borderRadius: '50%',
|
|
177
|
+
pointerEvents: 'none',
|
|
178
|
+
zIndex: 9999,
|
|
179
|
+
willChange: 'left, top, transform',
|
|
180
|
+
mixBlendMode: blendMode ? 'difference' : 'normal',
|
|
181
|
+
...styles.cursorInner
|
|
182
|
+
}}
|
|
183
|
+
/>
|
|
184
|
+
{/* Default cursor hidden via useEffect */}
|
|
185
|
+
</>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface AspectImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
|
4
|
+
/**
|
|
5
|
+
* Aspect ratio (width / height)
|
|
6
|
+
* @example 16/9, 4/3, 1
|
|
7
|
+
* @default 16/9
|
|
8
|
+
*/
|
|
9
|
+
ratio?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Image source
|
|
12
|
+
*/
|
|
13
|
+
src: string;
|
|
14
|
+
/**
|
|
15
|
+
* Image alt text
|
|
16
|
+
*/
|
|
17
|
+
alt: string;
|
|
18
|
+
/**
|
|
19
|
+
* Apply border radius
|
|
20
|
+
* @default true
|
|
21
|
+
*/
|
|
22
|
+
rounded?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Apply shadow
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
shadow?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Optional caption
|
|
30
|
+
*/
|
|
31
|
+
caption?: React.ReactNode;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const AspectImage = (
|
|
35
|
+
{
|
|
36
|
+
ref,
|
|
37
|
+
ratio = 16 / 9,
|
|
38
|
+
src,
|
|
39
|
+
alt,
|
|
40
|
+
rounded = true,
|
|
41
|
+
shadow = false,
|
|
42
|
+
caption,
|
|
43
|
+
className = '',
|
|
44
|
+
style,
|
|
45
|
+
...props
|
|
46
|
+
}: AspectImageProps & {
|
|
47
|
+
ref?: React.Ref<HTMLImageElement>;
|
|
48
|
+
}
|
|
49
|
+
) => {
|
|
50
|
+
return (
|
|
51
|
+
<figure className={`w-full ${className}`}>
|
|
52
|
+
<div
|
|
53
|
+
className={`relative overflow-hidden w-full bg-[var(--color-surface)] border border-[var(--color-border)]
|
|
54
|
+
${rounded ? 'rounded-xl' : ''}
|
|
55
|
+
${shadow ? 'shadow-md' : ''}`}
|
|
56
|
+
style={{ aspectRatio: ratio }}
|
|
57
|
+
>
|
|
58
|
+
<img
|
|
59
|
+
ref={ref}
|
|
60
|
+
src={src}
|
|
61
|
+
alt={alt}
|
|
62
|
+
className="w-full h-full object-cover transition-transform duration-500 hover:scale-105"
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
{caption && (
|
|
67
|
+
<figcaption className="mt-2 text-sm text-[var(--color-text-muted)] text-center">
|
|
68
|
+
{caption}
|
|
69
|
+
</figcaption>
|
|
70
|
+
)}
|
|
71
|
+
</figure>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react'
|
|
2
|
+
import { describe, it, expect } from 'vitest'
|
|
3
|
+
import { Avatar, AvatarImage, AvatarFallback } from './Avatar'
|
|
4
|
+
|
|
5
|
+
describe('Avatar', () => {
|
|
6
|
+
it('renders fallback text', () => {
|
|
7
|
+
render(
|
|
8
|
+
<Avatar>
|
|
9
|
+
<AvatarFallback>JD</AvatarFallback>
|
|
10
|
+
</Avatar>
|
|
11
|
+
)
|
|
12
|
+
expect(screen.getByText('JD')).toBeInTheDocument()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('shows fallback when image has not loaded', () => {
|
|
16
|
+
// Radix Avatar only renders <img> after the load event fires,
|
|
17
|
+
// which never happens in jsdom — so fallback is shown instead
|
|
18
|
+
render(
|
|
19
|
+
<Avatar>
|
|
20
|
+
<AvatarImage src="/avatar.jpg" alt="John Doe" />
|
|
21
|
+
<AvatarFallback>JD</AvatarFallback>
|
|
22
|
+
</Avatar>
|
|
23
|
+
)
|
|
24
|
+
expect(screen.getByText('JD')).toBeInTheDocument()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('applies custom className to avatar root', () => {
|
|
28
|
+
const { container } = render(
|
|
29
|
+
<Avatar className="custom-avatar">
|
|
30
|
+
<AvatarFallback>AB</AvatarFallback>
|
|
31
|
+
</Avatar>
|
|
32
|
+
)
|
|
33
|
+
expect(container.firstChild).toHaveClass('custom-avatar')
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
const Avatar = (
|
|
8
|
+
{
|
|
9
|
+
ref,
|
|
10
|
+
className,
|
|
11
|
+
...props
|
|
12
|
+
}: React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> & {
|
|
13
|
+
ref?: React.Ref<React.ElementRef<typeof AvatarPrimitive.Root>>;
|
|
14
|
+
}
|
|
15
|
+
) => (<AvatarPrimitive.Root
|
|
16
|
+
ref={ref}
|
|
17
|
+
className={cn(
|
|
18
|
+
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
|
19
|
+
className
|
|
20
|
+
)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>)
|
|
23
|
+
|
|
24
|
+
const AvatarImage = (
|
|
25
|
+
{
|
|
26
|
+
ref,
|
|
27
|
+
className,
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> & {
|
|
30
|
+
ref?: React.Ref<React.ElementRef<typeof AvatarPrimitive.Image>>;
|
|
31
|
+
}
|
|
32
|
+
) => (<AvatarPrimitive.Image
|
|
33
|
+
ref={ref}
|
|
34
|
+
className={cn("aspect-square h-full w-full", className)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>)
|
|
37
|
+
|
|
38
|
+
const AvatarFallback = (
|
|
39
|
+
{
|
|
40
|
+
ref,
|
|
41
|
+
className,
|
|
42
|
+
...props
|
|
43
|
+
}: React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> & {
|
|
44
|
+
ref?: React.Ref<React.ElementRef<typeof AvatarPrimitive.Fallback>>;
|
|
45
|
+
}
|
|
46
|
+
) => (<AvatarPrimitive.Fallback
|
|
47
|
+
ref={ref}
|
|
48
|
+
className={cn(
|
|
49
|
+
"flex h-full w-full items-center justify-center rounded-full bg-muted text-muted-foreground font-medium",
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>)
|
|
54
|
+
|
|
55
|
+
export { Avatar, AvatarImage, AvatarFallback }
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react'
|
|
2
|
+
import { describe, it, expect } from 'vitest'
|
|
3
|
+
import { Badge } from './Badge'
|
|
4
|
+
|
|
5
|
+
describe('Badge', () => {
|
|
6
|
+
it('renders with default variant', () => {
|
|
7
|
+
render(<Badge>Active</Badge>)
|
|
8
|
+
expect(screen.getByText('Active')).toBeInTheDocument()
|
|
9
|
+
expect(screen.getByText('Active')).toHaveClass('bg-primary')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('renders different variants', () => {
|
|
13
|
+
const { rerender } = render(<Badge variant="secondary">Secondary</Badge>)
|
|
14
|
+
expect(screen.getByText('Secondary')).toHaveClass('bg-secondary')
|
|
15
|
+
|
|
16
|
+
rerender(<Badge variant="destructive">Destructive</Badge>)
|
|
17
|
+
expect(screen.getByText('Destructive')).toHaveClass('bg-destructive')
|
|
18
|
+
|
|
19
|
+
rerender(<Badge variant="outline">Outline</Badge>)
|
|
20
|
+
expect(screen.getByText('Outline')).toHaveClass('text-foreground')
|
|
21
|
+
|
|
22
|
+
rerender(<Badge variant="success">Success</Badge>)
|
|
23
|
+
expect(screen.getByText('Success')).toHaveClass('bg-success')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('renders dot indicator when dot prop is true', () => {
|
|
27
|
+
render(<Badge dot>Status</Badge>)
|
|
28
|
+
const dot = screen.getByText('Status').querySelector('[aria-hidden="true"]')
|
|
29
|
+
expect(dot).toBeInTheDocument()
|
|
30
|
+
expect(dot).toHaveClass('animate-pulse')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('does not render dot when dot prop is false', () => {
|
|
34
|
+
render(<Badge>No Dot</Badge>)
|
|
35
|
+
const dot = screen.getByText('No Dot').querySelector('[aria-hidden="true"]')
|
|
36
|
+
expect(dot).not.toBeInTheDocument()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('applies custom className', () => {
|
|
40
|
+
render(<Badge className="custom-badge">Styled</Badge>)
|
|
41
|
+
expect(screen.getByText('Styled')).toHaveClass('custom-badge')
|
|
42
|
+
})
|
|
43
|
+
})
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
const badgeVariants = cva(
|
|
6
|
+
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default:
|
|
11
|
+
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
|
12
|
+
secondary:
|
|
13
|
+
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
14
|
+
destructive:
|
|
15
|
+
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
|
16
|
+
outline: "text-foreground",
|
|
17
|
+
// Semantic variants from original design system
|
|
18
|
+
success: "border-transparent bg-success text-success-foreground hover:bg-success/80",
|
|
19
|
+
warning: "border-transparent bg-warning text-warning-foreground hover:bg-warning/80",
|
|
20
|
+
error: "border-transparent bg-error text-error-foreground hover:bg-error/80",
|
|
21
|
+
info: "border-transparent bg-info text-info-foreground hover:bg-info/80",
|
|
22
|
+
},
|
|
23
|
+
size: {
|
|
24
|
+
sm: "px-2 py-0.5 text-xs",
|
|
25
|
+
md: "px-2.5 py-1 text-sm",
|
|
26
|
+
lg: "px-3 py-1.5 text-base",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: {
|
|
30
|
+
variant: "default",
|
|
31
|
+
size: "md",
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
export interface BadgeProps
|
|
37
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
38
|
+
VariantProps<typeof badgeVariants> {
|
|
39
|
+
dot?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function Badge({ className, variant, size, dot, children, style, ...props }: BadgeProps & { style?: React.CSSProperties }) {
|
|
43
|
+
const sizeStyles: Record<string, React.CSSProperties> = {
|
|
44
|
+
sm: { padding: '0.125rem 0.5rem', fontSize: 'var(--text-xs, 0.75rem)' },
|
|
45
|
+
md: { padding: '0.25rem 0.625rem', fontSize: 'var(--text-sm, 0.875rem)' },
|
|
46
|
+
lg: { padding: '0.375rem 0.75rem', fontSize: '1rem' },
|
|
47
|
+
};
|
|
48
|
+
const variantStyles: Record<string, React.CSSProperties> = {
|
|
49
|
+
default: { backgroundColor: 'var(--color-primary, #346BEA)', color: 'var(--color-primary-foreground, #ffffff)', borderColor: 'transparent' },
|
|
50
|
+
secondary: { backgroundColor: 'var(--color-secondary, #EBF0FD)', color: 'var(--color-secondary-foreground, #1E49AA)', borderColor: 'transparent' },
|
|
51
|
+
destructive: { backgroundColor: 'var(--color-destructive, #C62828)', color: 'var(--color-destructive-foreground, #ffffff)', borderColor: 'transparent' },
|
|
52
|
+
outline: { color: 'var(--color-foreground, #212121)' },
|
|
53
|
+
success: { backgroundColor: 'var(--color-success, #2E7D32)', color: 'var(--color-success-foreground, #ffffff)', borderColor: 'transparent' },
|
|
54
|
+
warning: { backgroundColor: 'var(--color-warning, #E65100)', color: 'var(--color-warning-foreground, #ffffff)', borderColor: 'transparent' },
|
|
55
|
+
error: { backgroundColor: 'var(--color-error, #C62828)', color: 'var(--color-error-foreground, #ffffff)', borderColor: 'transparent' },
|
|
56
|
+
info: { backgroundColor: 'var(--color-info, #346BEA)', color: 'var(--color-info-foreground, #ffffff)', borderColor: 'transparent' },
|
|
57
|
+
};
|
|
58
|
+
return (
|
|
59
|
+
<div
|
|
60
|
+
className={cn(badgeVariants({ variant, size }), className)}
|
|
61
|
+
style={{
|
|
62
|
+
display: 'inline-flex',
|
|
63
|
+
alignItems: 'center',
|
|
64
|
+
borderRadius: '9999px',
|
|
65
|
+
fontWeight: 600,
|
|
66
|
+
...sizeStyles[size || 'md'],
|
|
67
|
+
...variantStyles[variant || 'default'],
|
|
68
|
+
...style,
|
|
69
|
+
}}
|
|
70
|
+
{...props}
|
|
71
|
+
>
|
|
72
|
+
{dot && (
|
|
73
|
+
<span className={cn(
|
|
74
|
+
"mr-1.5 rounded-full bg-current animate-pulse",
|
|
75
|
+
size === 'sm' ? "w-1.5 h-1.5" :
|
|
76
|
+
size === 'lg' ? "w-2.5 h-2.5" : "w-2 h-2"
|
|
77
|
+
)} aria-hidden="true" />
|
|
78
|
+
)}
|
|
79
|
+
{children}
|
|
80
|
+
</div>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export { Badge, badgeVariants }
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface BrandProps {
|
|
4
|
+
/**
|
|
5
|
+
* Brand name or logo element
|
|
6
|
+
*/
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Size variant
|
|
11
|
+
* @default 'md'
|
|
12
|
+
*/
|
|
13
|
+
size?: 'sm' | 'md' | 'lg';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Optional href for link behavior
|
|
17
|
+
* When provided, renders as an anchor tag
|
|
18
|
+
*/
|
|
19
|
+
href?: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Additional className for customization
|
|
23
|
+
*/
|
|
24
|
+
className?: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Click handler (used when href is provided)
|
|
28
|
+
*/
|
|
29
|
+
onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Brand Component
|
|
34
|
+
*
|
|
35
|
+
* A theme-aware brand/logo component that automatically handles:
|
|
36
|
+
* - Dark mode support via design system tokens
|
|
37
|
+
* - Consistent typography and sizing
|
|
38
|
+
* - Optional link behavior
|
|
39
|
+
* - Focus states and accessibility
|
|
40
|
+
*
|
|
41
|
+
* This component encapsulates the proper styling so you never need to
|
|
42
|
+
* manually apply `text-[var(--color-text-primary)]` to logos.
|
|
43
|
+
*
|
|
44
|
+
* Features:
|
|
45
|
+
* - Automatic theme-aware text color
|
|
46
|
+
* - Three size variants (sm, md, lg)
|
|
47
|
+
* - Works standalone or as a link
|
|
48
|
+
* - Compatible with Next.js Link wrapper
|
|
49
|
+
* - Proper focus states
|
|
50
|
+
*
|
|
51
|
+
* Usage:
|
|
52
|
+
* ```tsx
|
|
53
|
+
* // Standalone brand text
|
|
54
|
+
* <Brand>Company Name</Brand>
|
|
55
|
+
*
|
|
56
|
+
* // As a link (use with Next.js Link)
|
|
57
|
+
* <Brand href="/">Company Name</Brand>
|
|
58
|
+
*
|
|
59
|
+
* // With Next.js Link wrapper
|
|
60
|
+
* <Brand>
|
|
61
|
+
* <NextLink href="/">Company Name</NextLink>
|
|
62
|
+
* </Brand>
|
|
63
|
+
*
|
|
64
|
+
* // Different sizes
|
|
65
|
+
* <Brand size="sm">Brand</Brand>
|
|
66
|
+
* <Brand size="lg">Brand</Brand>
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export const Brand = (
|
|
70
|
+
{
|
|
71
|
+
ref,
|
|
72
|
+
children,
|
|
73
|
+
size = 'md',
|
|
74
|
+
href,
|
|
75
|
+
className = '',
|
|
76
|
+
onClick
|
|
77
|
+
}: BrandProps & {
|
|
78
|
+
ref?: React.Ref<HTMLElement>;
|
|
79
|
+
}
|
|
80
|
+
) => {
|
|
81
|
+
// Size mappings following Swiss Grid typography scale
|
|
82
|
+
const sizeStyles = {
|
|
83
|
+
sm: 'text-base', // 16px
|
|
84
|
+
md: 'text-lg', // 18px
|
|
85
|
+
lg: 'text-2xl', // 24px
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Base styles with design system tokens
|
|
89
|
+
const baseStyles = `
|
|
90
|
+
font-bold
|
|
91
|
+
text-[var(--color-text-primary)]
|
|
92
|
+
transition-colors duration-200
|
|
93
|
+
focus-visible:outline-none
|
|
94
|
+
focus-visible:ring-2
|
|
95
|
+
focus-visible:ring-[var(--color-focus)]
|
|
96
|
+
focus-visible:ring-offset-2
|
|
97
|
+
${sizeStyles[size]}
|
|
98
|
+
`;
|
|
99
|
+
|
|
100
|
+
// If href is provided, render as link
|
|
101
|
+
if (href) {
|
|
102
|
+
return (
|
|
103
|
+
<a
|
|
104
|
+
ref={ref as React.Ref<HTMLAnchorElement>}
|
|
105
|
+
href={href}
|
|
106
|
+
onClick={onClick}
|
|
107
|
+
className={`${baseStyles} hover:opacity-80 ${className}`}
|
|
108
|
+
>
|
|
109
|
+
{children}
|
|
110
|
+
</a>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Otherwise render as span
|
|
115
|
+
return (
|
|
116
|
+
<span
|
|
117
|
+
ref={ref as React.Ref<HTMLSpanElement>}
|
|
118
|
+
className={`${baseStyles} ${className}`}
|
|
119
|
+
>
|
|
120
|
+
{children}
|
|
121
|
+
</span>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { ChevronLeft, ChevronRight } from "lucide-react"
|
|
5
|
+
import { DayPicker } from "react-day-picker"
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils"
|
|
8
|
+
import { buttonVariants } from "../actions/Button"
|
|
9
|
+
|
|
10
|
+
export type CalendarProps = React.ComponentProps<typeof DayPicker>
|
|
11
|
+
|
|
12
|
+
function Calendar({
|
|
13
|
+
className,
|
|
14
|
+
classNames,
|
|
15
|
+
showOutsideDays = true,
|
|
16
|
+
...props
|
|
17
|
+
}: CalendarProps) {
|
|
18
|
+
return (
|
|
19
|
+
<DayPicker
|
|
20
|
+
showOutsideDays={showOutsideDays}
|
|
21
|
+
className={cn("p-3", className)}
|
|
22
|
+
classNames={{
|
|
23
|
+
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
|
24
|
+
month: "space-y-4",
|
|
25
|
+
caption: "flex justify-center pt-1 relative items-center",
|
|
26
|
+
caption_label: "text-sm font-medium",
|
|
27
|
+
nav: "space-x-1 flex items-center",
|
|
28
|
+
nav_button: cn(
|
|
29
|
+
buttonVariants({ variant: "outline" }),
|
|
30
|
+
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
|
31
|
+
),
|
|
32
|
+
nav_button_previous: "absolute left-1",
|
|
33
|
+
nav_button_next: "absolute right-1",
|
|
34
|
+
table: "w-full border-collapse space-y-1",
|
|
35
|
+
head_row: "flex",
|
|
36
|
+
head_cell:
|
|
37
|
+
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
|
|
38
|
+
row: "flex w-full mt-2",
|
|
39
|
+
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
|
|
40
|
+
day: cn(
|
|
41
|
+
buttonVariants({ variant: "ghost" }),
|
|
42
|
+
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
|
|
43
|
+
),
|
|
44
|
+
day_range_end: "day-range-end",
|
|
45
|
+
day_selected:
|
|
46
|
+
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
|
47
|
+
day_today: "bg-accent text-accent-foreground",
|
|
48
|
+
day_outside:
|
|
49
|
+
"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
|
|
50
|
+
day_disabled: "text-muted-foreground opacity-50",
|
|
51
|
+
day_range_middle:
|
|
52
|
+
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
|
53
|
+
day_hidden: "invisible",
|
|
54
|
+
...classNames,
|
|
55
|
+
}}
|
|
56
|
+
components={{
|
|
57
|
+
Chevron: ({ ...props }) => {
|
|
58
|
+
if (props.orientation === "left") {
|
|
59
|
+
return <ChevronLeft className="h-4 w-4" />
|
|
60
|
+
}
|
|
61
|
+
return <ChevronRight className="h-4 w-4" />
|
|
62
|
+
},
|
|
63
|
+
}}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
Calendar.displayName = "Calendar"
|
|
69
|
+
|
|
70
|
+
export { Calendar }
|