@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.
Files changed (192) hide show
  1. package/CHANGELOG.md +33 -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,181 @@
1
+ "use client";
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * ThemeSwitcher - Dropdown to switch between preset themes
7
+ *
8
+ * Works standalone without any context - applies themes directly to the DOM.
9
+ * Also watches for data-theme attribute changes in case theme is applied after mount.
10
+ */
11
+
12
+ import { useState, useEffect } from 'react';
13
+ import { baseThemes, findTheme, findThemeById, getThemeVariant, applyTheme, getBaseThemeName } from '../themes';
14
+ import type { ThemeConfig } from '../themes';
15
+
16
+ interface ThemeSwitcherProps {
17
+ className?: string;
18
+ /** Show only base themes (not dark variants separately) */
19
+ showBaseThemesOnly?: boolean;
20
+ }
21
+
22
+ /**
23
+ * Read current theme from DOM
24
+ */
25
+ function readThemeFromDOM(): { themeName: string; isDark: boolean } {
26
+ if (typeof document === 'undefined') {
27
+ return { themeName: 'Linear', isDark: false };
28
+ }
29
+
30
+ const root = document.documentElement;
31
+ const themeId = root.getAttribute('data-theme');
32
+ const dark = root.classList.contains('dark');
33
+
34
+ if (themeId) {
35
+ const theme = findThemeById(themeId);
36
+ if (theme) {
37
+ const baseName = getBaseThemeName(theme);
38
+ return { themeName: baseName, isDark: theme.isDark || dark };
39
+ }
40
+ }
41
+
42
+ return { themeName: 'Linear', isDark: dark };
43
+ }
44
+
45
+ export function ThemeSwitcher({ className = '', showBaseThemesOnly = true }: ThemeSwitcherProps) {
46
+ const [currentTheme, setCurrentTheme] = useState<string>('Linear');
47
+ const [isDark, setIsDark] = useState(false);
48
+ const [isOpen, setIsOpen] = useState(false);
49
+
50
+ // Initialize and watch for data-theme attribute changes
51
+ useEffect(() => {
52
+ // Read initial theme
53
+ const { themeName, isDark: dark } = readThemeFromDOM();
54
+ setCurrentTheme(themeName);
55
+ setIsDark(dark);
56
+
57
+ // Watch for attribute changes (theme may be applied after mount by ThemeProvider)
58
+ const observer = new MutationObserver((mutations) => {
59
+ for (const mutation of mutations) {
60
+ if (mutation.type === 'attributes' &&
61
+ (mutation.attributeName === 'data-theme' || mutation.attributeName === 'class')) {
62
+ const { themeName: newName, isDark: newDark } = readThemeFromDOM();
63
+ setCurrentTheme(newName);
64
+ setIsDark(newDark);
65
+ break;
66
+ }
67
+ }
68
+ });
69
+
70
+ observer.observe(document.documentElement, {
71
+ attributes: true,
72
+ attributeFilter: ['data-theme', 'class'],
73
+ });
74
+
75
+ return () => observer.disconnect();
76
+ }, []);
77
+
78
+ const handleThemeSelect = (themeName: string) => {
79
+ // Find the base theme
80
+ const theme = findTheme(themeName);
81
+ if (!theme) return;
82
+
83
+ // Get the appropriate variant based on current dark mode
84
+ const targetTheme = getThemeVariant(theme, isDark);
85
+
86
+ const config: ThemeConfig = { name: targetTheme.name };
87
+ applyTheme(config);
88
+ setCurrentTheme(themeName);
89
+ setIsOpen(false);
90
+ };
91
+
92
+ const handleDarkModeToggle = () => {
93
+ const newIsDark = !isDark;
94
+ setIsDark(newIsDark);
95
+
96
+ // Find the current base theme
97
+ const theme = findTheme(currentTheme);
98
+ if (!theme) return;
99
+
100
+ // Get the target variant (light or dark)
101
+ const targetTheme = getThemeVariant(theme, newIsDark);
102
+
103
+ const config: ThemeConfig = { name: targetTheme.name };
104
+ applyTheme(config);
105
+ };
106
+
107
+ const themes = showBaseThemesOnly ? baseThemes : baseThemes;
108
+
109
+ return (
110
+ <div className={`flex items-center gap-3 ${className}`}>
111
+ {/* Theme Dropdown */}
112
+ <div className="relative">
113
+ <button
114
+ onClick={() => setIsOpen(!isOpen)}
115
+ className="flex items-center gap-2 px-4 py-2 bg-card border border-border rounded-lg hover:bg-muted transition-colors text-foreground"
116
+ >
117
+ <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
118
+ <path strokeLinecap="round" strokeLinejoin="round" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
119
+ </svg>
120
+ <span className="font-medium">{currentTheme}</span>
121
+ <svg className={`w-4 h-4 transition-transform ${isOpen ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
122
+ <path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
123
+ </svg>
124
+ </button>
125
+
126
+ {isOpen && (
127
+ <>
128
+ {/* Backdrop */}
129
+ <div
130
+ className="fixed inset-0 z-40"
131
+ onClick={() => setIsOpen(false)}
132
+ />
133
+
134
+ {/* Dropdown */}
135
+ <div className="absolute top-full left-0 mt-2 w-56 max-h-80 overflow-auto bg-card border border-border rounded-lg shadow-lg z-50">
136
+ {themes.map((theme) => (
137
+ <button
138
+ key={theme.name}
139
+ onClick={() => handleThemeSelect(theme.name)}
140
+ className={`w-full px-4 py-2 text-left hover:bg-muted transition-colors flex items-center gap-3 ${
141
+ currentTheme === theme.name ? 'bg-muted' : ''
142
+ }`}
143
+ >
144
+ <div
145
+ className="w-4 h-4 rounded-full border border-border"
146
+ style={{ backgroundColor: theme.primaryColor || '#888' }}
147
+ />
148
+ <span className="text-foreground">{theme.name}</span>
149
+ {currentTheme === theme.name && (
150
+ <svg className="w-4 h-4 ml-auto text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
151
+ <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
152
+ </svg>
153
+ )}
154
+ </button>
155
+ ))}
156
+ </div>
157
+ </>
158
+ )}
159
+ </div>
160
+
161
+ {/* Dark Mode Toggle */}
162
+ <button
163
+ onClick={handleDarkModeToggle}
164
+ className="flex items-center gap-2 px-4 py-2 bg-card border border-border rounded-lg hover:bg-muted transition-colors text-foreground"
165
+ title={`Switch to ${isDark ? 'light' : 'dark'} mode`}
166
+ >
167
+ {isDark ? (
168
+ <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
169
+ <circle cx="12" cy="12" r="5" />
170
+ <path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" />
171
+ </svg>
172
+ ) : (
173
+ <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
174
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
175
+ </svg>
176
+ )}
177
+ <span className="text-sm">{isDark ? 'Light' : 'Dark'}</span>
178
+ </button>
179
+ </div>
180
+ );
181
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * TypographyScale - Displays font families and type scale
3
+ */
4
+
5
+ const FONT_SAMPLES = [
6
+ {
7
+ name: 'Heading',
8
+ cssVar: '--font-heading',
9
+ sample: 'The quick brown fox jumps over the lazy dog',
10
+ sizes: ['text-4xl', 'text-3xl', 'text-2xl', 'text-xl'],
11
+ },
12
+ {
13
+ name: 'Body',
14
+ cssVar: '--font-body',
15
+ sample: 'The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.',
16
+ sizes: ['text-lg', 'text-base', 'text-sm'],
17
+ },
18
+ {
19
+ name: 'Label',
20
+ cssVar: '--font-label',
21
+ sample: 'CATEGORY · PRODUCT NAME · $99.00',
22
+ sizes: ['text-sm', 'text-xs'],
23
+ },
24
+ {
25
+ name: 'Button',
26
+ cssVar: '--font-button',
27
+ sample: 'Add to Cart · Buy Now · Learn More',
28
+ sizes: ['text-base', 'text-sm'],
29
+ },
30
+ {
31
+ name: 'Caption',
32
+ cssVar: '--font-caption',
33
+ sample: 'Image caption · Timestamp · Fine print',
34
+ sizes: ['text-sm', 'text-xs'],
35
+ },
36
+ ];
37
+
38
+ interface TypographyScaleProps {
39
+ className?: string;
40
+ }
41
+
42
+ export function TypographyScale({ className = '' }: TypographyScaleProps) {
43
+ return (
44
+ <div className={`space-y-8 ${className}`}>
45
+ {FONT_SAMPLES.map((font) => (
46
+ <section key={font.name} className="bg-card rounded-lg border border-border p-6">
47
+ <div className="flex items-center justify-between mb-4">
48
+ <h3 className="text-lg font-semibold text-foreground">{font.name}</h3>
49
+ <code className="text-xs text-muted-foreground font-mono bg-muted px-2 py-1 rounded">
50
+ {font.cssVar}
51
+ </code>
52
+ </div>
53
+
54
+ <div className="space-y-4">
55
+ {font.sizes.map((size) => (
56
+ <div key={size} className="flex items-baseline gap-4">
57
+ <span className="text-xs text-muted-foreground w-20 flex-shrink-0">
58
+ {size}
59
+ </span>
60
+ <p
61
+ className={`${size} text-foreground`}
62
+ style={{ fontFamily: `var(${font.cssVar})` }}
63
+ >
64
+ {font.sample}
65
+ </p>
66
+ </div>
67
+ ))}
68
+ </div>
69
+ </section>
70
+ ))}
71
+ </div>
72
+ );
73
+ }
74
+
75
+ const RADIUS_SAMPLES = [
76
+ { name: 'None', value: '0px' },
77
+ { name: 'Small', cssVar: '--radius-small' },
78
+ { name: 'Medium', cssVar: '--radius-medium' },
79
+ { name: 'Large', cssVar: '--radius-large' },
80
+ { name: 'Full', value: '9999px' },
81
+ ];
82
+
83
+ export function RadiusScale({ className = '' }: { className?: string }) {
84
+ return (
85
+ <div className={`space-y-4 ${className}`}>
86
+ <div className="grid grid-cols-5 gap-4">
87
+ {RADIUS_SAMPLES.map((radius) => (
88
+ <div key={radius.name} className="flex flex-col items-center gap-2">
89
+ <div
90
+ className="h-16 w-16 bg-primary"
91
+ style={{
92
+ borderRadius: radius.cssVar
93
+ ? `var(${radius.cssVar})`
94
+ : radius.value,
95
+ }}
96
+ />
97
+ <span className="text-sm font-medium text-foreground">{radius.name}</span>
98
+ <code className="text-xs text-muted-foreground font-mono">
99
+ {radius.cssVar || radius.value}
100
+ </code>
101
+ </div>
102
+ ))}
103
+ </div>
104
+ </div>
105
+ );
106
+ }
@@ -0,0 +1,5 @@
1
+ export { DesignSystemPage } from './DesignSystemPage';
2
+ export { ColorPalette } from './ColorPalette';
3
+ export { ColorTokenSwatch, ColorRow } from './ColorSwatch';
4
+ export { TypographyScale, RadiusScale } from './TypographyScale';
5
+ export { ThemeSwitcher } from './ThemeSwitcher';
@@ -0,0 +1,66 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import React from 'react';
3
+ import { HeroProductImage } from '../../composed/HeroProductImage';
4
+
5
+ const meta: Meta<typeof HeroProductImage> = {
6
+ title: 'Layouts/Hero Product Image',
7
+ component: HeroProductImage,
8
+ parameters: {
9
+ layout: 'centered',
10
+ },
11
+ tags: ['autodocs'],
12
+ };
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof HeroProductImage>;
16
+
17
+ // Mock product data
18
+ const mockProduct = {
19
+ id: "test-product",
20
+ placements: [
21
+ { label: "Front", type: "image", width: 1000, height: 1000 }
22
+ ]
23
+ };
24
+
25
+ export const Default: Story = {
26
+ args: {
27
+ width: 600,
28
+ className: "w-[600px] h-[600px] bg-gray-50",
29
+ artwork: {
30
+ type: "regular",
31
+ src: "https://placehold.co/600x600/png?text=Artwork"
32
+ },
33
+ product: mockProduct,
34
+ placement: "Front",
35
+ mockupId: "mockup-1",
36
+ variantId: "default",
37
+ },
38
+ render: (args) => (
39
+ <div className="w-[600px] h-[600px] border border-gray-200 rounded-lg overflow-hidden">
40
+ <HeroProductImage {...args} />
41
+ </div>
42
+ )
43
+ };
44
+
45
+ export const Loading: Story = {
46
+ args: {
47
+ width: 600,
48
+ className: "w-[600px] h-[600px] bg-gray-50",
49
+ artwork: {
50
+ type: "regular",
51
+ src: "https://placehold.co/600x600/png?text=Artwork"
52
+ },
53
+ product: mockProduct,
54
+ placement: "Front",
55
+ mockupId: "mockup-1",
56
+ variantId: "default",
57
+ },
58
+ render: (args) => (
59
+ <div className="w-[600px] h-[600px] border border-gray-200 rounded-lg overflow-hidden">
60
+ {/* Simulate initial loading state by not providing enough info or forcing load state if possible */}
61
+ {/* Actually, the component handles loading shimmer when artwork is present but image not ready */}
62
+ <HeroProductImage {...args} />
63
+ </div>
64
+ )
65
+ };
66
+
@@ -0,0 +1,105 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import React from 'react';
3
+ import { Icon } from '@iconify/react';
4
+
5
+ /**
6
+ * PDPHeroGallery - Product Detail Page Hero Image Gallery
7
+ *
8
+ * The main product image area showing large mockups that stack vertically.
9
+ * On desktop, the info panel overlaps these images on the right side.
10
+ *
11
+ * Features:
12
+ * - Full-width hero images that stack vertically
13
+ * - Each image takes ~70vh height
14
+ * - Placement label in bottom left
15
+ * - Click to trigger zoom/lightbox
16
+ */
17
+
18
+ // Demo mockup images
19
+ const demoMockups = [
20
+ { id: 'front', label: 'Front' },
21
+ { id: 'back', label: 'Back' },
22
+ { id: 'detail', label: 'Detail' },
23
+ ];
24
+
25
+ interface PDPHeroGalleryProps {
26
+ mockups?: { id: string; label: string }[];
27
+ onImageClick?: (mockupId: string) => void;
28
+ }
29
+
30
+ const PDPHeroGallery = ({
31
+ mockups = demoMockups,
32
+ onImageClick,
33
+ }: PDPHeroGalleryProps) => {
34
+ return (
35
+ <div className="w-full">
36
+ {mockups.map((mockup) => (
37
+ <div
38
+ key={mockup.id}
39
+ className="relative w-full cursor-pointer"
40
+ style={{ height: '70vh' }}
41
+ onClick={() => onImageClick?.(mockup.id)}
42
+ >
43
+ {/* Placeholder gradient - in real usage this is HeroProductImage */}
44
+ <div className="w-full h-full bg-gradient-to-br from-muted to-muted/50 flex items-center justify-center">
45
+ <div className="text-center text-muted-foreground">
46
+ <Icon
47
+ icon="gravity-ui:t-shirt"
48
+ className="w-24 h-24 mx-auto mb-4 opacity-20"
49
+ />
50
+ <p className="text-lg font-medium opacity-60">{mockup.label} View</p>
51
+ <p className="text-sm opacity-40">Product mockup</p>
52
+ </div>
53
+ </div>
54
+
55
+ </div>
56
+ ))}
57
+ </div>
58
+ );
59
+ };
60
+
61
+ const meta: Meta<typeof PDPHeroGallery> = {
62
+ title: 'Layouts/PDP Hero Gallery',
63
+ component: PDPHeroGallery,
64
+ parameters: {
65
+ layout: 'fullscreen',
66
+ docs: {
67
+ description: {
68
+ component: `
69
+ The PDP Hero Gallery displays large product mockup images in a vertical stack.
70
+
71
+ ## Layout
72
+
73
+ - Images are full-width and stack vertically
74
+ - Each image is approximately 70vh tall
75
+ - The info panel (not shown here) overlaps on the right side on desktop
76
+
77
+ ## Usage
78
+
79
+ In the actual PDP, this is used with \`HeroProductImage\` components:
80
+
81
+ \`\`\`tsx
82
+ {heroImages.map((image) => (
83
+ <HeroProductImage
84
+ key={image.mockupId}
85
+ mockupId={image.mockupId}
86
+ placement={image.placement}
87
+ onClick={() => openLightbox(image)}
88
+ />
89
+ ))}
90
+ \`\`\`
91
+ `,
92
+ },
93
+ },
94
+ },
95
+ tags: ['autodocs'],
96
+ };
97
+
98
+ export default meta;
99
+ type Story = StoryObj<typeof meta>;
100
+
101
+ export const Default: Story = {
102
+ args: {
103
+ onImageClick: (id) => console.log(`Clicked: ${id}`),
104
+ },
105
+ };