@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,206 @@
1
+ "use client";
2
+
3
+ 'use client';
4
+
5
+ import * as React from 'react';
6
+ import * as TabsPrimitive from '@radix-ui/react-tabs';
7
+ import { cn } from '../lib/utils';
8
+ import { useSurface } from './surface';
9
+
10
+ interface TabsProps
11
+ extends Omit<React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>, 'value' | 'onValueChange'> {
12
+ value?: string;
13
+ onValueChange?: (value: string) => void;
14
+ }
15
+
16
+ const TabsRoot = React.forwardRef<React.ElementRef<typeof TabsPrimitive.Root>, TabsProps>(
17
+ ({ value, onValueChange, ...props }, ref) => (
18
+ <TabsPrimitive.Root ref={ref} value={value} onValueChange={onValueChange} {...props} />
19
+ )
20
+ );
21
+ TabsRoot.displayName = 'Tabs';
22
+
23
+ const TabsList = React.forwardRef<
24
+ React.ElementRef<typeof TabsPrimitive.List>,
25
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
26
+ >(({ className, children, ...props }, ref) => {
27
+ const listRef = React.useRef<HTMLDivElement>(null);
28
+ const [indicatorStyle, setIndicatorStyle] = React.useState<React.CSSProperties>({
29
+ opacity: 0,
30
+ });
31
+
32
+ // Update indicator position when active tab changes
33
+ React.useEffect(() => {
34
+ const listElement = listRef.current;
35
+ if (!listElement) return;
36
+
37
+ const updateIndicator = () => {
38
+ const activeTab = listElement.querySelector('[data-state="active"]') as HTMLElement;
39
+ if (activeTab) {
40
+ const listRect = listElement.getBoundingClientRect();
41
+ const tabRect = activeTab.getBoundingClientRect();
42
+
43
+ setIndicatorStyle({
44
+ width: tabRect.width,
45
+ height: tabRect.height,
46
+ left: tabRect.left - listRect.left,
47
+ opacity: 1,
48
+ });
49
+ }
50
+ };
51
+
52
+ // Initial position
53
+ updateIndicator();
54
+
55
+ // Use MutationObserver to detect when active tab changes
56
+ const observer = new MutationObserver((mutations) => {
57
+ for (const mutation of mutations) {
58
+ if (mutation.type === 'attributes' && mutation.attributeName === 'data-state') {
59
+ updateIndicator();
60
+ break;
61
+ }
62
+ }
63
+ });
64
+
65
+ // Observe all trigger elements for data-state changes
66
+ const triggers = listElement.querySelectorAll('[role="tab"]');
67
+ triggers.forEach((trigger) => {
68
+ observer.observe(trigger, { attributes: true, attributeFilter: ['data-state'] });
69
+ });
70
+
71
+ // Also update on resize
72
+ const resizeObserver = new ResizeObserver(updateIndicator);
73
+ resizeObserver.observe(listElement);
74
+
75
+ return () => {
76
+ observer.disconnect();
77
+ resizeObserver.disconnect();
78
+ };
79
+ }, [children]);
80
+
81
+ return (
82
+ <TabsPrimitive.List
83
+ ref={(node) => {
84
+ // Handle both refs
85
+ (listRef as React.MutableRefObject<HTMLDivElement | null>).current = node;
86
+ if (typeof ref === 'function') {
87
+ ref(node);
88
+ } else if (ref) {
89
+ ref.current = node;
90
+ }
91
+ }}
92
+ className={cn(
93
+ 'relative inline-flex h-10 items-center justify-center rounded-full px-1 text-muted-foreground',
94
+ // Consistent bg-default matches Button secondary variant for toolbar harmony
95
+ 'bg-default dark:bg-[var(--color-default-on-surface)]',
96
+ className
97
+ )}
98
+ {...props}
99
+ >
100
+ {/* Animated sliding indicator. In dark mode the previous `bg-muted`
101
+ value resolved to the same color as `bg-default` on the container,
102
+ so the active tab was invisible without relying purely on
103
+ shadow-md. `bg-popover` lands ~0.08 lightness above `bg-default`
104
+ in dark mode for a clearly visible elevation. */}
105
+ <span
106
+ className="absolute top-1/2 -translate-y-1/2 rounded-full bg-surface dark:bg-popover shadow-md transition-[left,width,opacity]"
107
+ style={{
108
+ ...indicatorStyle,
109
+ transitionDuration: '350ms',
110
+ transitionTimingFunction: 'cubic-bezier(0.22, 1, 0.36, 1)',
111
+ }}
112
+ aria-hidden="true"
113
+ />
114
+ {children}
115
+ </TabsPrimitive.List>
116
+ );
117
+ });
118
+ TabsList.displayName = TabsPrimitive.List.displayName;
119
+
120
+ interface TabsTriggerProps extends Omit<React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>, 'value'> {
121
+ // HeroUI uses 'id' for tab identification, Radix uses 'value'
122
+ // At least one must be provided
123
+ id?: string;
124
+ value?: string;
125
+ }
126
+
127
+ const TabsTrigger = React.forwardRef<React.ElementRef<typeof TabsPrimitive.Trigger>, TabsTriggerProps>(
128
+ ({ className, id, value, ...props }, ref) => {
129
+ const { variant: surfaceVariant } = useSurface();
130
+ const isOnSurface = surfaceVariant === 'default' || surfaceVariant === 'secondary';
131
+
132
+ return (
133
+ <TabsPrimitive.Trigger
134
+ ref={ref}
135
+ value={id ?? value ?? ''}
136
+ className={cn(
137
+ 'relative z-10 inline-flex h-8 items-center justify-center gap-2 whitespace-nowrap rounded-full px-4 text-sm font-label',
138
+ 'transition-[color,opacity]',
139
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus focus-visible:ring-offset-2',
140
+ 'disabled:pointer-events-none disabled:opacity-50',
141
+ 'data-[state=active]:text-foreground data-[state=active]:opacity-100',
142
+ // Inactive tabs use element opacity (not color opacity) to avoid SVG path overlap issues
143
+ isOnSurface
144
+ ? 'data-[state=inactive]:text-foreground data-[state=inactive]:opacity-30 data-[state=inactive]:hover:opacity-50'
145
+ : 'data-[state=inactive]:text-foreground data-[state=inactive]:opacity-50 data-[state=inactive]:hover:opacity-70',
146
+ className
147
+ )}
148
+ {...props}
149
+ />
150
+ );
151
+ }
152
+ );
153
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
154
+
155
+ interface TabsContentProps extends Omit<React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>, 'value'> {
156
+ // HeroUI uses 'id' for panel identification, Radix uses 'value'
157
+ // At least one must be provided
158
+ id?: string;
159
+ value?: string;
160
+ }
161
+
162
+ const TabsContent = React.forwardRef<React.ElementRef<typeof TabsPrimitive.Content>, TabsContentProps>(
163
+ ({ className, id, value, ...props }, ref) => (
164
+ <TabsPrimitive.Content
165
+ ref={ref}
166
+ value={id ?? value ?? ''}
167
+ className={cn(
168
+ 'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus focus-visible:ring-offset-2',
169
+ className
170
+ )}
171
+ {...props}
172
+ />
173
+ )
174
+ );
175
+ TabsContent.displayName = TabsPrimitive.Content.displayName;
176
+
177
+ // HeroUI ListContainer - wrapper for the tabs list (passthrough div)
178
+ const TabsListContainer = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
179
+ ({ className, ...props }, ref) => <div ref={ref} className={className} {...props} />
180
+ );
181
+ TabsListContainer.displayName = 'TabsListContainer';
182
+
183
+ // HeroUI Tab - alias for TabsTrigger
184
+ const TabsTab = TabsTrigger;
185
+
186
+ // HeroUI Panel - alias for TabsContent
187
+ const TabsPanel = TabsContent;
188
+
189
+ // HeroUI Indicator - now rendered automatically by TabsList, this is a no-op
190
+ const TabsIndicator = () => null;
191
+
192
+ // Create compound component with all aliases
193
+ const TabsBase = TabsRoot;
194
+
195
+ // HeroUI-compatible compound component
196
+ const Tabs = Object.assign(TabsBase, {
197
+ List: TabsList,
198
+ ListContainer: TabsListContainer,
199
+ Tab: TabsTab,
200
+ Trigger: TabsTrigger,
201
+ Panel: TabsPanel,
202
+ Content: TabsContent,
203
+ Indicator: TabsIndicator,
204
+ });
205
+
206
+ export { Tabs, TabsList, TabsTrigger, TabsContent, TabsRoot };
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { cn } from '../lib/utils';
5
+
6
+ export interface TextFieldProps extends React.HTMLAttributes<HTMLDivElement> {
7
+ /**
8
+ * Accessible label for the input.
9
+ * If not provided via children, use aria-label on the div.
10
+ */
11
+ 'aria-label'?: string;
12
+ /**
13
+ * Error state - applies error styling to children inputs
14
+ */
15
+ isInvalid?: boolean;
16
+ /**
17
+ * Disabled state - disables all children inputs
18
+ */
19
+ disabled?: boolean;
20
+ /**
21
+ * Required state - marks all children inputs as required
22
+ */
23
+ required?: boolean;
24
+ }
25
+
26
+ /**
27
+ * TextField - A wrapper component that groups Label and Input together.
28
+ *
29
+ * Groups Label and Input with a convenient wrapper that can propagate
30
+ * disabled, required, and invalid states to child inputs.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * <TextField>
35
+ * <Label>Email</Label>
36
+ * <Input type="email" placeholder="Enter email..." />
37
+ * </TextField>
38
+ * ```
39
+ */
40
+ const TextField = React.forwardRef<HTMLDivElement, TextFieldProps>(
41
+ ({ className, children, isInvalid, disabled, required, ...props }, ref) => {
42
+ // Clone children to pass through state props to Input/Textarea children
43
+ const enhancedChildren = React.Children.map(children, (child) => {
44
+ if (!React.isValidElement(child)) return child;
45
+
46
+ // Get the display name or type name to check if it's an input component
47
+ const childType = child.type;
48
+ const displayName =
49
+ typeof childType === 'function'
50
+ ? (childType as React.ComponentType).displayName || childType.name
51
+ : typeof childType === 'string'
52
+ ? childType
53
+ : '';
54
+
55
+ // Check if this is an input-like element that should receive state props
56
+ const isInputElement =
57
+ displayName === 'Input' ||
58
+ displayName === 'Textarea' ||
59
+ displayName === 'TextArea' ||
60
+ displayName === 'input' ||
61
+ displayName === 'textarea';
62
+
63
+ if (isInputElement) {
64
+ const childProps = child.props as Record<string, unknown>;
65
+ return React.cloneElement(child as React.ReactElement<Record<string, unknown>>, {
66
+ isInvalid: (childProps.isInvalid as boolean) ?? isInvalid,
67
+ disabled: (childProps.disabled as boolean) ?? disabled,
68
+ required: (childProps.required as boolean) ?? required,
69
+ });
70
+ }
71
+
72
+ return child;
73
+ });
74
+
75
+ return (
76
+ <div ref={ref} className={cn('flex flex-col gap-1.5', className)} {...props}>
77
+ {enhancedChildren}
78
+ </div>
79
+ );
80
+ }
81
+ );
82
+ TextField.displayName = 'TextField';
83
+
84
+ export { TextField };
@@ -0,0 +1,50 @@
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 TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
8
+ /** Whether the textarea is in an invalid state */
9
+ isInvalid?: boolean;
10
+ /** Visual variant - auto-detected from Surface context, or can be set explicitly */
11
+ variant?: 'default' | 'filled';
12
+ }
13
+
14
+ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
15
+ ({ className, isInvalid, disabled, readOnly, required, variant, ...props }, ref) => {
16
+ // Auto-detect variant from Surface context
17
+ const surface = useSurface();
18
+ const effectiveVariant = variant ?? (surface.variant === 'default' ? 'filled' : 'default');
19
+
20
+ return (
21
+ <textarea
22
+ className={cn(
23
+ 'flex min-h-[80px] w-full rounded-input px-2.5 py-2 text-sm transition-colors',
24
+ 'text-foreground border-none',
25
+ 'placeholder:text-foreground/50',
26
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus',
27
+ 'disabled:cursor-not-allowed disabled:opacity-50',
28
+ // Variant styles - semantic: field on page vs field on surface
29
+ effectiveVariant === 'default' && 'bg-field shadow-soft dark:shadow-none',
30
+ effectiveVariant === 'filled' && 'bg-[var(--color-field-on-surface)]',
31
+ isInvalid && 'ring-2 ring-danger focus-visible:ring-danger',
32
+ className
33
+ )}
34
+ ref={ref}
35
+ disabled={disabled}
36
+ readOnly={readOnly}
37
+ required={required}
38
+ aria-invalid={isInvalid}
39
+ {...props}
40
+ />
41
+ );
42
+ }
43
+ );
44
+ Textarea.displayName = 'Textarea';
45
+
46
+ // Alias for HeroUI compatibility (HeroUI uses TextArea, Shadcn uses Textarea)
47
+ const TextArea = Textarea;
48
+ TextArea.displayName = 'TextArea';
49
+
50
+ export { Textarea, TextArea };
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as TooltipPrimitive from '@radix-ui/react-tooltip';
5
+ import { cn } from '../lib/utils';
6
+
7
+ const TooltipProvider = TooltipPrimitive.Provider;
8
+
9
+ const TooltipRoot = TooltipPrimitive.Root;
10
+
11
+ const TooltipTrigger = TooltipPrimitive.Trigger;
12
+
13
+ const TooltipContent = React.forwardRef<
14
+ React.ElementRef<typeof TooltipPrimitive.Content>,
15
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
16
+ >(({ className, sideOffset = 4, ...props }, ref) => (
17
+ <TooltipPrimitive.Portal>
18
+ <TooltipPrimitive.Content
19
+ ref={ref}
20
+ sideOffset={sideOffset}
21
+ className={cn(
22
+ 'z-tooltip overflow-hidden rounded-tooltip bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 font-caption',
23
+ 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
24
+ 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
25
+ className
26
+ )}
27
+ {...props}
28
+ />
29
+ </TooltipPrimitive.Portal>
30
+ ));
31
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
32
+
33
+ // HeroUI-compatible compound component
34
+ interface TooltipProps {
35
+ children: React.ReactNode;
36
+ delayDuration?: number;
37
+ skipDelayDuration?: number;
38
+ disableHoverableContent?: boolean;
39
+ // HeroUI compatibility props
40
+ delay?: number;
41
+ closeDelay?: number;
42
+ }
43
+
44
+ const TooltipBase = ({ children, delayDuration, delay, ...props }: TooltipProps) => (
45
+ <TooltipProvider>
46
+ <TooltipRoot delayDuration={delay ?? delayDuration ?? 200} {...props}>
47
+ {children}
48
+ </TooltipRoot>
49
+ </TooltipProvider>
50
+ );
51
+
52
+ // HeroUI-compatible compound component with .Trigger and .Content
53
+ const Tooltip = Object.assign(TooltipBase, {
54
+ Trigger: TooltipTrigger,
55
+ Content: TooltipContent,
56
+ });
57
+
58
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider, TooltipRoot };