@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,221 @@
1
+ "use client";
2
+
3
+ 'use client';
4
+
5
+ import * as React from 'react';
6
+ import * as SliderPrimitive from '@radix-ui/react-slider';
7
+ import { cn } from '../lib/utils';
8
+
9
+ interface SliderProps
10
+ extends Omit<
11
+ React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>,
12
+ 'value' | 'onValueChange'
13
+ > {
14
+ // Accepts both array and single number for convenience
15
+ value?: number | number[];
16
+ onValueChange?: (value: number[]) => void;
17
+ onChangeEnd?: (value: number[]) => void;
18
+ children?: React.ReactNode;
19
+ }
20
+
21
+ // Context for passing slider state to compound components
22
+ interface SliderContextValue {
23
+ values: number[];
24
+ min: number;
25
+ max: number;
26
+ }
27
+ const SliderContext = React.createContext<SliderContextValue | null>(null);
28
+
29
+ // HeroUI-compatible compound components
30
+ const SliderTrack = React.forwardRef<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>(
31
+ ({ className, children, ...props }, ref) => (
32
+ <SliderPrimitive.Track
33
+ ref={ref}
34
+ className={cn('relative h-2 w-full grow overflow-hidden rounded-full bg-muted', className)}
35
+ {...props}
36
+ >
37
+ {children}
38
+ </SliderPrimitive.Track>
39
+ )
40
+ );
41
+ SliderTrack.displayName = 'SliderTrack';
42
+
43
+ const SliderThumb = React.forwardRef<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>(
44
+ ({ className, ...props }, ref) => (
45
+ <SliderPrimitive.Thumb
46
+ ref={ref}
47
+ // Base thumb size: h-6 w-6 (24px), touch devices can override via CSS to 32-36px
48
+ className={cn(
49
+ 'relative block h-6 w-6 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
50
+ className
51
+ )}
52
+ {...props}
53
+ />
54
+ )
55
+ );
56
+ SliderThumb.displayName = 'SliderThumb';
57
+
58
+ // HeroUI Fill component - displays the filled range portion
59
+ const SliderFill = React.forwardRef<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>(
60
+ ({ className, ...props }, ref) => (
61
+ <SliderPrimitive.Range ref={ref} className={cn('absolute h-full bg-primary', className)} {...props} />
62
+ )
63
+ );
64
+ SliderFill.displayName = 'SliderFill';
65
+
66
+ // HeroUI Output component - renders current value(s)
67
+ interface SliderOutputProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'children'> {
68
+ children?: React.ReactNode | ((props: { state: { values: number[] } }) => React.ReactNode);
69
+ }
70
+
71
+ const SliderOutput = React.forwardRef<HTMLSpanElement, SliderOutputProps>(
72
+ ({ className, children, ...props }, ref) => {
73
+ const context = React.useContext(SliderContext);
74
+ const values = context?.values ?? [0];
75
+
76
+ return (
77
+ <span ref={ref} className={cn('text-sm text-foreground/60', className)} {...props}>
78
+ {typeof children === 'function'
79
+ ? children({ state: { values } })
80
+ : children ?? values.join(' - ')}
81
+ </span>
82
+ );
83
+ }
84
+ );
85
+ SliderOutput.displayName = 'SliderOutput';
86
+
87
+ const SliderBase = React.forwardRef<React.ElementRef<typeof SliderPrimitive.Root>, SliderProps>(
88
+ (
89
+ {
90
+ className,
91
+ value,
92
+ onValueChange,
93
+ onChangeEnd,
94
+ min = 0,
95
+ max = 100,
96
+ step = 1,
97
+ children,
98
+ ...props
99
+ },
100
+ ref
101
+ ) => {
102
+ // Normalize value to array (accepts both single number and array for convenience)
103
+ const normalizedValue = value !== undefined ? (Array.isArray(value) ? value : [value]) : undefined;
104
+
105
+ // Track internal value for context
106
+ const [internalValue, setInternalValue] = React.useState(normalizedValue ?? [min]);
107
+
108
+ // Track if we're currently dragging (for iOS body scroll prevention)
109
+ const isDraggingRef = React.useRef(false);
110
+ const touchMoveHandlerRef = React.useRef<((e: TouchEvent) => void) | null>(null);
111
+
112
+ // Prevent scroll during drag - added to document on pointer down, removed on pointer up
113
+ const preventScroll = React.useCallback((e: TouchEvent) => {
114
+ if (isDraggingRef.current) {
115
+ e.preventDefault();
116
+ }
117
+ }, []);
118
+
119
+ const handleChange = (newValue: number[]) => {
120
+ // Mark as actively dragging once value changes
121
+ isDraggingRef.current = true;
122
+ setInternalValue(newValue);
123
+ onValueChange?.(newValue);
124
+ };
125
+
126
+ const handleChangeEnd = (newValue: number[]) => {
127
+ isDraggingRef.current = false;
128
+ onChangeEnd?.(newValue);
129
+ };
130
+
131
+ // Handle pointer down - add document-level touchmove prevention
132
+ const handlePointerDown = React.useCallback(() => {
133
+ // Add touchmove listener to document to intercept scroll during drag
134
+ if (!touchMoveHandlerRef.current) {
135
+ touchMoveHandlerRef.current = preventScroll;
136
+ document.addEventListener('touchmove', preventScroll, { passive: false });
137
+ }
138
+ }, [preventScroll]);
139
+
140
+ // Handle pointer up/cancel - remove document listener
141
+ const handlePointerUp = React.useCallback(() => {
142
+ isDraggingRef.current = false;
143
+ if (touchMoveHandlerRef.current) {
144
+ document.removeEventListener('touchmove', touchMoveHandlerRef.current);
145
+ touchMoveHandlerRef.current = null;
146
+ }
147
+ }, []);
148
+
149
+ // Cleanup on unmount
150
+ React.useEffect(() => {
151
+ return () => {
152
+ if (touchMoveHandlerRef.current) {
153
+ document.removeEventListener('touchmove', touchMoveHandlerRef.current);
154
+ }
155
+ };
156
+ }, []);
157
+
158
+ // Check if children are provided (compound pattern)
159
+ const hasChildren = React.Children.count(children) > 0;
160
+
161
+ // Determine number of thumbs based on value array length or defaultValue
162
+ const thumbCount = normalizedValue?.length ?? props.defaultValue?.length ?? 1;
163
+
164
+ // Context value for compound components
165
+ const contextValue = React.useMemo(
166
+ () => ({
167
+ values: normalizedValue ?? internalValue,
168
+ min,
169
+ max,
170
+ }),
171
+ [normalizedValue, internalValue, min, max]
172
+ );
173
+
174
+ return (
175
+ <SliderContext.Provider value={contextValue}>
176
+ <SliderPrimitive.Root
177
+ ref={ref}
178
+ className={cn('relative flex w-full touch-none select-none items-center', className)}
179
+ value={normalizedValue}
180
+ onValueChange={handleChange}
181
+ onValueCommit={handleChangeEnd}
182
+ onPointerDown={handlePointerDown}
183
+ onPointerUp={handlePointerUp}
184
+ onPointerCancel={handlePointerUp}
185
+ onLostPointerCapture={handlePointerUp}
186
+ min={min}
187
+ max={max}
188
+ step={step}
189
+ {...props}
190
+ >
191
+ {hasChildren ? (
192
+ children
193
+ ) : (
194
+ <>
195
+ <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-muted">
196
+ <SliderPrimitive.Range className="absolute h-full bg-primary" />
197
+ </SliderPrimitive.Track>
198
+ {Array.from({ length: thumbCount }).map((_, index) => (
199
+ <SliderPrimitive.Thumb
200
+ key={index}
201
+ className="relative block h-6 w-6 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
202
+ />
203
+ ))}
204
+ </>
205
+ )}
206
+ </SliderPrimitive.Root>
207
+ </SliderContext.Provider>
208
+ );
209
+ }
210
+ );
211
+ SliderBase.displayName = SliderPrimitive.Root.displayName;
212
+
213
+ // Compound component with sub-components
214
+ const Slider = Object.assign(SliderBase, {
215
+ Track: SliderTrack,
216
+ Thumb: SliderThumb,
217
+ Fill: SliderFill,
218
+ Output: SliderOutput,
219
+ });
220
+
221
+ export { Slider, SliderTrack, SliderThumb, SliderFill, SliderOutput };
@@ -0,0 +1,72 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { cva, type VariantProps } from 'class-variance-authority';
5
+ import { cn } from '../lib/utils';
6
+
7
+ const spinnerVariants = cva('pointer-events-none relative origin-center animate-spin size-6 text-primary', {
8
+ variants: {
9
+ size: {
10
+ sm: 'size-4',
11
+ md: 'size-6',
12
+ lg: 'size-8',
13
+ xl: 'size-10',
14
+ },
15
+ color: {
16
+ primary: 'text-primary',
17
+ secondary: 'text-foreground/60',
18
+ success: 'text-success',
19
+ warning: 'text-warning',
20
+ danger: 'text-danger',
21
+ current: 'text-current',
22
+ },
23
+ },
24
+ });
25
+
26
+ export interface SpinnerProps
27
+ extends Omit<React.ComponentProps<'span'>, 'color'>,
28
+ VariantProps<typeof spinnerVariants> {
29
+ /** Label for screen readers */
30
+ label?: string;
31
+ }
32
+
33
+ function Spinner({ className, size, color, label = 'Loading', ...props }: SpinnerProps) {
34
+ const id = React.useId();
35
+
36
+ return (
37
+ <span
38
+ role="status"
39
+ aria-label={label}
40
+ className={cn(spinnerVariants({ size, color }), className)}
41
+ {...props}
42
+ >
43
+ <svg viewBox="0 0 24 24" className="size-full" aria-hidden>
44
+ <defs>
45
+ <linearGradient id={`spinner-grad-1-${id}`} x1="50%" x2="50%" y1="5.271%" y2="91.793%">
46
+ <stop offset="0%" stopColor="currentColor" />
47
+ <stop offset="100%" stopColor="currentColor" stopOpacity={0.55} />
48
+ </linearGradient>
49
+ <linearGradient id={`spinner-grad-2-${id}`} x1="50%" x2="50%" y1="15.24%" y2="87.15%">
50
+ <stop offset="0%" stopColor="currentColor" stopOpacity={0} />
51
+ <stop offset="100%" stopColor="currentColor" stopOpacity={0.55} />
52
+ </linearGradient>
53
+ </defs>
54
+ <g fill="none">
55
+ <path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
56
+ <path
57
+ d="M8.749.021a1.5 1.5 0 0 1 .497 2.958A7.5 7.5 0 0 0 3 10.375a7.5 7.5 0 0 0 7.5 7.5v3c-5.799 0-10.5-4.7-10.5-10.5C0 5.23 3.726.865 8.749.021"
58
+ fill={`url(#spinner-grad-1-${id})`}
59
+ transform="translate(1.5 1.625)"
60
+ />
61
+ <path
62
+ d="M15.392 2.673a1.5 1.5 0 0 1 2.119-.115A10.48 10.48 0 0 1 21 10.375c0 5.8-4.701 10.5-10.5 10.5v-3a7.5 7.5 0 0 0 5.007-13.084a1.5 1.5 0 0 1-.115-2.118"
63
+ fill={`url(#spinner-grad-2-${id})`}
64
+ transform="translate(1.5 1.625)"
65
+ />
66
+ </g>
67
+ </svg>
68
+ </span>
69
+ );
70
+ }
71
+
72
+ export { Spinner, spinnerVariants };
@@ -0,0 +1,121 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '../accordion';
3
+
4
+ const meta = {
5
+ title: 'UI/Accordion',
6
+ component: Accordion,
7
+ parameters: {
8
+ layout: 'centered',
9
+ },
10
+ tags: ['autodocs'],
11
+ } satisfies Meta<typeof Accordion>;
12
+
13
+ export default meta;
14
+ type Story = StoryObj<typeof Accordion>;
15
+
16
+ export const Default: Story = {
17
+ render: () => (
18
+ <Accordion type="single" collapsible className="w-[400px]">
19
+ <AccordionItem value="item-1">
20
+ <AccordionTrigger>Is it accessible?</AccordionTrigger>
21
+ <AccordionContent>Yes. It adheres to the WAI-ARIA design pattern.</AccordionContent>
22
+ </AccordionItem>
23
+ <AccordionItem value="item-2">
24
+ <AccordionTrigger>Is it styled?</AccordionTrigger>
25
+ <AccordionContent>
26
+ Yes. It comes with default styles that match your theme.
27
+ </AccordionContent>
28
+ </AccordionItem>
29
+ <AccordionItem value="item-3">
30
+ <AccordionTrigger>Is it animated?</AccordionTrigger>
31
+ <AccordionContent>
32
+ Yes. It has smooth expand/collapse animations using CSS keyframes.
33
+ </AccordionContent>
34
+ </AccordionItem>
35
+ </Accordion>
36
+ ),
37
+ };
38
+
39
+ export const Multiple: Story = {
40
+ render: () => (
41
+ <Accordion type="multiple" className="w-[400px]">
42
+ <AccordionItem value="item-1">
43
+ <AccordionTrigger>Can I open multiple items?</AccordionTrigger>
44
+ <AccordionContent>
45
+ Yes! When using type="multiple", you can have multiple items open at once.
46
+ </AccordionContent>
47
+ </AccordionItem>
48
+ <AccordionItem value="item-2">
49
+ <AccordionTrigger>How does it work?</AccordionTrigger>
50
+ <AccordionContent>
51
+ Just set the type prop to "multiple" instead of "single".
52
+ </AccordionContent>
53
+ </AccordionItem>
54
+ <AccordionItem value="item-3">
55
+ <AccordionTrigger>What about collapsible?</AccordionTrigger>
56
+ <AccordionContent>
57
+ The collapsible prop only applies to type="single" accordions.
58
+ </AccordionContent>
59
+ </AccordionItem>
60
+ </Accordion>
61
+ ),
62
+ };
63
+
64
+ export const DefaultOpen: Story = {
65
+ render: () => (
66
+ <Accordion type="single" defaultValue="item-2" collapsible className="w-[400px]">
67
+ <AccordionItem value="item-1">
68
+ <AccordionTrigger>First Section</AccordionTrigger>
69
+ <AccordionContent>Content for the first section.</AccordionContent>
70
+ </AccordionItem>
71
+ <AccordionItem value="item-2">
72
+ <AccordionTrigger>Second Section (Default Open)</AccordionTrigger>
73
+ <AccordionContent>
74
+ This section is open by default because of the defaultValue prop.
75
+ </AccordionContent>
76
+ </AccordionItem>
77
+ <AccordionItem value="item-3">
78
+ <AccordionTrigger>Third Section</AccordionTrigger>
79
+ <AccordionContent>Content for the third section.</AccordionContent>
80
+ </AccordionItem>
81
+ </Accordion>
82
+ ),
83
+ };
84
+
85
+ export const FAQ: Story = {
86
+ render: () => (
87
+ <div className="w-[500px]">
88
+ <h2 className="text-xl font-heading mb-4">Frequently Asked Questions</h2>
89
+ <Accordion type="single" collapsible>
90
+ <AccordionItem value="q1">
91
+ <AccordionTrigger>What payment methods do you accept?</AccordionTrigger>
92
+ <AccordionContent>
93
+ We accept all major credit cards (Visa, MasterCard, American Express), PayPal,
94
+ and bank transfers for larger orders.
95
+ </AccordionContent>
96
+ </AccordionItem>
97
+ <AccordionItem value="q2">
98
+ <AccordionTrigger>How long does shipping take?</AccordionTrigger>
99
+ <AccordionContent>
100
+ Standard shipping takes 5-7 business days. Express shipping (2-3 days) is
101
+ available for an additional fee. International orders may take 10-14 days.
102
+ </AccordionContent>
103
+ </AccordionItem>
104
+ <AccordionItem value="q3">
105
+ <AccordionTrigger>What is your return policy?</AccordionTrigger>
106
+ <AccordionContent>
107
+ We offer a 30-day return policy for all unused items in their original
108
+ packaging. Custom orders are final sale and cannot be returned.
109
+ </AccordionContent>
110
+ </AccordionItem>
111
+ <AccordionItem value="q4">
112
+ <AccordionTrigger>Do you offer wholesale pricing?</AccordionTrigger>
113
+ <AccordionContent>
114
+ Yes! We offer wholesale pricing for orders of 50+ units. Please contact our
115
+ sales team at wholesale@example.com for more information.
116
+ </AccordionContent>
117
+ </AccordionItem>
118
+ </Accordion>
119
+ </div>
120
+ ),
121
+ };
@@ -0,0 +1,221 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Icon } from '@iconify/react';
3
+ import { Badge, Chip } from '../badge';
4
+ import { Surface } from '../surface';
5
+
6
+ const meta: Meta<typeof Badge> = {
7
+ title: 'UI/Badge',
8
+ component: Badge,
9
+ parameters: {
10
+ layout: 'centered',
11
+ },
12
+ tags: ['autodocs'],
13
+ argTypes: {
14
+ variant: {
15
+ control: 'select',
16
+ options: ['default', 'secondary', 'destructive', 'success', 'warning', 'outline', 'gradient'],
17
+ },
18
+ size: {
19
+ control: 'select',
20
+ options: ['default', 'sm', 'lg'],
21
+ },
22
+ color: {
23
+ control: 'select',
24
+ options: ['default', 'primary', 'secondary', 'success', 'warning', 'danger'],
25
+ description: 'HeroUI Chip compatibility prop',
26
+ },
27
+ dot: {
28
+ control: 'boolean',
29
+ },
30
+ startContent: { control: false },
31
+ endContent: { control: false },
32
+ onClose: { control: false },
33
+ },
34
+ };
35
+
36
+ export default meta;
37
+ type Story = StoryObj<typeof meta>;
38
+
39
+ export const Default: Story = {
40
+ args: {
41
+ children: 'Badge',
42
+ },
43
+ };
44
+
45
+ export const AllVariants: Story = {
46
+ render: () => (
47
+ <div className="flex flex-wrap gap-2">
48
+ <Badge variant="default">Default</Badge>
49
+ <Badge variant="secondary">Secondary</Badge>
50
+ <Badge variant="destructive">Destructive</Badge>
51
+ <Badge variant="success">Success</Badge>
52
+ <Badge variant="warning">Warning</Badge>
53
+ <Badge variant="outline">Outline</Badge>
54
+ <Badge variant="gradient">Gradient</Badge>
55
+ </div>
56
+ ),
57
+ };
58
+
59
+ export const AllSizes: Story = {
60
+ render: () => (
61
+ <div className="flex items-center gap-2">
62
+ <Badge size="sm">Small</Badge>
63
+ <Badge size="default">Default</Badge>
64
+ <Badge size="lg">Large</Badge>
65
+ </div>
66
+ ),
67
+ };
68
+
69
+ export const WithDot: Story = {
70
+ render: () => (
71
+ <div className="flex flex-wrap gap-2">
72
+ <Badge variant="default" dot>
73
+ Active
74
+ </Badge>
75
+ <Badge variant="success" dot>
76
+ Online
77
+ </Badge>
78
+ <Badge variant="warning" dot>
79
+ Pending
80
+ </Badge>
81
+ <Badge variant="destructive" dot>
82
+ Offline
83
+ </Badge>
84
+ </div>
85
+ ),
86
+ };
87
+
88
+ export const WithIcons: Story = {
89
+ render: () => (
90
+ <div className="flex flex-wrap gap-2">
91
+ <Badge startContent={<Icon icon="gravity-ui:check" className="size-3" />}>Verified</Badge>
92
+ <Badge endContent={<Icon icon="gravity-ui:arrow-right" className="size-3" />}>Next</Badge>
93
+ <Badge
94
+ variant="success"
95
+ startContent={<Icon icon="gravity-ui:circle-check-fill" className="size-3" />}
96
+ >
97
+ Completed
98
+ </Badge>
99
+ </div>
100
+ ),
101
+ };
102
+
103
+ export const Dismissible: Story = {
104
+ render: () => (
105
+ <div className="flex flex-wrap gap-2">
106
+ <Badge onClose={() => alert('Dismissed!')}>Dismissible</Badge>
107
+ <Badge variant="secondary" onClose={() => alert('Dismissed!')}>
108
+ Click X to close
109
+ </Badge>
110
+ <Badge variant="success" onClose={() => alert('Dismissed!')}>
111
+ Success
112
+ </Badge>
113
+ </div>
114
+ ),
115
+ };
116
+
117
+ export const HeroUIColorProp: Story = {
118
+ name: 'HeroUI Color Prop (Chip)',
119
+ render: () => (
120
+ <div className="flex flex-wrap gap-2">
121
+ <Chip color="default">Default</Chip>
122
+ <Chip color="primary">Primary</Chip>
123
+ <Chip color="secondary">Secondary</Chip>
124
+ <Chip color="success">Success</Chip>
125
+ <Chip color="warning">Warning</Chip>
126
+ <Chip color="danger">Danger</Chip>
127
+ </div>
128
+ ),
129
+ };
130
+
131
+ export const StatusBadges: Story = {
132
+ render: () => (
133
+ <div className="space-y-4">
134
+ <div className="flex items-center gap-2">
135
+ <span className="text-sm text-foreground/70 w-20">Order:</span>
136
+ <Badge variant="success" dot>
137
+ Shipped
138
+ </Badge>
139
+ </div>
140
+ <div className="flex items-center gap-2">
141
+ <span className="text-sm text-foreground/70 w-20">Payment:</span>
142
+ <Badge variant="warning" dot>
143
+ Pending
144
+ </Badge>
145
+ </div>
146
+ <div className="flex items-center gap-2">
147
+ <span className="text-sm text-foreground/70 w-20">Status:</span>
148
+ <Badge variant="destructive" dot>
149
+ Cancelled
150
+ </Badge>
151
+ </div>
152
+ </div>
153
+ ),
154
+ };
155
+
156
+ export const TagList: Story = {
157
+ render: () => (
158
+ <div className="flex flex-wrap gap-2 max-w-sm">
159
+ {['React', 'TypeScript', 'Tailwind', 'Radix UI', 'Storybook'].map((tag) => (
160
+ <Badge key={tag} variant="secondary" onClose={() => console.log(`Remove ${tag}`)}>
161
+ {tag}
162
+ </Badge>
163
+ ))}
164
+ </div>
165
+ ),
166
+ };
167
+
168
+ export const OnSurfaces: Story = {
169
+ name: 'Surface Awareness',
170
+ parameters: {
171
+ layout: 'padded',
172
+ },
173
+ render: () => (
174
+ <div className="space-y-8">
175
+ {/* On transparent background (page) */}
176
+ <div className="space-y-3">
177
+ <h3 className="text-sm font-medium text-muted-foreground">On Page Background (transparent)</h3>
178
+ <Surface variant="transparent" className="p-4 rounded-lg border border-dashed border-border">
179
+ <div className="flex flex-wrap gap-2">
180
+ <Badge variant="default">Default</Badge>
181
+ <Badge variant="secondary">Secondary</Badge>
182
+ <Badge variant="outline">Outline</Badge>
183
+ <Badge variant="destructive">Destructive</Badge>
184
+ <Badge variant="success">Success</Badge>
185
+ <Badge variant="warning">Warning</Badge>
186
+ </div>
187
+ </Surface>
188
+ </div>
189
+
190
+ {/* On default surface (card) */}
191
+ <div className="space-y-3">
192
+ <h3 className="text-sm font-medium text-muted-foreground">On Surface (card)</h3>
193
+ <Surface variant="default" className="p-4 rounded-lg">
194
+ <div className="flex flex-wrap gap-2">
195
+ <Badge variant="default">Default</Badge>
196
+ <Badge variant="secondary">Secondary</Badge>
197
+ <Badge variant="outline">Outline</Badge>
198
+ <Badge variant="destructive">Destructive</Badge>
199
+ <Badge variant="success">Success</Badge>
200
+ <Badge variant="warning">Warning</Badge>
201
+ </div>
202
+ </Surface>
203
+ </div>
204
+
205
+ {/* On secondary surface */}
206
+ <div className="space-y-3">
207
+ <h3 className="text-sm font-medium text-muted-foreground">On Secondary Surface</h3>
208
+ <Surface variant="secondary" className="p-4 rounded-lg">
209
+ <div className="flex flex-wrap gap-2">
210
+ <Badge variant="default">Default</Badge>
211
+ <Badge variant="secondary">Secondary</Badge>
212
+ <Badge variant="outline">Outline</Badge>
213
+ <Badge variant="destructive">Destructive</Badge>
214
+ <Badge variant="success">Success</Badge>
215
+ <Badge variant="warning">Warning</Badge>
216
+ </div>
217
+ </Surface>
218
+ </div>
219
+ </div>
220
+ ),
221
+ };