@snowcone-app/ui 0.1.43 → 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.
Files changed (192) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +18 -4
  3. package/package.json +9 -5
  4. package/src/components/CanvasIsolationBoundary.tsx +202 -0
  5. package/src/components/LoadingOverlayPrism.tsx +251 -0
  6. package/src/composed/AddToCart.tsx +229 -0
  7. package/src/composed/ArtAlignment.tsx +703 -0
  8. package/src/composed/ArtSelector.tsx +290 -0
  9. package/src/composed/ArtworkCustomizer.tsx +212 -0
  10. package/src/composed/CanvasEditor.tsx +79 -0
  11. package/src/composed/ColorPicker.tsx +111 -0
  12. package/src/composed/CurrentSelectionDisplay.tsx +86 -0
  13. package/src/composed/HeroProductImage.tsx +1071 -0
  14. package/src/composed/Lightbox.index.ts +2 -0
  15. package/src/composed/Lightbox.tsx +230 -0
  16. package/src/composed/PlacementClipShapeSelector.tsx +88 -0
  17. package/src/composed/PlacementTabs.tsx +179 -0
  18. package/src/composed/ProductCard.tsx +298 -0
  19. package/src/composed/ProductGallery.tsx +54 -0
  20. package/src/composed/ProductImage.tsx +129 -0
  21. package/src/composed/ProductList.tsx +147 -0
  22. package/src/composed/ProductOptions.tsx +305 -0
  23. package/src/composed/RealtimeMockup.tsx +121 -0
  24. package/src/composed/TileCount.tsx +348 -0
  25. package/src/composed/carousels/HeroCarousel.tsx +240 -0
  26. package/src/composed/carousels/MobileProductCarousel.tsx +1002 -0
  27. package/src/composed/carousels/index.ts +11 -0
  28. package/src/composed/carousels/types.ts +58 -0
  29. package/src/composed/grids/MasonryGrid.tsx +238 -0
  30. package/src/composed/grids/index.ts +9 -0
  31. package/src/composed/search/CurrentRefinements.tsx +80 -0
  32. package/src/composed/search/Filters.tsx +49 -0
  33. package/src/composed/search/FiltersButton.tsx +57 -0
  34. package/src/composed/search/FiltersDrawer.tsx +375 -0
  35. package/src/composed/search/ProductGrid.tsx +118 -0
  36. package/src/composed/search/ProductHit.tsx +56 -0
  37. package/src/composed/search/SearchBox.tsx +109 -0
  38. package/src/composed/search/SearchProvider.tsx +136 -0
  39. package/src/composed/search/facetConfig.ts +16 -0
  40. package/src/composed/search/index.ts +22 -0
  41. package/src/composed/search/meilisearchAdapter.ts +20 -0
  42. package/src/composed/search/types.ts +22 -0
  43. package/src/composed/zoom/EnhancedImageViewer.tsx +505 -0
  44. package/src/composed/zoom/ResponsiveZoom.tsx +134 -0
  45. package/src/composed/zoom/ZoomOverlay.tsx +194 -0
  46. package/src/composed/zoom/index.ts +12 -0
  47. package/src/composed/zoom/types.ts +12 -0
  48. package/src/design-system/ColorPalette.tsx +126 -0
  49. package/src/design-system/ColorSwatch.tsx +49 -0
  50. package/src/design-system/DesignSystemPage.tsx +130 -0
  51. package/src/design-system/ThemeSwitcher.tsx +181 -0
  52. package/src/design-system/TypographyScale.tsx +106 -0
  53. package/src/design-system/index.ts +5 -0
  54. package/src/ecommerce/stories/HeroProductImage.stories.tsx +66 -0
  55. package/src/ecommerce/stories/PDPHeroGallery.stories.tsx +105 -0
  56. package/src/ecommerce/stories/PDPInfoPanel.stories.tsx +472 -0
  57. package/src/ecommerce/stories/PDPLayout.stories.tsx +365 -0
  58. package/src/hooks/useBrand.ts +41 -0
  59. package/src/hooks/useCanvasContext.ts +127 -0
  60. package/src/hooks/useDeviceDetection.ts +64 -0
  61. package/src/hooks/useFocusTrap.ts +70 -0
  62. package/src/hooks/useImagePreloader.ts +268 -0
  63. package/src/hooks/useImageTransition.ts +608 -0
  64. package/src/hooks/usePlacementsProcessor.ts +74 -0
  65. package/src/hooks/useProductGallery.ts +193 -0
  66. package/src/hooks/useProductPage.ts +467 -0
  67. package/src/hooks/useRenderGuard.ts +96 -0
  68. package/src/hooks/useScrollDirection.ts +196 -0
  69. package/src/hooks/viewport/index.ts +25 -0
  70. package/src/hooks/viewport/useContainerWidth.ts +59 -0
  71. package/src/hooks/viewport/useMediaQuery.ts +52 -0
  72. package/src/hooks/viewport/useResponsiveImageCap.ts +149 -0
  73. package/src/hooks/viewport/useViewportDimensions.ts +135 -0
  74. package/src/hooks/viewport/useWideMonitorMode.ts +150 -0
  75. package/src/hooks/visibility/index.ts +15 -0
  76. package/src/hooks/visibility/observerPool.ts +150 -0
  77. package/src/index.ts +240 -0
  78. package/src/layouts/hero-zoom/HeroShrinkLayout.tsx +209 -0
  79. package/src/layouts/hero-zoom/HeroZoomLayout.tsx +351 -0
  80. package/src/layouts/hero-zoom/index.ts +30 -0
  81. package/src/layouts/hero-zoom/stories/HeroZoomLayout.stories.tsx +350 -0
  82. package/src/layouts/hero-zoom/types.ts +113 -0
  83. package/src/layouts/hero-zoom/useHeroZoomScales.ts +156 -0
  84. package/src/layouts/index.ts +9 -0
  85. package/src/layouts/pdp/EdgeBlurBox.tsx +210 -0
  86. package/src/layouts/pdp/ImageBlurExtension.tsx +215 -0
  87. package/src/layouts/pdp/ImageEdgeBlur.tsx +215 -0
  88. package/src/layouts/pdp/PDPLayout.tsx +246 -0
  89. package/src/layouts/pdp/SimpleImageBlur.tsx +140 -0
  90. package/src/layouts/pdp/index.ts +40 -0
  91. package/src/lib/env.ts +15 -0
  92. package/src/lib/locale.ts +167 -0
  93. package/src/lib/router.tsx +46 -0
  94. package/src/lib/utils.ts +6 -0
  95. package/src/lightbox/README.md +77 -0
  96. package/src/next/index.tsx +26 -0
  97. package/src/patterns/MockupPriorityProvider.tsx +1014 -0
  98. package/src/patterns/Product.tsx +850 -0
  99. package/src/patterns/ProductPageProvider.tsx +224 -0
  100. package/src/patterns/RealtimeProvider.tsx +1162 -0
  101. package/src/patterns/ShopProvider.tsx +603 -0
  102. package/src/personalization/PersonalizationBridge.tsx +235 -0
  103. package/src/personalization/PersonalizationContext.ts +29 -0
  104. package/src/personalization/PersonalizationInputs.tsx +110 -0
  105. package/src/personalization/PersonalizationProvider.tsx +407 -0
  106. package/src/personalization/canvas-stub.d.ts +22 -0
  107. package/src/personalization/index.ts +43 -0
  108. package/src/personalization/types.ts +48 -0
  109. package/src/personalization/usePersonalization.ts +32 -0
  110. package/src/personalization/usePersonalizationShimmer.ts +159 -0
  111. package/src/personalization/utils.ts +59 -0
  112. package/src/primitives/BrandLogo.tsx +65 -0
  113. package/src/primitives/BrandName.tsx +51 -0
  114. package/src/primitives/Button.tsx +123 -0
  115. package/src/primitives/ColorSwatch.tsx +221 -0
  116. package/src/primitives/DragHintAnimation.tsx +190 -0
  117. package/src/primitives/EdgeSwipeGuards.tsx +60 -0
  118. package/src/primitives/FloatingActionGroup.tsx +176 -0
  119. package/src/primitives/ProductPrice.tsx +171 -0
  120. package/src/primitives/ProgressiveBlur.tsx +295 -0
  121. package/src/primitives/ThemeToggle.tsx +125 -0
  122. package/src/primitives/__tests__/story-coverage.test.ts +98 -0
  123. package/src/primitives/accordion.tsx +280 -0
  124. package/src/primitives/badge.tsx +137 -0
  125. package/src/primitives/card.tsx +61 -0
  126. package/src/primitives/checkbox.tsx +56 -0
  127. package/src/primitives/collapsible.tsx +51 -0
  128. package/src/primitives/drawer.tsx +828 -0
  129. package/src/primitives/dropdown-menu.tsx +197 -0
  130. package/src/primitives/fieldset.tsx +73 -0
  131. package/src/primitives/index.ts +138 -0
  132. package/src/primitives/input.tsx +91 -0
  133. package/src/primitives/kbd.tsx +130 -0
  134. package/src/primitives/label.tsx +20 -0
  135. package/src/primitives/link.tsx +182 -0
  136. package/src/primitives/popover.tsx +80 -0
  137. package/src/primitives/radio-group.tsx +79 -0
  138. package/src/primitives/scroll-fade.tsx +159 -0
  139. package/src/primitives/select.tsx +170 -0
  140. package/src/primitives/separator.tsx +25 -0
  141. package/src/primitives/slider.tsx +221 -0
  142. package/src/primitives/spinner.tsx +72 -0
  143. package/src/primitives/stories/Accordion.stories.tsx +121 -0
  144. package/src/primitives/stories/Badge.stories.tsx +221 -0
  145. package/src/primitives/stories/Button.stories.tsx +185 -0
  146. package/src/primitives/stories/Card.stories.tsx +171 -0
  147. package/src/primitives/stories/Checkbox.stories.tsx +214 -0
  148. package/src/primitives/stories/Collapsible.stories.tsx +230 -0
  149. package/src/primitives/stories/Drawer.stories.tsx +378 -0
  150. package/src/primitives/stories/DropdownMenu.stories.tsx +182 -0
  151. package/src/primitives/stories/Fieldset.stories.tsx +212 -0
  152. package/src/primitives/stories/Input.stories.tsx +172 -0
  153. package/src/primitives/stories/Kbd.stories.tsx +183 -0
  154. package/src/primitives/stories/Label.stories.tsx +98 -0
  155. package/src/primitives/stories/Link.stories.tsx +260 -0
  156. package/src/primitives/stories/Popover.stories.tsx +178 -0
  157. package/src/primitives/stories/RadioGroup.stories.tsx +205 -0
  158. package/src/primitives/stories/Select.stories.tsx +222 -0
  159. package/src/primitives/stories/Separator.stories.tsx +134 -0
  160. package/src/primitives/stories/Slider.stories.tsx +203 -0
  161. package/src/primitives/stories/Spinner.stories.tsx +142 -0
  162. package/src/primitives/stories/Surface.stories.tsx +257 -0
  163. package/src/primitives/stories/Switch.stories.tsx +131 -0
  164. package/src/primitives/stories/Tabs.stories.tsx +275 -0
  165. package/src/primitives/stories/TextField.stories.tsx +139 -0
  166. package/src/primitives/stories/Textarea.stories.tsx +148 -0
  167. package/src/primitives/stories/Tooltip.stories.tsx +119 -0
  168. package/src/primitives/surface.tsx +86 -0
  169. package/src/primitives/switch.tsx +35 -0
  170. package/src/primitives/tabs.tsx +206 -0
  171. package/src/primitives/text-field.tsx +84 -0
  172. package/src/primitives/textarea.tsx +50 -0
  173. package/src/primitives/tooltip.tsx +58 -0
  174. package/src/services/CanvasExportService.ts +518 -0
  175. package/src/styles/base.css +380 -0
  176. package/src/styles/defaults.css +280 -0
  177. package/src/styles/globals.css +1242 -0
  178. package/src/styles/index.css +17 -0
  179. package/src/styles/ne-themes.css +4740 -0
  180. package/src/styles/tailwind.css +11 -0
  181. package/src/styles/tokens.css +117 -0
  182. package/src/styles/utilities.css +188 -0
  183. package/src/themes/apply-theme.ts +449 -0
  184. package/src/themes/getThemeStyles.ts +454 -0
  185. package/src/themes/index.ts +48 -0
  186. package/src/themes/oklch-theme.ts +283 -0
  187. package/src/themes/presets.ts +989 -0
  188. package/src/themes/types.ts +386 -0
  189. package/src/themes/useTheme.tsx +450 -0
  190. package/src/utils/dev-warnings.ts +161 -0
  191. package/src/utils/devWarnings.ts +153 -0
  192. 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,12 @@
1
+ /**
2
+ * Image interface for zoom components
3
+ */
4
+ export interface ZoomImage {
5
+ src: string;
6
+ alt?: string;
7
+ naturalWidth?: number;
8
+ naturalHeight?: number;
9
+ isRealMockup?: boolean;
10
+ placement?: string;
11
+ mockupId?: string;
12
+ }
@@ -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
+ }