@snowcone-app/ui 0.1.42 → 0.2.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/CHANGELOG.md +33 -0
- package/README.md +18 -4
- package/package.json +9 -5
- package/src/components/CanvasIsolationBoundary.tsx +202 -0
- package/src/components/LoadingOverlayPrism.tsx +251 -0
- package/src/composed/AddToCart.tsx +229 -0
- package/src/composed/ArtAlignment.tsx +703 -0
- package/src/composed/ArtSelector.tsx +290 -0
- package/src/composed/ArtworkCustomizer.tsx +212 -0
- package/src/composed/CanvasEditor.tsx +79 -0
- package/src/composed/ColorPicker.tsx +111 -0
- package/src/composed/CurrentSelectionDisplay.tsx +86 -0
- package/src/composed/HeroProductImage.tsx +1071 -0
- package/src/composed/Lightbox.index.ts +2 -0
- package/src/composed/Lightbox.tsx +230 -0
- package/src/composed/PlacementClipShapeSelector.tsx +88 -0
- package/src/composed/PlacementTabs.tsx +179 -0
- package/src/composed/ProductCard.tsx +298 -0
- package/src/composed/ProductGallery.tsx +54 -0
- package/src/composed/ProductImage.tsx +129 -0
- package/src/composed/ProductList.tsx +147 -0
- package/src/composed/ProductOptions.tsx +305 -0
- package/src/composed/RealtimeMockup.tsx +121 -0
- package/src/composed/TileCount.tsx +348 -0
- package/src/composed/carousels/HeroCarousel.tsx +240 -0
- package/src/composed/carousels/MobileProductCarousel.tsx +1002 -0
- package/src/composed/carousels/index.ts +11 -0
- package/src/composed/carousels/types.ts +58 -0
- package/src/composed/grids/MasonryGrid.tsx +238 -0
- package/src/composed/grids/index.ts +9 -0
- package/src/composed/search/CurrentRefinements.tsx +80 -0
- package/src/composed/search/Filters.tsx +49 -0
- package/src/composed/search/FiltersButton.tsx +57 -0
- package/src/composed/search/FiltersDrawer.tsx +375 -0
- package/src/composed/search/ProductGrid.tsx +118 -0
- package/src/composed/search/ProductHit.tsx +56 -0
- package/src/composed/search/SearchBox.tsx +109 -0
- package/src/composed/search/SearchProvider.tsx +136 -0
- package/src/composed/search/facetConfig.ts +16 -0
- package/src/composed/search/index.ts +22 -0
- package/src/composed/search/meilisearchAdapter.ts +20 -0
- package/src/composed/search/types.ts +22 -0
- package/src/composed/zoom/EnhancedImageViewer.tsx +505 -0
- package/src/composed/zoom/ResponsiveZoom.tsx +134 -0
- package/src/composed/zoom/ZoomOverlay.tsx +194 -0
- package/src/composed/zoom/index.ts +12 -0
- package/src/composed/zoom/types.ts +12 -0
- package/src/design-system/ColorPalette.tsx +126 -0
- package/src/design-system/ColorSwatch.tsx +49 -0
- package/src/design-system/DesignSystemPage.tsx +130 -0
- package/src/design-system/ThemeSwitcher.tsx +181 -0
- package/src/design-system/TypographyScale.tsx +106 -0
- package/src/design-system/index.ts +5 -0
- package/src/ecommerce/stories/HeroProductImage.stories.tsx +66 -0
- package/src/ecommerce/stories/PDPHeroGallery.stories.tsx +105 -0
- package/src/ecommerce/stories/PDPInfoPanel.stories.tsx +472 -0
- package/src/ecommerce/stories/PDPLayout.stories.tsx +365 -0
- package/src/hooks/useBrand.ts +41 -0
- package/src/hooks/useCanvasContext.ts +127 -0
- package/src/hooks/useDeviceDetection.ts +64 -0
- package/src/hooks/useFocusTrap.ts +70 -0
- package/src/hooks/useImagePreloader.ts +268 -0
- package/src/hooks/useImageTransition.ts +608 -0
- package/src/hooks/usePlacementsProcessor.ts +74 -0
- package/src/hooks/useProductGallery.ts +193 -0
- package/src/hooks/useProductPage.ts +467 -0
- package/src/hooks/useRenderGuard.ts +96 -0
- package/src/hooks/useScrollDirection.ts +196 -0
- package/src/hooks/viewport/index.ts +25 -0
- package/src/hooks/viewport/useContainerWidth.ts +59 -0
- package/src/hooks/viewport/useMediaQuery.ts +52 -0
- package/src/hooks/viewport/useResponsiveImageCap.ts +149 -0
- package/src/hooks/viewport/useViewportDimensions.ts +135 -0
- package/src/hooks/viewport/useWideMonitorMode.ts +150 -0
- package/src/hooks/visibility/index.ts +15 -0
- package/src/hooks/visibility/observerPool.ts +150 -0
- package/src/index.ts +240 -0
- package/src/layouts/hero-zoom/HeroShrinkLayout.tsx +209 -0
- package/src/layouts/hero-zoom/HeroZoomLayout.tsx +351 -0
- package/src/layouts/hero-zoom/index.ts +30 -0
- package/src/layouts/hero-zoom/stories/HeroZoomLayout.stories.tsx +350 -0
- package/src/layouts/hero-zoom/types.ts +113 -0
- package/src/layouts/hero-zoom/useHeroZoomScales.ts +156 -0
- package/src/layouts/index.ts +9 -0
- package/src/layouts/pdp/EdgeBlurBox.tsx +210 -0
- package/src/layouts/pdp/ImageBlurExtension.tsx +215 -0
- package/src/layouts/pdp/ImageEdgeBlur.tsx +215 -0
- package/src/layouts/pdp/PDPLayout.tsx +246 -0
- package/src/layouts/pdp/SimpleImageBlur.tsx +140 -0
- package/src/layouts/pdp/index.ts +40 -0
- package/src/lib/env.ts +15 -0
- package/src/lib/locale.ts +167 -0
- package/src/lib/router.tsx +46 -0
- package/src/lib/utils.ts +6 -0
- package/src/lightbox/README.md +77 -0
- package/src/next/index.tsx +26 -0
- package/src/patterns/MockupPriorityProvider.tsx +1014 -0
- package/src/patterns/Product.tsx +850 -0
- package/src/patterns/ProductPageProvider.tsx +224 -0
- package/src/patterns/RealtimeProvider.tsx +1162 -0
- package/src/patterns/ShopProvider.tsx +603 -0
- package/src/personalization/PersonalizationBridge.tsx +235 -0
- package/src/personalization/PersonalizationContext.ts +29 -0
- package/src/personalization/PersonalizationInputs.tsx +110 -0
- package/src/personalization/PersonalizationProvider.tsx +407 -0
- package/src/personalization/canvas-stub.d.ts +22 -0
- package/src/personalization/index.ts +43 -0
- package/src/personalization/types.ts +48 -0
- package/src/personalization/usePersonalization.ts +32 -0
- package/src/personalization/usePersonalizationShimmer.ts +159 -0
- package/src/personalization/utils.ts +59 -0
- package/src/primitives/BrandLogo.tsx +65 -0
- package/src/primitives/BrandName.tsx +51 -0
- package/src/primitives/Button.tsx +123 -0
- package/src/primitives/ColorSwatch.tsx +221 -0
- package/src/primitives/DragHintAnimation.tsx +190 -0
- package/src/primitives/EdgeSwipeGuards.tsx +60 -0
- package/src/primitives/FloatingActionGroup.tsx +176 -0
- package/src/primitives/ProductPrice.tsx +171 -0
- package/src/primitives/ProgressiveBlur.tsx +295 -0
- package/src/primitives/ThemeToggle.tsx +125 -0
- package/src/primitives/__tests__/story-coverage.test.ts +98 -0
- package/src/primitives/accordion.tsx +280 -0
- package/src/primitives/badge.tsx +137 -0
- package/src/primitives/card.tsx +61 -0
- package/src/primitives/checkbox.tsx +56 -0
- package/src/primitives/collapsible.tsx +51 -0
- package/src/primitives/drawer.tsx +828 -0
- package/src/primitives/dropdown-menu.tsx +197 -0
- package/src/primitives/fieldset.tsx +73 -0
- package/src/primitives/index.ts +138 -0
- package/src/primitives/input.tsx +91 -0
- package/src/primitives/kbd.tsx +130 -0
- package/src/primitives/label.tsx +20 -0
- package/src/primitives/link.tsx +182 -0
- package/src/primitives/popover.tsx +80 -0
- package/src/primitives/radio-group.tsx +79 -0
- package/src/primitives/scroll-fade.tsx +159 -0
- package/src/primitives/select.tsx +170 -0
- package/src/primitives/separator.tsx +25 -0
- package/src/primitives/slider.tsx +221 -0
- package/src/primitives/spinner.tsx +72 -0
- package/src/primitives/stories/Accordion.stories.tsx +121 -0
- package/src/primitives/stories/Badge.stories.tsx +221 -0
- package/src/primitives/stories/Button.stories.tsx +185 -0
- package/src/primitives/stories/Card.stories.tsx +171 -0
- package/src/primitives/stories/Checkbox.stories.tsx +214 -0
- package/src/primitives/stories/Collapsible.stories.tsx +230 -0
- package/src/primitives/stories/Drawer.stories.tsx +378 -0
- package/src/primitives/stories/DropdownMenu.stories.tsx +182 -0
- package/src/primitives/stories/Fieldset.stories.tsx +212 -0
- package/src/primitives/stories/Input.stories.tsx +172 -0
- package/src/primitives/stories/Kbd.stories.tsx +183 -0
- package/src/primitives/stories/Label.stories.tsx +98 -0
- package/src/primitives/stories/Link.stories.tsx +260 -0
- package/src/primitives/stories/Popover.stories.tsx +178 -0
- package/src/primitives/stories/RadioGroup.stories.tsx +205 -0
- package/src/primitives/stories/Select.stories.tsx +222 -0
- package/src/primitives/stories/Separator.stories.tsx +134 -0
- package/src/primitives/stories/Slider.stories.tsx +203 -0
- package/src/primitives/stories/Spinner.stories.tsx +142 -0
- package/src/primitives/stories/Surface.stories.tsx +257 -0
- package/src/primitives/stories/Switch.stories.tsx +131 -0
- package/src/primitives/stories/Tabs.stories.tsx +275 -0
- package/src/primitives/stories/TextField.stories.tsx +139 -0
- package/src/primitives/stories/Textarea.stories.tsx +148 -0
- package/src/primitives/stories/Tooltip.stories.tsx +119 -0
- package/src/primitives/surface.tsx +86 -0
- package/src/primitives/switch.tsx +35 -0
- package/src/primitives/tabs.tsx +206 -0
- package/src/primitives/text-field.tsx +84 -0
- package/src/primitives/textarea.tsx +50 -0
- package/src/primitives/tooltip.tsx +58 -0
- package/src/services/CanvasExportService.ts +518 -0
- package/src/styles/base.css +380 -0
- package/src/styles/defaults.css +280 -0
- package/src/styles/globals.css +1242 -0
- package/src/styles/index.css +17 -0
- package/src/styles/ne-themes.css +4740 -0
- package/src/styles/tailwind.css +11 -0
- package/src/styles/tokens.css +117 -0
- package/src/styles/utilities.css +188 -0
- package/src/themes/apply-theme.ts +449 -0
- package/src/themes/getThemeStyles.ts +454 -0
- package/src/themes/index.ts +48 -0
- package/src/themes/oklch-theme.ts +283 -0
- package/src/themes/presets.ts +989 -0
- package/src/themes/types.ts +386 -0
- package/src/themes/useTheme.tsx +450 -0
- package/src/utils/dev-warnings.ts +161 -0
- package/src/utils/devWarnings.ts +153 -0
- package/dist/styles.css +0 -1
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useState, useCallback, type ComponentType } from 'react';
|
|
4
|
+
import { Plus as LucidePlus, Minus as LucideMinus } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
// Cast to fix React 19 type compatibility with lucide-react
|
|
7
|
+
type IconProps = { className?: string; size?: number; strokeWidth?: number; "aria-hidden"?: boolean | "true" | "false" };
|
|
8
|
+
const PlusIcon = LucidePlus as ComponentType<IconProps>;
|
|
9
|
+
const MinusIcon = LucideMinus as ComponentType<IconProps>;
|
|
10
|
+
|
|
11
|
+
interface ZoomOverlayProps {
|
|
12
|
+
imageIndex: number;
|
|
13
|
+
isLargeTouchDevice: boolean;
|
|
14
|
+
isTouchDevice: boolean;
|
|
15
|
+
onEnhancedViewer?: (index: number) => void;
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
className?: string;
|
|
18
|
+
style?: React.CSSProperties;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function ZoomOverlay({
|
|
22
|
+
imageIndex,
|
|
23
|
+
isLargeTouchDevice,
|
|
24
|
+
isTouchDevice,
|
|
25
|
+
onEnhancedViewer,
|
|
26
|
+
children,
|
|
27
|
+
className,
|
|
28
|
+
style,
|
|
29
|
+
}: ZoomOverlayProps) {
|
|
30
|
+
const [zoomedImageIndex, setZoomedImageIndex] = useState<number | null>(null);
|
|
31
|
+
const [zoomOrigin, setZoomOrigin] = useState({ x: 50, y: 50 });
|
|
32
|
+
const [hoveredImageIndex, setHoveredImageIndex] = useState<number | null>(null);
|
|
33
|
+
const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
|
|
34
|
+
const [announcement, setAnnouncement] = useState('');
|
|
35
|
+
const [isZooming, setIsZooming] = useState(false);
|
|
36
|
+
|
|
37
|
+
const handleZoomClick = useCallback(
|
|
38
|
+
(e: React.MouseEvent<HTMLDivElement>) => {
|
|
39
|
+
e.stopPropagation();
|
|
40
|
+
|
|
41
|
+
// On large touch devices (tablets), use the enhanced viewer
|
|
42
|
+
if (isLargeTouchDevice && onEnhancedViewer) {
|
|
43
|
+
onEnhancedViewer(imageIndex);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Desktop behavior
|
|
48
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
49
|
+
const x = ((e.clientX - rect.left) / rect.width) * 100;
|
|
50
|
+
const y = ((e.clientY - rect.top) / rect.height) * 100;
|
|
51
|
+
|
|
52
|
+
if (zoomedImageIndex === imageIndex) {
|
|
53
|
+
setIsZooming(true);
|
|
54
|
+
setZoomedImageIndex(null);
|
|
55
|
+
setAnnouncement('Zoomed out to 100%');
|
|
56
|
+
setTimeout(() => setIsZooming(false), 200);
|
|
57
|
+
} else {
|
|
58
|
+
setZoomOrigin({ x, y });
|
|
59
|
+
setIsZooming(true);
|
|
60
|
+
setZoomedImageIndex(imageIndex);
|
|
61
|
+
setAnnouncement('Zoomed in to 200%');
|
|
62
|
+
setTimeout(() => setIsZooming(false), 200);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
[zoomedImageIndex, isLargeTouchDevice, onEnhancedViewer, imageIndex]
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const handleZoomMouseMove = useCallback(
|
|
69
|
+
(e: React.MouseEvent<HTMLDivElement>) => {
|
|
70
|
+
// Always update cursor position for custom cursor
|
|
71
|
+
setCursorPos({ x: e.clientX, y: e.clientY });
|
|
72
|
+
|
|
73
|
+
// Only update zoom origin when zoomed
|
|
74
|
+
if (zoomedImageIndex === imageIndex) {
|
|
75
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
76
|
+
const x = ((e.clientX - rect.left) / rect.width) * 100;
|
|
77
|
+
const y = ((e.clientY - rect.top) / rect.height) * 100;
|
|
78
|
+
|
|
79
|
+
// Don't clamp - allow full range for panning
|
|
80
|
+
setZoomOrigin({ x, y });
|
|
81
|
+
|
|
82
|
+
// Disable transition while panning
|
|
83
|
+
setIsZooming(false);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
[zoomedImageIndex, imageIndex]
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const isZoomed = zoomedImageIndex === imageIndex;
|
|
90
|
+
const isHovered = hoveredImageIndex === imageIndex;
|
|
91
|
+
|
|
92
|
+
const cursorStyle = isLargeTouchDevice
|
|
93
|
+
? "pointer"
|
|
94
|
+
: isTouchDevice
|
|
95
|
+
? "default"
|
|
96
|
+
: "none"; // Always hide default cursor on desktop since we show custom cursor
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<>
|
|
100
|
+
{/* Screen reader announcements */}
|
|
101
|
+
<div className="sr-only" role="status" aria-live="polite" aria-atomic="true">
|
|
102
|
+
{announcement}
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div
|
|
106
|
+
className={className}
|
|
107
|
+
style={{
|
|
108
|
+
...style,
|
|
109
|
+
position: 'relative',
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
<div
|
|
113
|
+
style={{
|
|
114
|
+
cursor: cursorStyle,
|
|
115
|
+
overflow: 'hidden',
|
|
116
|
+
position: 'relative',
|
|
117
|
+
borderRadius: '0.5rem', // 8px - Tailwind's rounded-lg
|
|
118
|
+
}}
|
|
119
|
+
onClick={
|
|
120
|
+
!isTouchDevice || isLargeTouchDevice
|
|
121
|
+
? handleZoomClick
|
|
122
|
+
: undefined
|
|
123
|
+
}
|
|
124
|
+
onMouseEnter={
|
|
125
|
+
!isTouchDevice
|
|
126
|
+
? () => setHoveredImageIndex(imageIndex)
|
|
127
|
+
: undefined
|
|
128
|
+
}
|
|
129
|
+
onMouseLeave={
|
|
130
|
+
!isTouchDevice
|
|
131
|
+
? () => setHoveredImageIndex(null)
|
|
132
|
+
: undefined
|
|
133
|
+
}
|
|
134
|
+
onMouseMove={
|
|
135
|
+
!isTouchDevice
|
|
136
|
+
? handleZoomMouseMove
|
|
137
|
+
: undefined
|
|
138
|
+
}
|
|
139
|
+
role="button"
|
|
140
|
+
aria-label={isZoomed ? "Click to zoom out (currently at 200%)" : "Click to zoom in to 200%"}
|
|
141
|
+
tabIndex={!isTouchDevice || isLargeTouchDevice ? 0 : undefined}
|
|
142
|
+
onKeyDown={(e) => {
|
|
143
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
handleZoomClick(e as any);
|
|
146
|
+
}
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
<div
|
|
150
|
+
style={{
|
|
151
|
+
transform: isZoomed ? "scale(2)" : "",
|
|
152
|
+
transformOrigin: `${zoomOrigin.x}% ${zoomOrigin.y}%`,
|
|
153
|
+
transition: isZooming ? "transform 0.2s ease-out" : "none",
|
|
154
|
+
willChange: isZoomed ? 'transform' : 'auto',
|
|
155
|
+
}}
|
|
156
|
+
>
|
|
157
|
+
{children}
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
{/* Custom cursor for desktop image hover - only on non-touch devices */}
|
|
163
|
+
{isHovered && !isTouchDevice && (
|
|
164
|
+
<div
|
|
165
|
+
className="fixed pointer-events-none z-50 hidden md:block"
|
|
166
|
+
style={{
|
|
167
|
+
left: cursorPos.x,
|
|
168
|
+
top: cursorPos.y,
|
|
169
|
+
transform: "translate(-50%, -50%)",
|
|
170
|
+
}}
|
|
171
|
+
aria-hidden="true"
|
|
172
|
+
>
|
|
173
|
+
<div className="bg-white dark:bg-gray-800 rounded-full p-2 shadow-sm dark:shadow-gray-950/50">
|
|
174
|
+
{isZoomed ? (
|
|
175
|
+
<MinusIcon
|
|
176
|
+
size={20}
|
|
177
|
+
className="text-gray-900 dark:text-gray-100"
|
|
178
|
+
strokeWidth={1.5}
|
|
179
|
+
aria-hidden="true"
|
|
180
|
+
/>
|
|
181
|
+
) : (
|
|
182
|
+
<PlusIcon
|
|
183
|
+
size={20}
|
|
184
|
+
className="text-gray-900 dark:text-gray-100"
|
|
185
|
+
strokeWidth={1.5}
|
|
186
|
+
aria-hidden="true"
|
|
187
|
+
/>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
</>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zoom Components - Responsive image zoom with device-adaptive behavior
|
|
3
|
+
*
|
|
4
|
+
* - Desktop: Inline 2x zoom with custom cursor (ZoomOverlay)
|
|
5
|
+
* - Mobile/Tablet: Fullscreen pinch-to-zoom viewer (EnhancedImageViewer)
|
|
6
|
+
* - ResponsiveZoom: Adaptive wrapper that selects the best experience per device
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { ResponsiveZoom } from './ResponsiveZoom';
|
|
10
|
+
export { ZoomOverlay } from './ZoomOverlay';
|
|
11
|
+
export { EnhancedImageViewer } from './EnhancedImageViewer';
|
|
12
|
+
export type { ZoomImage } from './types';
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ColorPalette - Displays all theme color tokens organized by category
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ColorTokenSwatch, ColorRow } from './ColorSwatch';
|
|
6
|
+
|
|
7
|
+
const COLOR_GROUPS = [
|
|
8
|
+
{
|
|
9
|
+
name: 'Base',
|
|
10
|
+
description: 'Primary background and text colors',
|
|
11
|
+
colors: [
|
|
12
|
+
{ name: 'Background', cssVar: '--background', description: 'Page background' },
|
|
13
|
+
{ name: 'Foreground', cssVar: '--foreground', description: 'Primary text color' },
|
|
14
|
+
{ name: 'Muted', cssVar: '--muted', description: 'Muted background' },
|
|
15
|
+
{ name: 'Muted Foreground', cssVar: '--muted-foreground', description: 'Secondary text' },
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'Cards & Surfaces',
|
|
20
|
+
description: 'Elevated surface colors',
|
|
21
|
+
colors: [
|
|
22
|
+
{ name: 'Card', cssVar: '--color-card', description: 'Card background' },
|
|
23
|
+
{ name: 'Card Foreground', cssVar: '--color-card-foreground', description: 'Card text' },
|
|
24
|
+
{ name: 'Popover', cssVar: '--color-popover', description: 'Popover/dropdown background' },
|
|
25
|
+
{ name: 'Popover Foreground', cssVar: '--color-popover-foreground', description: 'Popover text' },
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'Primary & Accent',
|
|
30
|
+
description: 'Brand and accent colors',
|
|
31
|
+
colors: [
|
|
32
|
+
{ name: 'Primary', cssVar: '--primary', description: 'Primary brand color' },
|
|
33
|
+
{ name: 'Primary Foreground', cssVar: '--primary-foreground', description: 'Text on primary' },
|
|
34
|
+
{ name: 'Accent', cssVar: '--accent', description: 'Accent highlights' },
|
|
35
|
+
{ name: 'Accent Foreground', cssVar: '--accent-foreground', description: 'Text on accent' },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Secondary',
|
|
40
|
+
description: 'Secondary action colors',
|
|
41
|
+
colors: [
|
|
42
|
+
{ name: 'Secondary', cssVar: '--secondary', description: 'Secondary actions' },
|
|
43
|
+
{ name: 'Secondary Foreground', cssVar: '--secondary-foreground', description: 'Text on secondary' },
|
|
44
|
+
{ name: 'Default', cssVar: '--default', description: 'Default/neutral' },
|
|
45
|
+
{ name: 'Default Foreground', cssVar: '--default-foreground', description: 'Text on default' },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'Surfaces (Semantic)',
|
|
50
|
+
description: 'OKLCH-generated surface hierarchy',
|
|
51
|
+
colors: [
|
|
52
|
+
{ name: 'Surface', cssVar: '--color-surface', description: 'Cards, panels' },
|
|
53
|
+
{ name: 'Surface Raised', cssVar: '--color-surface-raised', description: 'Popovers, dropdowns' },
|
|
54
|
+
{ name: 'Muted', cssVar: '--color-muted', description: 'Muted backgrounds, tracks' },
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'Borders & Dividers',
|
|
59
|
+
description: 'Separators and outlines',
|
|
60
|
+
colors: [
|
|
61
|
+
{ name: 'Border', cssVar: '--border', description: 'Default border color' },
|
|
62
|
+
{ name: 'Divider', cssVar: '--divider', description: 'Divider lines' },
|
|
63
|
+
{ name: 'Ring', cssVar: '--color-ring', description: 'Focus ring color' },
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'Form Fields (Semantic)',
|
|
68
|
+
description: 'Context-aware field backgrounds',
|
|
69
|
+
colors: [
|
|
70
|
+
{ name: 'Field', cssVar: '--color-field', description: 'Field on page/transparent' },
|
|
71
|
+
{ name: 'Field on Surface', cssVar: '--color-field-on-surface', description: 'Field inside Card/Surface' },
|
|
72
|
+
{ name: 'Field Foreground', cssVar: '--color-foreground', description: 'Input text' },
|
|
73
|
+
{ name: 'Focus Ring', cssVar: '--focus', description: 'Focus indicator' },
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'Icons & Text Accents',
|
|
78
|
+
description: 'Decorative text and icon colors',
|
|
79
|
+
colors: [
|
|
80
|
+
{ name: 'Icon', cssVar: '--color-icon', description: 'Default icon color' },
|
|
81
|
+
{ name: 'Accent Text (Overlay)', cssVar: '--color-accent-text-overlay', description: 'Text on images' },
|
|
82
|
+
{ name: 'Accent Text (Page)', cssVar: '--color-accent-text-page', description: 'Accent labels' },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
interface ColorPaletteProps {
|
|
88
|
+
layout?: 'grid' | 'list';
|
|
89
|
+
className?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function ColorPalette({ layout = 'list', className = '' }: ColorPaletteProps) {
|
|
93
|
+
return (
|
|
94
|
+
<div className={`space-y-8 ${className}`}>
|
|
95
|
+
{COLOR_GROUPS.map((group) => (
|
|
96
|
+
<section key={group.name}>
|
|
97
|
+
<h3 className="text-lg font-semibold text-foreground mb-1">{group.name}</h3>
|
|
98
|
+
<p className="text-sm text-muted-foreground mb-4">{group.description}</p>
|
|
99
|
+
|
|
100
|
+
{layout === 'grid' ? (
|
|
101
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
|
102
|
+
{group.colors.map((color) => (
|
|
103
|
+
<ColorTokenSwatch
|
|
104
|
+
key={color.cssVar}
|
|
105
|
+
name={color.name}
|
|
106
|
+
cssVar={color.cssVar}
|
|
107
|
+
/>
|
|
108
|
+
))}
|
|
109
|
+
</div>
|
|
110
|
+
) : (
|
|
111
|
+
<div className="bg-card rounded-lg border border-border p-4 divide-y divide-border">
|
|
112
|
+
{group.colors.map((color) => (
|
|
113
|
+
<ColorRow
|
|
114
|
+
key={color.cssVar}
|
|
115
|
+
name={color.name}
|
|
116
|
+
cssVar={color.cssVar}
|
|
117
|
+
description={color.description}
|
|
118
|
+
/>
|
|
119
|
+
))}
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
</section>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ColorTokenSwatch - Displays a single color token with its value
|
|
3
|
+
* (For design system documentation, not the interactive product color selector)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
interface ColorTokenSwatchProps {
|
|
7
|
+
name: string;
|
|
8
|
+
cssVar: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ColorTokenSwatch({ name, cssVar, className = '' }: ColorTokenSwatchProps) {
|
|
13
|
+
return (
|
|
14
|
+
<div className={`flex flex-col gap-1 ${className}`}>
|
|
15
|
+
<div
|
|
16
|
+
className="h-16 w-full rounded-lg border border-border shadow-sm"
|
|
17
|
+
style={{ backgroundColor: `var(${cssVar})` }}
|
|
18
|
+
/>
|
|
19
|
+
<div className="text-sm font-medium text-foreground">{name}</div>
|
|
20
|
+
<code className="text-xs text-muted-foreground font-mono">{cssVar}</code>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ColorRowProps {
|
|
26
|
+
name: string;
|
|
27
|
+
cssVar: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function ColorRow({ name, cssVar, description }: ColorRowProps) {
|
|
32
|
+
return (
|
|
33
|
+
<div className="flex items-center gap-4 py-2">
|
|
34
|
+
<div
|
|
35
|
+
className="h-10 w-10 rounded-lg border border-border shadow-sm flex-shrink-0"
|
|
36
|
+
style={{ backgroundColor: `var(${cssVar})` }}
|
|
37
|
+
/>
|
|
38
|
+
<div className="flex-1 min-w-0">
|
|
39
|
+
<div className="text-sm font-medium text-foreground">{name}</div>
|
|
40
|
+
{description && (
|
|
41
|
+
<div className="text-xs text-muted-foreground">{description}</div>
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
<code className="text-xs text-muted-foreground font-mono bg-muted px-2 py-1 rounded">
|
|
45
|
+
{cssVar}
|
|
46
|
+
</code>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DesignSystemPage - A complete design system overview
|
|
5
|
+
*
|
|
6
|
+
* Import this component into any app to display the current theme's
|
|
7
|
+
* color palette, typography, and spacing tokens.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* import { DesignSystemPage } from '@snowcone-app/ui';
|
|
12
|
+
*
|
|
13
|
+
* export default function DesignSystem() {
|
|
14
|
+
* return <DesignSystemPage />;
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { ColorPalette } from './ColorPalette';
|
|
20
|
+
import { TypographyScale, RadiusScale } from './TypographyScale';
|
|
21
|
+
import { ThemeSwitcher } from './ThemeSwitcher';
|
|
22
|
+
|
|
23
|
+
interface DesignSystemPageProps {
|
|
24
|
+
/** Show theme switcher at the top */
|
|
25
|
+
showThemeSwitcher?: boolean;
|
|
26
|
+
/** Show color palette section */
|
|
27
|
+
showColors?: boolean;
|
|
28
|
+
/** Show typography section */
|
|
29
|
+
showTypography?: boolean;
|
|
30
|
+
/** Show border radius section */
|
|
31
|
+
showRadius?: boolean;
|
|
32
|
+
/** Show spacing section */
|
|
33
|
+
showSpacing?: boolean;
|
|
34
|
+
/** Layout for color swatches */
|
|
35
|
+
colorLayout?: 'grid' | 'list';
|
|
36
|
+
/** Additional CSS classes */
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function DesignSystemPage({
|
|
41
|
+
showThemeSwitcher = true,
|
|
42
|
+
showColors = true,
|
|
43
|
+
showTypography = true,
|
|
44
|
+
showRadius = true,
|
|
45
|
+
showSpacing = false,
|
|
46
|
+
colorLayout = 'list',
|
|
47
|
+
className = '',
|
|
48
|
+
}: DesignSystemPageProps) {
|
|
49
|
+
return (
|
|
50
|
+
<div className={`max-w-4xl mx-auto px-4 py-8 ${className}`}>
|
|
51
|
+
<header className="mb-12">
|
|
52
|
+
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-4">
|
|
53
|
+
<div>
|
|
54
|
+
<h1 className="text-4xl font-bold text-foreground mb-2">Design System</h1>
|
|
55
|
+
<p className="text-lg text-muted-foreground">
|
|
56
|
+
Visual reference for theme tokens and styles
|
|
57
|
+
</p>
|
|
58
|
+
</div>
|
|
59
|
+
{showThemeSwitcher && <ThemeSwitcher />}
|
|
60
|
+
</div>
|
|
61
|
+
</header>
|
|
62
|
+
|
|
63
|
+
{showColors && (
|
|
64
|
+
<section className="mb-16">
|
|
65
|
+
<h2 className="text-2xl font-semibold text-foreground mb-6 pb-2 border-b border-border">
|
|
66
|
+
Color Palette
|
|
67
|
+
</h2>
|
|
68
|
+
<ColorPalette layout={colorLayout} />
|
|
69
|
+
</section>
|
|
70
|
+
)}
|
|
71
|
+
|
|
72
|
+
{showTypography && (
|
|
73
|
+
<section className="mb-16">
|
|
74
|
+
<h2 className="text-2xl font-semibold text-foreground mb-6 pb-2 border-b border-border">
|
|
75
|
+
Typography
|
|
76
|
+
</h2>
|
|
77
|
+
<TypographyScale />
|
|
78
|
+
</section>
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
{showRadius && (
|
|
82
|
+
<section className="mb-16">
|
|
83
|
+
<h2 className="text-2xl font-semibold text-foreground mb-6 pb-2 border-b border-border">
|
|
84
|
+
Border Radius
|
|
85
|
+
</h2>
|
|
86
|
+
<RadiusScale />
|
|
87
|
+
</section>
|
|
88
|
+
)}
|
|
89
|
+
|
|
90
|
+
{showSpacing && (
|
|
91
|
+
<section className="mb-16">
|
|
92
|
+
<h2 className="text-2xl font-semibold text-foreground mb-6 pb-2 border-b border-border">
|
|
93
|
+
Spacing Scale
|
|
94
|
+
</h2>
|
|
95
|
+
<SpacingScale />
|
|
96
|
+
</section>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function SpacingScale() {
|
|
103
|
+
const spaces = [
|
|
104
|
+
{ name: '1', value: '0.25rem' },
|
|
105
|
+
{ name: '2', value: '0.5rem' },
|
|
106
|
+
{ name: '3', value: '0.75rem' },
|
|
107
|
+
{ name: '4', value: '1rem' },
|
|
108
|
+
{ name: '6', value: '1.5rem' },
|
|
109
|
+
{ name: '8', value: '2rem' },
|
|
110
|
+
{ name: '12', value: '3rem' },
|
|
111
|
+
{ name: '16', value: '4rem' },
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div className="space-y-2">
|
|
116
|
+
{spaces.map((space) => (
|
|
117
|
+
<div key={space.name} className="flex items-center gap-4">
|
|
118
|
+
<code className="text-xs text-muted-foreground font-mono w-8">
|
|
119
|
+
{space.name}
|
|
120
|
+
</code>
|
|
121
|
+
<div
|
|
122
|
+
className="h-4 bg-primary rounded"
|
|
123
|
+
style={{ width: space.value }}
|
|
124
|
+
/>
|
|
125
|
+
<span className="text-sm text-muted-foreground">{space.value}</span>
|
|
126
|
+
</div>
|
|
127
|
+
))}
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|