@snowcone-app/ui 0.1.43 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +18 -4
  3. package/dist/index.cjs +5 -2
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.js +5 -2
  6. package/dist/index.js.map +1 -1
  7. package/package.json +9 -5
  8. package/src/components/CanvasIsolationBoundary.tsx +202 -0
  9. package/src/components/LoadingOverlayPrism.tsx +251 -0
  10. package/src/composed/AddToCart.tsx +229 -0
  11. package/src/composed/ArtAlignment.tsx +703 -0
  12. package/src/composed/ArtSelector.tsx +290 -0
  13. package/src/composed/ArtworkCustomizer.tsx +212 -0
  14. package/src/composed/CanvasEditor.tsx +79 -0
  15. package/src/composed/ColorPicker.tsx +111 -0
  16. package/src/composed/CurrentSelectionDisplay.tsx +86 -0
  17. package/src/composed/HeroProductImage.tsx +1079 -0
  18. package/src/composed/Lightbox.index.ts +2 -0
  19. package/src/composed/Lightbox.tsx +230 -0
  20. package/src/composed/PlacementClipShapeSelector.tsx +88 -0
  21. package/src/composed/PlacementTabs.tsx +179 -0
  22. package/src/composed/ProductCard.tsx +298 -0
  23. package/src/composed/ProductGallery.tsx +54 -0
  24. package/src/composed/ProductImage.tsx +129 -0
  25. package/src/composed/ProductList.tsx +147 -0
  26. package/src/composed/ProductOptions.tsx +305 -0
  27. package/src/composed/RealtimeMockup.tsx +121 -0
  28. package/src/composed/TileCount.tsx +348 -0
  29. package/src/composed/carousels/HeroCarousel.tsx +240 -0
  30. package/src/composed/carousels/MobileProductCarousel.tsx +1002 -0
  31. package/src/composed/carousels/index.ts +11 -0
  32. package/src/composed/carousels/types.ts +58 -0
  33. package/src/composed/grids/MasonryGrid.tsx +238 -0
  34. package/src/composed/grids/index.ts +9 -0
  35. package/src/composed/search/CurrentRefinements.tsx +80 -0
  36. package/src/composed/search/Filters.tsx +49 -0
  37. package/src/composed/search/FiltersButton.tsx +57 -0
  38. package/src/composed/search/FiltersDrawer.tsx +375 -0
  39. package/src/composed/search/ProductGrid.tsx +118 -0
  40. package/src/composed/search/ProductHit.tsx +56 -0
  41. package/src/composed/search/SearchBox.tsx +109 -0
  42. package/src/composed/search/SearchProvider.tsx +136 -0
  43. package/src/composed/search/facetConfig.ts +16 -0
  44. package/src/composed/search/index.ts +22 -0
  45. package/src/composed/search/meilisearchAdapter.ts +20 -0
  46. package/src/composed/search/types.ts +22 -0
  47. package/src/composed/zoom/EnhancedImageViewer.tsx +505 -0
  48. package/src/composed/zoom/ResponsiveZoom.tsx +134 -0
  49. package/src/composed/zoom/ZoomOverlay.tsx +194 -0
  50. package/src/composed/zoom/index.ts +12 -0
  51. package/src/composed/zoom/types.ts +12 -0
  52. package/src/design-system/ColorPalette.tsx +126 -0
  53. package/src/design-system/ColorSwatch.tsx +49 -0
  54. package/src/design-system/DesignSystemPage.tsx +130 -0
  55. package/src/design-system/ThemeSwitcher.tsx +181 -0
  56. package/src/design-system/TypographyScale.tsx +106 -0
  57. package/src/design-system/index.ts +5 -0
  58. package/src/ecommerce/stories/HeroProductImage.stories.tsx +66 -0
  59. package/src/ecommerce/stories/PDPHeroGallery.stories.tsx +105 -0
  60. package/src/ecommerce/stories/PDPInfoPanel.stories.tsx +472 -0
  61. package/src/ecommerce/stories/PDPLayout.stories.tsx +365 -0
  62. package/src/hooks/useBrand.ts +41 -0
  63. package/src/hooks/useCanvasContext.ts +127 -0
  64. package/src/hooks/useDeviceDetection.ts +64 -0
  65. package/src/hooks/useFocusTrap.ts +70 -0
  66. package/src/hooks/useImagePreloader.ts +268 -0
  67. package/src/hooks/useImageTransition.ts +608 -0
  68. package/src/hooks/usePlacementsProcessor.ts +74 -0
  69. package/src/hooks/useProductGallery.ts +193 -0
  70. package/src/hooks/useProductPage.ts +467 -0
  71. package/src/hooks/useRenderGuard.ts +96 -0
  72. package/src/hooks/useScrollDirection.ts +196 -0
  73. package/src/hooks/viewport/index.ts +25 -0
  74. package/src/hooks/viewport/useContainerWidth.ts +59 -0
  75. package/src/hooks/viewport/useMediaQuery.ts +52 -0
  76. package/src/hooks/viewport/useResponsiveImageCap.ts +149 -0
  77. package/src/hooks/viewport/useViewportDimensions.ts +135 -0
  78. package/src/hooks/viewport/useWideMonitorMode.ts +150 -0
  79. package/src/hooks/visibility/index.ts +15 -0
  80. package/src/hooks/visibility/observerPool.ts +150 -0
  81. package/src/index.ts +240 -0
  82. package/src/layouts/hero-zoom/HeroShrinkLayout.tsx +209 -0
  83. package/src/layouts/hero-zoom/HeroZoomLayout.tsx +351 -0
  84. package/src/layouts/hero-zoom/index.ts +30 -0
  85. package/src/layouts/hero-zoom/stories/HeroZoomLayout.stories.tsx +350 -0
  86. package/src/layouts/hero-zoom/types.ts +113 -0
  87. package/src/layouts/hero-zoom/useHeroZoomScales.ts +156 -0
  88. package/src/layouts/index.ts +9 -0
  89. package/src/layouts/pdp/EdgeBlurBox.tsx +210 -0
  90. package/src/layouts/pdp/ImageBlurExtension.tsx +215 -0
  91. package/src/layouts/pdp/ImageEdgeBlur.tsx +215 -0
  92. package/src/layouts/pdp/PDPLayout.tsx +246 -0
  93. package/src/layouts/pdp/SimpleImageBlur.tsx +140 -0
  94. package/src/layouts/pdp/index.ts +40 -0
  95. package/src/lib/env.ts +15 -0
  96. package/src/lib/locale.ts +167 -0
  97. package/src/lib/router.tsx +46 -0
  98. package/src/lib/utils.ts +6 -0
  99. package/src/lightbox/README.md +77 -0
  100. package/src/next/index.tsx +26 -0
  101. package/src/patterns/MockupPriorityProvider.tsx +1014 -0
  102. package/src/patterns/Product.tsx +850 -0
  103. package/src/patterns/ProductPageProvider.tsx +224 -0
  104. package/src/patterns/RealtimeProvider.tsx +1162 -0
  105. package/src/patterns/ShopProvider.tsx +603 -0
  106. package/src/personalization/PersonalizationBridge.tsx +235 -0
  107. package/src/personalization/PersonalizationContext.ts +29 -0
  108. package/src/personalization/PersonalizationInputs.tsx +110 -0
  109. package/src/personalization/PersonalizationProvider.tsx +407 -0
  110. package/src/personalization/canvas-stub.d.ts +22 -0
  111. package/src/personalization/index.ts +43 -0
  112. package/src/personalization/types.ts +48 -0
  113. package/src/personalization/usePersonalization.ts +32 -0
  114. package/src/personalization/usePersonalizationShimmer.ts +159 -0
  115. package/src/personalization/utils.ts +59 -0
  116. package/src/primitives/BrandLogo.tsx +65 -0
  117. package/src/primitives/BrandName.tsx +51 -0
  118. package/src/primitives/Button.tsx +123 -0
  119. package/src/primitives/ColorSwatch.tsx +221 -0
  120. package/src/primitives/DragHintAnimation.tsx +190 -0
  121. package/src/primitives/EdgeSwipeGuards.tsx +60 -0
  122. package/src/primitives/FloatingActionGroup.tsx +176 -0
  123. package/src/primitives/ProductPrice.tsx +171 -0
  124. package/src/primitives/ProgressiveBlur.tsx +295 -0
  125. package/src/primitives/ThemeToggle.tsx +125 -0
  126. package/src/primitives/__tests__/story-coverage.test.ts +98 -0
  127. package/src/primitives/accordion.tsx +280 -0
  128. package/src/primitives/badge.tsx +137 -0
  129. package/src/primitives/card.tsx +61 -0
  130. package/src/primitives/checkbox.tsx +56 -0
  131. package/src/primitives/collapsible.tsx +51 -0
  132. package/src/primitives/drawer.tsx +828 -0
  133. package/src/primitives/dropdown-menu.tsx +197 -0
  134. package/src/primitives/fieldset.tsx +73 -0
  135. package/src/primitives/index.ts +138 -0
  136. package/src/primitives/input.tsx +91 -0
  137. package/src/primitives/kbd.tsx +130 -0
  138. package/src/primitives/label.tsx +20 -0
  139. package/src/primitives/link.tsx +182 -0
  140. package/src/primitives/popover.tsx +80 -0
  141. package/src/primitives/radio-group.tsx +79 -0
  142. package/src/primitives/scroll-fade.tsx +159 -0
  143. package/src/primitives/select.tsx +170 -0
  144. package/src/primitives/separator.tsx +25 -0
  145. package/src/primitives/slider.tsx +221 -0
  146. package/src/primitives/spinner.tsx +72 -0
  147. package/src/primitives/stories/Accordion.stories.tsx +121 -0
  148. package/src/primitives/stories/Badge.stories.tsx +221 -0
  149. package/src/primitives/stories/Button.stories.tsx +185 -0
  150. package/src/primitives/stories/Card.stories.tsx +171 -0
  151. package/src/primitives/stories/Checkbox.stories.tsx +214 -0
  152. package/src/primitives/stories/Collapsible.stories.tsx +230 -0
  153. package/src/primitives/stories/Drawer.stories.tsx +378 -0
  154. package/src/primitives/stories/DropdownMenu.stories.tsx +182 -0
  155. package/src/primitives/stories/Fieldset.stories.tsx +212 -0
  156. package/src/primitives/stories/Input.stories.tsx +172 -0
  157. package/src/primitives/stories/Kbd.stories.tsx +183 -0
  158. package/src/primitives/stories/Label.stories.tsx +98 -0
  159. package/src/primitives/stories/Link.stories.tsx +260 -0
  160. package/src/primitives/stories/Popover.stories.tsx +178 -0
  161. package/src/primitives/stories/RadioGroup.stories.tsx +205 -0
  162. package/src/primitives/stories/Select.stories.tsx +222 -0
  163. package/src/primitives/stories/Separator.stories.tsx +134 -0
  164. package/src/primitives/stories/Slider.stories.tsx +203 -0
  165. package/src/primitives/stories/Spinner.stories.tsx +142 -0
  166. package/src/primitives/stories/Surface.stories.tsx +257 -0
  167. package/src/primitives/stories/Switch.stories.tsx +131 -0
  168. package/src/primitives/stories/Tabs.stories.tsx +275 -0
  169. package/src/primitives/stories/TextField.stories.tsx +139 -0
  170. package/src/primitives/stories/Textarea.stories.tsx +148 -0
  171. package/src/primitives/stories/Tooltip.stories.tsx +119 -0
  172. package/src/primitives/surface.tsx +86 -0
  173. package/src/primitives/switch.tsx +35 -0
  174. package/src/primitives/tabs.tsx +206 -0
  175. package/src/primitives/text-field.tsx +84 -0
  176. package/src/primitives/textarea.tsx +50 -0
  177. package/src/primitives/tooltip.tsx +58 -0
  178. package/src/services/CanvasExportService.ts +518 -0
  179. package/src/styles/base.css +380 -0
  180. package/src/styles/defaults.css +280 -0
  181. package/src/styles/globals.css +1242 -0
  182. package/src/styles/index.css +17 -0
  183. package/src/styles/ne-themes.css +4740 -0
  184. package/src/styles/tailwind.css +11 -0
  185. package/src/styles/tokens.css +117 -0
  186. package/src/styles/utilities.css +188 -0
  187. package/src/themes/apply-theme.ts +449 -0
  188. package/src/themes/getThemeStyles.ts +454 -0
  189. package/src/themes/index.ts +48 -0
  190. package/src/themes/oklch-theme.ts +283 -0
  191. package/src/themes/presets.ts +989 -0
  192. package/src/themes/types.ts +386 -0
  193. package/src/themes/useTheme.tsx +450 -0
  194. package/src/utils/dev-warnings.ts +161 -0
  195. package/src/utils/devWarnings.ts +153 -0
  196. package/dist/styles.css +0 -1
@@ -0,0 +1,197 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
5
+ import { Icon } from '@iconify/react';
6
+ import { cn } from '../lib/utils';
7
+
8
+ const DropdownMenu = DropdownMenuPrimitive.Root;
9
+
10
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
11
+
12
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group;
13
+
14
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
15
+
16
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
17
+
18
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
19
+
20
+ const DropdownMenuSubTrigger = React.forwardRef<
21
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
22
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
23
+ inset?: boolean;
24
+ }
25
+ >(({ className, inset, children, ...props }, ref) => (
26
+ <DropdownMenuPrimitive.SubTrigger
27
+ ref={ref}
28
+ className={cn(
29
+ 'focus:bg-default/50 dark:focus:bg-white/5 data-[state=open]:bg-default/50 dark:data-[state=open]:bg-white/5 flex cursor-default items-center gap-3 rounded-lg px-3 py-3 text-base font-medium text-foreground outline-none select-none',
30
+ '[&_svg]:size-5 [&_svg]:shrink-0 [&_svg]:text-foreground/60',
31
+ inset && 'pl-8',
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ <Icon icon="gravity-ui:chevron-right" className="ml-auto size-5" />
38
+ </DropdownMenuPrimitive.SubTrigger>
39
+ ));
40
+ DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
41
+
42
+ const DropdownMenuSubContent = React.forwardRef<
43
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
44
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
45
+ >(({ className, ...props }, ref) => (
46
+ <DropdownMenuPrimitive.SubContent
47
+ ref={ref}
48
+ className={cn(
49
+ 'z-[200] bg-popover text-popover-foreground min-w-[8rem] overflow-hidden rounded-2xl p-2',
50
+ 'shadow-[0_4px_24px_-4px_rgba(0,0,0,0.15),0_0_0_1px_rgba(0,0,0,0.05)]',
51
+ 'dark:shadow-[0_4px_24px_-4px_rgba(0,0,0,0.4),0_0_0_1px_rgba(255,255,255,0.1)]',
52
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
53
+ 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
54
+ 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',
55
+ 'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
56
+ className
57
+ )}
58
+ {...props}
59
+ />
60
+ ));
61
+ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
62
+
63
+ const DropdownMenuContent = React.forwardRef<
64
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
65
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
66
+ >(({ className, sideOffset = 4, ...props }, ref) => (
67
+ <DropdownMenuPrimitive.Portal>
68
+ <DropdownMenuPrimitive.Content
69
+ ref={ref}
70
+ sideOffset={sideOffset}
71
+ className={cn(
72
+ 'z-[200] bg-popover text-popover-foreground min-w-[8rem] overflow-hidden rounded-2xl p-2',
73
+ 'shadow-[0_4px_24px_-4px_rgba(0,0,0,0.15),0_0_0_1px_rgba(0,0,0,0.05)]',
74
+ 'dark:shadow-[0_4px_24px_-4px_rgba(0,0,0,0.4),0_0_0_1px_rgba(255,255,255,0.1)]',
75
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
76
+ 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
77
+ 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',
78
+ 'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
79
+ className
80
+ )}
81
+ {...props}
82
+ />
83
+ </DropdownMenuPrimitive.Portal>
84
+ ));
85
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
86
+
87
+ const DropdownMenuItem = React.forwardRef<
88
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
89
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
90
+ inset?: boolean;
91
+ }
92
+ >(({ className, inset, ...props }, ref) => (
93
+ <DropdownMenuPrimitive.Item
94
+ ref={ref}
95
+ className={cn(
96
+ 'relative flex cursor-default items-center gap-3 rounded-lg px-3 py-3 text-base font-medium text-foreground transition-colors outline-none select-none',
97
+ '[&_svg]:size-5 [&_svg]:shrink-0 [&_svg]:text-foreground/60',
98
+ 'hover:bg-default/50 focus:bg-default/50 dark:hover:bg-white/5 dark:focus:bg-white/5 data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
99
+ inset && 'pl-8',
100
+ className
101
+ )}
102
+ {...props}
103
+ />
104
+ ));
105
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
106
+
107
+ const DropdownMenuCheckboxItem = React.forwardRef<
108
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
109
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
110
+ >(({ className, children, checked, ...props }, ref) => (
111
+ <DropdownMenuPrimitive.CheckboxItem
112
+ ref={ref}
113
+ className={cn(
114
+ 'relative flex cursor-default items-center rounded-full py-1 pr-2 pl-7 text-sm transition-colors outline-none select-none',
115
+ 'hover:bg-default/50 focus:bg-default/50 dark:hover:bg-white/5 dark:focus:bg-white/5 data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
116
+ className
117
+ )}
118
+ checked={checked}
119
+ {...props}
120
+ >
121
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
122
+ <DropdownMenuPrimitive.ItemIndicator>
123
+ <Icon icon="gravity-ui:check" className="size-4" />
124
+ </DropdownMenuPrimitive.ItemIndicator>
125
+ </span>
126
+ {children}
127
+ </DropdownMenuPrimitive.CheckboxItem>
128
+ ));
129
+ DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
130
+
131
+ const DropdownMenuRadioItem = React.forwardRef<
132
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
133
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
134
+ >(({ className, children, ...props }, ref) => (
135
+ <DropdownMenuPrimitive.RadioItem
136
+ ref={ref}
137
+ className={cn(
138
+ 'relative flex cursor-default items-center rounded-full py-1 pr-2 pl-7 text-sm transition-colors outline-none select-none',
139
+ 'hover:bg-default/50 focus:bg-default/50 dark:hover:bg-white/5 dark:focus:bg-white/5 data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
140
+ className
141
+ )}
142
+ {...props}
143
+ >
144
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
145
+ <DropdownMenuPrimitive.ItemIndicator>
146
+ <Icon icon="gravity-ui:circle-fill" className="size-2" />
147
+ </DropdownMenuPrimitive.ItemIndicator>
148
+ </span>
149
+ {children}
150
+ </DropdownMenuPrimitive.RadioItem>
151
+ ));
152
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
153
+
154
+ const DropdownMenuLabel = React.forwardRef<
155
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
156
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
157
+ inset?: boolean;
158
+ }
159
+ >(({ className, inset, ...props }, ref) => (
160
+ <DropdownMenuPrimitive.Label
161
+ ref={ref}
162
+ className={cn('px-2 py-1.5 text-xs font-semibold text-muted-foreground', inset && 'pl-8', className)}
163
+ {...props}
164
+ />
165
+ ));
166
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
167
+
168
+ const DropdownMenuSeparator = React.forwardRef<
169
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
170
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
171
+ >(({ className, ...props }, ref) => (
172
+ <DropdownMenuPrimitive.Separator ref={ref} className={cn('bg-divider mx-2 my-1 h-px', className)} {...props} />
173
+ ));
174
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
175
+
176
+ const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
177
+ return <span className={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...props} />;
178
+ };
179
+ DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
180
+
181
+ export {
182
+ DropdownMenu,
183
+ DropdownMenuTrigger,
184
+ DropdownMenuContent,
185
+ DropdownMenuItem,
186
+ DropdownMenuCheckboxItem,
187
+ DropdownMenuRadioItem,
188
+ DropdownMenuLabel,
189
+ DropdownMenuSeparator,
190
+ DropdownMenuShortcut,
191
+ DropdownMenuGroup,
192
+ DropdownMenuPortal,
193
+ DropdownMenuSub,
194
+ DropdownMenuSubContent,
195
+ DropdownMenuSubTrigger,
196
+ DropdownMenuRadioGroup,
197
+ };
@@ -0,0 +1,73 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { cn } from '../lib/utils';
5
+
6
+ export interface FieldsetProps extends React.FieldsetHTMLAttributes<HTMLFieldSetElement> {}
7
+
8
+ /**
9
+ * Fieldset - A semantic grouping element for form controls.
10
+ *
11
+ * Use this to group related form elements together with an optional legend.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * <Fieldset>
16
+ * <legend className="text-sm font-medium">Personal Info</legend>
17
+ * <TextField>
18
+ * <Label>Name</Label>
19
+ * <Input placeholder="Enter name..." />
20
+ * </TextField>
21
+ * </Fieldset>
22
+ * ```
23
+ */
24
+ const Fieldset = React.forwardRef<HTMLFieldSetElement, FieldsetProps>(
25
+ ({ className, disabled, children, ...props }, ref) => {
26
+ return (
27
+ <fieldset
28
+ ref={ref}
29
+ disabled={disabled}
30
+ className={cn(
31
+ 'flex flex-col gap-3',
32
+ // Disabled styles
33
+ disabled && 'opacity-60 cursor-not-allowed',
34
+ className
35
+ )}
36
+ {...props}
37
+ >
38
+ {children}
39
+ </fieldset>
40
+ );
41
+ }
42
+ );
43
+ Fieldset.displayName = 'Fieldset';
44
+
45
+ export interface LegendProps extends React.HTMLAttributes<HTMLLegendElement> {}
46
+
47
+ /**
48
+ * Legend - A caption for a fieldset.
49
+ *
50
+ * @example
51
+ * ```tsx
52
+ * <Fieldset>
53
+ * <Legend>Account Settings</Legend>
54
+ * ...
55
+ * </Fieldset>
56
+ * ```
57
+ */
58
+ const Legend = React.forwardRef<HTMLLegendElement, LegendProps>(
59
+ ({ className, children, ...props }, ref) => {
60
+ return (
61
+ <legend
62
+ ref={ref}
63
+ className={cn('text-base text-foreground mb-3 font-heading', className)}
64
+ {...props}
65
+ >
66
+ {children}
67
+ </legend>
68
+ );
69
+ }
70
+ );
71
+ Legend.displayName = 'Legend';
72
+
73
+ export { Fieldset, Legend };
@@ -0,0 +1,138 @@
1
+ // Button
2
+ export { Button, buttonVariants } from './Button';
3
+ export type { ButtonProps } from './Button';
4
+
5
+ // Label
6
+ export { Label } from './label';
7
+
8
+ // Tooltip
9
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider, TooltipRoot } from './tooltip';
10
+
11
+ // Slider
12
+ export { Slider } from './slider';
13
+
14
+ // Switch
15
+ export { Switch } from './switch';
16
+
17
+ // Popover
18
+ export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverRoot } from './popover';
19
+
20
+ // Tabs
21
+ export { Tabs, TabsList, TabsTrigger, TabsContent, TabsRoot } from './tabs';
22
+
23
+ // RadioGroup
24
+ export { RadioGroup, RadioGroupItem, Radio } from './radio-group';
25
+
26
+ // Collapsible (and Disclosure aliases)
27
+ export {
28
+ Collapsible,
29
+ CollapsibleTrigger,
30
+ CollapsibleContent,
31
+ CollapsibleRoot,
32
+ Disclosure,
33
+ DisclosureTrigger,
34
+ DisclosureContent,
35
+ } from './collapsible';
36
+
37
+ // Accordion
38
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from './accordion';
39
+
40
+ // DropdownMenu
41
+ export {
42
+ DropdownMenu,
43
+ DropdownMenuTrigger,
44
+ DropdownMenuContent,
45
+ DropdownMenuItem,
46
+ DropdownMenuCheckboxItem,
47
+ DropdownMenuRadioItem,
48
+ DropdownMenuLabel,
49
+ DropdownMenuSeparator,
50
+ DropdownMenuShortcut,
51
+ DropdownMenuGroup,
52
+ DropdownMenuPortal,
53
+ DropdownMenuSub,
54
+ DropdownMenuSubContent,
55
+ DropdownMenuSubTrigger,
56
+ DropdownMenuRadioGroup,
57
+ } from './dropdown-menu';
58
+
59
+ // Select
60
+ export {
61
+ Select,
62
+ SelectGroup,
63
+ SelectValue,
64
+ SelectTrigger,
65
+ SelectContent,
66
+ SelectLabel,
67
+ SelectItem,
68
+ SelectSeparator,
69
+ SelectScrollUpButton,
70
+ SelectScrollDownButton,
71
+ } from './select';
72
+
73
+ // Drawer - Mobile-optimized drawer component (works on all platforms including iOS Safari)
74
+ export {
75
+ // Primary exports
76
+ Drawer,
77
+ DrawerTrigger,
78
+ DrawerClose,
79
+ DrawerContent,
80
+ DrawerHeader,
81
+ DrawerFooter,
82
+ DrawerTitle,
83
+ DrawerDescription,
84
+ // Legacy IOS-prefixed names (for backwards compatibility)
85
+ IOSDrawer,
86
+ IOSDrawerTrigger,
87
+ IOSDrawerClose,
88
+ IOSDrawerContent,
89
+ IOSDrawerHeader,
90
+ IOSDrawerFooter,
91
+ IOSDrawerTitle,
92
+ IOSDrawerDescription,
93
+ } from './drawer';
94
+
95
+ // Input
96
+ export { Input } from './input';
97
+ export type { InputProps } from './input';
98
+
99
+ // Textarea (with HeroUI TextArea alias)
100
+ export { Textarea, TextArea } from './textarea';
101
+ export type { TextareaProps } from './textarea';
102
+
103
+ // Checkbox
104
+ export { Checkbox } from './checkbox';
105
+
106
+ // Card
107
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from './card';
108
+
109
+ // Separator
110
+ export { Separator } from './separator';
111
+
112
+ // Badge (HeroUI Chip replacement)
113
+ export { Badge, Chip, badgeVariants } from './badge';
114
+ export type { BadgeProps } from './badge';
115
+
116
+ // Spinner
117
+ export { Spinner, spinnerVariants } from './spinner';
118
+ export type { SpinnerProps } from './spinner';
119
+
120
+ // Surface
121
+ export { Surface, surfaceVariants, useSurface } from './surface';
122
+ export type { SurfaceProps } from './surface';
123
+
124
+ // TextField (Input + Label wrapper for HeroUI compatibility)
125
+ export { TextField } from './text-field';
126
+ export type { TextFieldProps } from './text-field';
127
+
128
+ // Kbd (keyboard shortcut display)
129
+ export { Kbd, kbdVariants } from './kbd';
130
+ export type { KbdProps } from './kbd';
131
+
132
+ // Fieldset
133
+ export { Fieldset, Legend } from './fieldset';
134
+ export type { FieldsetProps, LegendProps } from './fieldset';
135
+
136
+ // Link
137
+ export { Link, LinkIcon, linkVariants } from './link';
138
+ export type { LinkProps } from './link';
@@ -0,0 +1,91 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { cn } from '../lib/utils';
5
+ import { useSurface } from './surface';
6
+
7
+ export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
8
+ /** Whether the input is in an invalid state */
9
+ isInvalid?: boolean;
10
+ /** Content to display at the start of the input */
11
+ startContent?: React.ReactNode;
12
+ /** Content to display at the end of the input */
13
+ endContent?: React.ReactNode;
14
+ /**
15
+ * Visual variant - auto-detected from Surface context, or can be set explicitly
16
+ * - `default`: Standard field styling for forms (bg-field)
17
+ * - `filled`: Field on elevated surface (bg-field-on-surface)
18
+ * - `subtle`: Blends with buttons/tabs in toolbars (bg-default)
19
+ */
20
+ variant?: 'default' | 'filled' | 'subtle';
21
+ }
22
+
23
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
24
+ (
25
+ {
26
+ className,
27
+ type,
28
+ isInvalid,
29
+ startContent,
30
+ endContent,
31
+ disabled,
32
+ readOnly,
33
+ required,
34
+ variant,
35
+ ...props
36
+ },
37
+ ref
38
+ ) => {
39
+ // Auto-detect variant from Surface context
40
+ const surface = useSurface();
41
+ const isOnSurface = surface.variant === 'default' || surface.variant === 'secondary';
42
+ const effectiveVariant = variant ?? (isOnSurface ? 'filled' : 'default');
43
+
44
+ const inputElement = (
45
+ <input
46
+ type={type}
47
+ className={cn(
48
+ 'flex h-10 w-full rounded-input px-2.5 py-1.5 text-sm appearance-none',
49
+ 'text-foreground border-none',
50
+ 'file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground',
51
+ 'placeholder:text-foreground/50',
52
+ 'transition-shadow duration-200 ease-out focus-visible:outline-none focus:ring-2 focus:ring-focus focus:ring-offset-1 focus:ring-offset-background',
53
+ 'disabled:cursor-not-allowed disabled:opacity-50',
54
+ // Variant styles - semantic: field on page vs field on surface vs toolbar
55
+ effectiveVariant === 'default' && 'bg-field shadow-soft dark:shadow-none',
56
+ effectiveVariant === 'filled' && 'bg-[var(--color-field-on-surface)]',
57
+ effectiveVariant === 'subtle' && 'bg-default dark:bg-[var(--color-default-on-surface)]',
58
+ isInvalid && 'ring-2 ring-danger focus-visible:ring-danger',
59
+ startContent && 'pl-10',
60
+ endContent && 'pr-10',
61
+ className
62
+ )}
63
+ ref={ref}
64
+ disabled={disabled}
65
+ readOnly={readOnly}
66
+ required={required}
67
+ aria-invalid={isInvalid}
68
+ {...props}
69
+ />
70
+ );
71
+
72
+ if (startContent || endContent) {
73
+ return (
74
+ <div className="relative">
75
+ {startContent && (
76
+ <div className="absolute left-3 top-1/2 -translate-y-1/2 text-foreground opacity-40">{startContent}</div>
77
+ )}
78
+ {inputElement}
79
+ {endContent && (
80
+ <div className="absolute right-3 top-1/2 -translate-y-1/2 text-foreground opacity-40">{endContent}</div>
81
+ )}
82
+ </div>
83
+ );
84
+ }
85
+
86
+ return inputElement;
87
+ }
88
+ );
89
+ Input.displayName = 'Input';
90
+
91
+ export { Input };
@@ -0,0 +1,130 @@
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 kbdVariants = cva(
8
+ 'inline-flex items-center justify-center gap-1 rounded font-mono text-xs font-medium',
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ 'border border-divider bg-default/50 px-1.5 py-0.5 text-foreground/80 shadow-[0_1px_0_1px] shadow-black/10 dark:shadow-black/30',
14
+ minimal: 'bg-default/30 px-1 py-0.5 text-foreground/60',
15
+ outline: 'border border-divider px-1.5 py-0.5 text-foreground/70',
16
+ },
17
+ size: {
18
+ sm: 'h-4 min-w-[1rem] text-[10px]',
19
+ md: 'h-5 min-w-[1.25rem] text-xs',
20
+ lg: 'h-6 min-w-[1.5rem] text-sm',
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ variant: 'default',
25
+ size: 'md',
26
+ },
27
+ }
28
+ );
29
+
30
+ export interface KbdProps
31
+ extends React.HTMLAttributes<HTMLElement>,
32
+ VariantProps<typeof kbdVariants> {
33
+ /**
34
+ * The keyboard key(s) to display.
35
+ * Can use special values that will be transformed:
36
+ * - "cmd" or "command" -> displays platform-specific modifier (⌘ on Mac, Ctrl on Windows)
37
+ * - "ctrl" -> ⌃
38
+ * - "alt" or "option" -> ⌥
39
+ * - "shift" -> ⇧
40
+ * - "enter" or "return" -> ↵
41
+ * - "backspace" or "delete" -> ⌫
42
+ * - "escape" or "esc" -> Esc
43
+ * - "tab" -> ⇥
44
+ * - "space" -> Space
45
+ * - "up", "down", "left", "right" -> arrows
46
+ */
47
+ keys?: string | string[];
48
+ }
49
+
50
+ /**
51
+ * Map of special key names to their display symbols
52
+ */
53
+ const keySymbols: Record<string, string> = {
54
+ cmd: '⌘',
55
+ command: '⌘',
56
+ ctrl: '⌃',
57
+ control: '⌃',
58
+ alt: '⌥',
59
+ option: '⌥',
60
+ shift: '⇧',
61
+ enter: '↵',
62
+ return: '↵',
63
+ backspace: '⌫',
64
+ delete: '⌫',
65
+ escape: 'Esc',
66
+ esc: 'Esc',
67
+ tab: '⇥',
68
+ space: '␣',
69
+ up: '↑',
70
+ down: '↓',
71
+ left: '←',
72
+ right: '→',
73
+ };
74
+
75
+ /**
76
+ * Transforms a key name to its display symbol
77
+ */
78
+ function formatKey(key: string): string {
79
+ const lowerKey = key.toLowerCase();
80
+ if (keySymbols[lowerKey]) {
81
+ return keySymbols[lowerKey];
82
+ }
83
+ // Single letters should be uppercase
84
+ if (key.length === 1) {
85
+ return key.toUpperCase();
86
+ }
87
+ return key;
88
+ }
89
+
90
+ /**
91
+ * Kbd - Keyboard key display component.
92
+ *
93
+ * Displays keyboard shortcuts in a visually consistent way.
94
+ *
95
+ * @example
96
+ * ```tsx
97
+ * <Kbd>K</Kbd>
98
+ * <Kbd keys={['cmd', 'shift', 'P']} />
99
+ * <Kbd keys="cmd+K" />
100
+ * ```
101
+ */
102
+ const Kbd = React.forwardRef<HTMLElement, KbdProps>(
103
+ ({ className, variant, size, keys, children, ...props }, ref) => {
104
+ // Parse keys if provided as a string with "+" separator
105
+ let parsedKeys: string[] = [];
106
+ if (keys) {
107
+ if (Array.isArray(keys)) {
108
+ parsedKeys = keys;
109
+ } else if (typeof keys === 'string') {
110
+ parsedKeys = keys.split('+').map((k) => k.trim());
111
+ }
112
+ }
113
+
114
+ return (
115
+ <kbd ref={ref} className={cn(kbdVariants({ variant, size }), className)} {...props}>
116
+ {parsedKeys.length > 0
117
+ ? parsedKeys.map((key, index) => (
118
+ <React.Fragment key={index}>
119
+ {index > 0 && <span className="text-foreground/40">+</span>}
120
+ <span>{formatKey(key)}</span>
121
+ </React.Fragment>
122
+ ))
123
+ : children}
124
+ </kbd>
125
+ );
126
+ }
127
+ );
128
+ Kbd.displayName = 'Kbd';
129
+
130
+ export { Kbd, kbdVariants };
@@ -0,0 +1,20 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as LabelPrimitive from '@radix-ui/react-label';
5
+ import { cva, type VariantProps } from 'class-variance-authority';
6
+ import { cn } from '../lib/utils';
7
+
8
+ const labelVariants = cva(
9
+ 'block text-sm leading-none text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70 font-label'
10
+ );
11
+
12
+ const Label = React.forwardRef<
13
+ React.ElementRef<typeof LabelPrimitive.Root>,
14
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
15
+ >(({ className, ...props }, ref) => (
16
+ <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
17
+ ));
18
+ Label.displayName = LabelPrimitive.Root.displayName;
19
+
20
+ export { Label };