@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,139 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { TextField } from '../text-field';
3
+ import { Input } from '../input';
4
+ import { Label } from '../label';
5
+ import { Textarea } from '../textarea';
6
+ import { Card, CardContent, CardHeader, CardTitle } from '../card';
7
+
8
+ const meta: Meta<typeof TextField> = {
9
+ title: 'UI/TextField',
10
+ component: TextField,
11
+ parameters: {
12
+ layout: 'centered',
13
+ },
14
+ tags: ['autodocs'],
15
+ argTypes: {
16
+ isInvalid: {
17
+ control: 'boolean',
18
+ description: 'Error state - applies error styling to children inputs',
19
+ },
20
+ disabled: {
21
+ control: 'boolean',
22
+ description: 'Disabled state - disables all children inputs',
23
+ },
24
+ required: {
25
+ control: 'boolean',
26
+ description: 'Required state - marks all children inputs as required',
27
+ },
28
+ },
29
+ };
30
+
31
+ export default meta;
32
+ type Story = StoryObj<typeof meta>;
33
+
34
+ export const Default: Story = {
35
+ render: () => (
36
+ <TextField className="w-[300px]">
37
+ <Label>Email</Label>
38
+ <Input type="email" placeholder="Enter your email" />
39
+ </TextField>
40
+ ),
41
+ };
42
+
43
+ export const WithTextarea: Story = {
44
+ render: () => (
45
+ <TextField className="w-[300px]">
46
+ <Label>Description</Label>
47
+ <Textarea placeholder="Enter description..." />
48
+ </TextField>
49
+ ),
50
+ };
51
+
52
+ export const Required: Story = {
53
+ render: () => (
54
+ <TextField className="w-[300px]" required>
55
+ <Label>
56
+ Email <span className="text-danger">*</span>
57
+ </Label>
58
+ <Input type="email" placeholder="Required field" />
59
+ </TextField>
60
+ ),
61
+ };
62
+
63
+ export const Invalid: Story = {
64
+ render: () => (
65
+ <TextField className="w-[300px]" isInvalid>
66
+ <Label>Email</Label>
67
+ <Input type="email" placeholder="Invalid email" />
68
+ <p className="text-sm text-danger">Please enter a valid email address.</p>
69
+ </TextField>
70
+ ),
71
+ };
72
+
73
+ export const Disabled: Story = {
74
+ render: () => (
75
+ <TextField className="w-[300px]" disabled>
76
+ <Label>Email</Label>
77
+ <Input type="email" placeholder="Disabled input" />
78
+ </TextField>
79
+ ),
80
+ };
81
+
82
+ export const FormExample: Story = {
83
+ render: () => (
84
+ <form className="w-full max-w-sm space-y-4">
85
+ <TextField>
86
+ <Label>Full Name</Label>
87
+ <Input placeholder="John Doe" />
88
+ </TextField>
89
+ <TextField>
90
+ <Label>Email</Label>
91
+ <Input type="email" placeholder="john@example.com" />
92
+ </TextField>
93
+ <TextField>
94
+ <Label>Bio</Label>
95
+ <Textarea placeholder="Tell us about yourself..." />
96
+ </TextField>
97
+ </form>
98
+ ),
99
+ };
100
+
101
+ export const OnCard: Story = {
102
+ render: () => (
103
+ <Card className="w-[350px]">
104
+ <CardHeader>
105
+ <CardTitle>Contact Form</CardTitle>
106
+ </CardHeader>
107
+ <CardContent className="space-y-4">
108
+ <TextField>
109
+ <Label>Name</Label>
110
+ <Input placeholder="Your name" />
111
+ </TextField>
112
+ <TextField>
113
+ <Label>Message</Label>
114
+ <Textarea placeholder="Your message..." />
115
+ </TextField>
116
+ </CardContent>
117
+ </Card>
118
+ ),
119
+ };
120
+
121
+ export const StateExamples: Story = {
122
+ name: 'State Examples',
123
+ render: () => (
124
+ <div className="space-y-4 w-[300px]">
125
+ <p className="text-sm text-foreground/60 mb-4">
126
+ TextField is a wrapper component that groups Label and Input together
127
+ and passes state props to child inputs.
128
+ </p>
129
+ <TextField disabled>
130
+ <Label>Disabled via TextField</Label>
131
+ <Input placeholder="This input is disabled" />
132
+ </TextField>
133
+ <TextField isInvalid>
134
+ <Label>Invalid via TextField</Label>
135
+ <Input placeholder="This input shows error state" />
136
+ </TextField>
137
+ </div>
138
+ ),
139
+ };
@@ -0,0 +1,148 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Textarea } from '../textarea';
3
+ import { Label } from '../label';
4
+ import { Input } from '../input';
5
+ import { Card, CardContent, CardHeader, CardTitle } from '../card';
6
+
7
+ const meta: Meta<typeof Textarea> = {
8
+ title: 'UI/Textarea',
9
+ component: Textarea,
10
+ parameters: {
11
+ layout: 'centered',
12
+ },
13
+ tags: ['autodocs'],
14
+ argTypes: {
15
+ disabled: {
16
+ control: 'boolean',
17
+ description: 'Disables the textarea',
18
+ },
19
+ isInvalid: {
20
+ control: 'boolean',
21
+ description: 'Shows error state',
22
+ },
23
+ },
24
+ };
25
+
26
+ export default meta;
27
+ type Story = StoryObj<typeof meta>;
28
+
29
+ export const Default: Story = {
30
+ args: {
31
+ placeholder: 'Type your message here...',
32
+ },
33
+ decorators: [
34
+ (Story) => (
35
+ <div className="w-[350px]">
36
+ <Story />
37
+ </div>
38
+ ),
39
+ ],
40
+ };
41
+
42
+ export const WithLabel: Story = {
43
+ render: () => (
44
+ <div className="grid w-full max-w-sm gap-1.5">
45
+ <Label htmlFor="message">Your Message</Label>
46
+ <Textarea id="message" placeholder="Type your message here..." />
47
+ </div>
48
+ ),
49
+ };
50
+
51
+ export const WithText: Story = {
52
+ render: () => (
53
+ <div className="grid w-full max-w-sm gap-1.5">
54
+ <Label htmlFor="bio">Bio</Label>
55
+ <Textarea id="bio" placeholder="Tell us a little bit about yourself" />
56
+ <p className="text-sm text-muted-foreground">You can @mention other users and organizations.</p>
57
+ </div>
58
+ ),
59
+ };
60
+
61
+ export const Invalid: Story = {
62
+ render: () => (
63
+ <div className="grid w-full max-w-sm gap-1.5">
64
+ <Label htmlFor="feedback">Feedback</Label>
65
+ <Textarea id="feedback" placeholder="Your feedback" isInvalid />
66
+ <p className="text-sm text-danger">Feedback must be at least 10 characters.</p>
67
+ </div>
68
+ ),
69
+ };
70
+
71
+ export const Disabled: Story = {
72
+ args: {
73
+ placeholder: 'Disabled textarea',
74
+ disabled: true,
75
+ },
76
+ decorators: [
77
+ (Story) => (
78
+ <div className="w-[350px]">
79
+ <Story />
80
+ </div>
81
+ ),
82
+ ],
83
+ };
84
+
85
+ export const WithRows: Story = {
86
+ render: () => (
87
+ <div className="grid w-full max-w-sm gap-1.5">
88
+ <Label htmlFor="long-text">Description</Label>
89
+ <Textarea id="long-text" placeholder="Write a detailed description..." rows={8} />
90
+ </div>
91
+ ),
92
+ };
93
+
94
+ export const ReadOnly: Story = {
95
+ render: () => (
96
+ <div className="grid w-full max-w-sm gap-1.5">
97
+ <Label htmlFor="readonly">Terms of Service</Label>
98
+ <Textarea
99
+ id="readonly"
100
+ readOnly
101
+ rows={6}
102
+ defaultValue="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris."
103
+ />
104
+ </div>
105
+ ),
106
+ };
107
+
108
+ export const FormExample: Story = {
109
+ render: () => (
110
+ <form className="w-full max-w-md space-y-4">
111
+ <h3 className="text-lg font-heading">Contact Form</h3>
112
+ <div className="grid grid-cols-2 gap-4">
113
+ <div className="space-y-1.5">
114
+ <Label htmlFor="firstName">First Name</Label>
115
+ <Input id="firstName" placeholder="John" />
116
+ </div>
117
+ <div className="space-y-1.5">
118
+ <Label htmlFor="lastName">Last Name</Label>
119
+ <Input id="lastName" placeholder="Doe" />
120
+ </div>
121
+ </div>
122
+ <div className="space-y-1.5">
123
+ <Label htmlFor="email">Email</Label>
124
+ <Input id="email" type="email" placeholder="john@example.com" />
125
+ </div>
126
+ <div className="space-y-1.5">
127
+ <Label htmlFor="message">Message</Label>
128
+ <Textarea id="message" placeholder="How can we help you?" rows={5} />
129
+ </div>
130
+ </form>
131
+ ),
132
+ };
133
+
134
+ export const OnCard: Story = {
135
+ render: () => (
136
+ <Card className="w-[350px]">
137
+ <CardHeader>
138
+ <CardTitle>Feedback</CardTitle>
139
+ </CardHeader>
140
+ <CardContent className="space-y-4">
141
+ <div className="space-y-1.5">
142
+ <Label htmlFor="card-feedback">Your Feedback</Label>
143
+ <Textarea id="card-feedback" placeholder="Tell us what you think..." rows={4} />
144
+ </div>
145
+ </CardContent>
146
+ </Card>
147
+ ),
148
+ };
@@ -0,0 +1,119 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '../tooltip';
3
+ import { Button } from '../button';
4
+
5
+ const meta: Meta<typeof Tooltip> = {
6
+ title: 'UI/Tooltip',
7
+ component: Tooltip,
8
+ parameters: {
9
+ layout: 'centered',
10
+ },
11
+ tags: ['autodocs'],
12
+ decorators: [
13
+ (Story) => (
14
+ <TooltipProvider>
15
+ <Story />
16
+ </TooltipProvider>
17
+ ),
18
+ ],
19
+ };
20
+
21
+ export default meta;
22
+ type Story = StoryObj<typeof meta>;
23
+
24
+ export const Default: Story = {
25
+ render: () => (
26
+ <Tooltip>
27
+ <TooltipTrigger asChild>
28
+ <Button variant="secondary">Hover me</Button>
29
+ </TooltipTrigger>
30
+ <TooltipContent>
31
+ <p>This is a tooltip</p>
32
+ </TooltipContent>
33
+ </Tooltip>
34
+ ),
35
+ };
36
+
37
+ export const WithDelay: Story = {
38
+ render: () => (
39
+ <Tooltip delayDuration={500}>
40
+ <TooltipTrigger asChild>
41
+ <Button variant="secondary">500ms delay</Button>
42
+ </TooltipTrigger>
43
+ <TooltipContent>
44
+ <p>Tooltip with longer delay</p>
45
+ </TooltipContent>
46
+ </Tooltip>
47
+ ),
48
+ };
49
+
50
+ export const Positions: Story = {
51
+ render: () => (
52
+ <div className="flex items-center gap-8">
53
+ <Tooltip>
54
+ <TooltipTrigger asChild>
55
+ <Button variant="secondary">Top</Button>
56
+ </TooltipTrigger>
57
+ <TooltipContent side="top">
58
+ <p>Top tooltip</p>
59
+ </TooltipContent>
60
+ </Tooltip>
61
+
62
+ <Tooltip>
63
+ <TooltipTrigger asChild>
64
+ <Button variant="secondary">Right</Button>
65
+ </TooltipTrigger>
66
+ <TooltipContent side="right">
67
+ <p>Right tooltip</p>
68
+ </TooltipContent>
69
+ </Tooltip>
70
+
71
+ <Tooltip>
72
+ <TooltipTrigger asChild>
73
+ <Button variant="secondary">Bottom</Button>
74
+ </TooltipTrigger>
75
+ <TooltipContent side="bottom">
76
+ <p>Bottom tooltip</p>
77
+ </TooltipContent>
78
+ </Tooltip>
79
+
80
+ <Tooltip>
81
+ <TooltipTrigger asChild>
82
+ <Button variant="secondary">Left</Button>
83
+ </TooltipTrigger>
84
+ <TooltipContent side="left">
85
+ <p>Left tooltip</p>
86
+ </TooltipContent>
87
+ </Tooltip>
88
+ </div>
89
+ ),
90
+ };
91
+
92
+ export const WithRichContent: Story = {
93
+ render: () => (
94
+ <Tooltip>
95
+ <TooltipTrigger asChild>
96
+ <Button variant="secondary">Rich Content</Button>
97
+ </TooltipTrigger>
98
+ <TooltipContent className="max-w-xs">
99
+ <div className="space-y-1">
100
+ <p className="font-label">Keyboard Shortcut</p>
101
+ <p className="text-muted-foreground">Press Ctrl+S to save your work</p>
102
+ </div>
103
+ </TooltipContent>
104
+ </Tooltip>
105
+ ),
106
+ };
107
+
108
+ export const NoDelay: Story = {
109
+ render: () => (
110
+ <Tooltip delayDuration={0}>
111
+ <TooltipTrigger asChild>
112
+ <Button variant="ghost">Instant</Button>
113
+ </TooltipTrigger>
114
+ <TooltipContent>
115
+ <p>No delay tooltip</p>
116
+ </TooltipContent>
117
+ </Tooltip>
118
+ ),
119
+ };
@@ -0,0 +1,86 @@
1
+ "use client";
2
+
3
+ 'use client';
4
+
5
+ import * as React from 'react';
6
+ import { Slot } from '@radix-ui/react-slot';
7
+ import { cva, type VariantProps } from 'class-variance-authority';
8
+ import { cn } from '../lib/utils';
9
+
10
+ /**
11
+ * Surface Context
12
+ *
13
+ * Provides information about the current surface variant to child components.
14
+ * This allows form components (Input, TextArea, etc.) to automatically adapt
15
+ * their styling based on their container's surface variant.
16
+ *
17
+ * Default is 'transparent' so that form fields on the page background
18
+ * (not inside a Surface/Card) use the white/shadow style.
19
+ */
20
+ const SurfaceContext = React.createContext<{
21
+ variant: 'transparent' | 'default' | 'secondary';
22
+ }>({ variant: 'transparent' });
23
+
24
+ /**
25
+ * Hook to access the current surface context
26
+ */
27
+ export function useSurface() {
28
+ return React.useContext(SurfaceContext);
29
+ }
30
+
31
+ const surfaceVariants = cva('', {
32
+ variants: {
33
+ variant: {
34
+ transparent: 'bg-transparent',
35
+ default: 'bg-card',
36
+ secondary: 'bg-default',
37
+ },
38
+ },
39
+ defaultVariants: {
40
+ variant: 'default',
41
+ },
42
+ });
43
+
44
+ export interface SurfaceProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof surfaceVariants> {
45
+ /**
46
+ * Change the default rendered element for the one passed as a child,
47
+ * merging their props and behavior.
48
+ */
49
+ asChild?: boolean;
50
+ }
51
+
52
+ /**
53
+ * Surface Component
54
+ *
55
+ * A semantic container that provides surface-level styling and context for child components.
56
+ *
57
+ * ## Variants:
58
+ * - `transparent` - No background
59
+ * - `default` - Card/panel surface (bg-card)
60
+ * - `secondary` - Raised/muted surface (bg-default)
61
+ *
62
+ * Form fields automatically adapt their styling based on which surface they're on.
63
+ *
64
+ * @example
65
+ * ```tsx
66
+ * <Surface variant="default">
67
+ * <Input placeholder="On a card surface" />
68
+ * </Surface>
69
+ * ```
70
+ */
71
+ const Surface = React.forwardRef<HTMLDivElement, SurfaceProps>(
72
+ ({ className, variant = 'default', asChild = false, children, ...props }, ref) => {
73
+ const Comp = asChild ? Slot : 'div';
74
+
75
+ return (
76
+ <SurfaceContext.Provider value={{ variant: variant ?? 'default' }}>
77
+ <Comp ref={ref} className={cn(surfaceVariants({ variant }), className)} {...props}>
78
+ {children}
79
+ </Comp>
80
+ </SurfaceContext.Provider>
81
+ );
82
+ }
83
+ );
84
+ Surface.displayName = 'Surface';
85
+
86
+ export { Surface, surfaceVariants, SurfaceContext };
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as SwitchPrimitive from '@radix-ui/react-switch';
5
+ import { cn } from '../lib/utils';
6
+
7
+ const Switch = React.forwardRef<
8
+ React.ElementRef<typeof SwitchPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>
10
+ >(({ className, ...props }, ref) => {
11
+ return (
12
+ <SwitchPrimitive.Root
13
+ className={cn(
14
+ 'peer inline-flex h-7 w-12 md:h-5 md:w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors',
15
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus focus-visible:ring-offset-2 focus-visible:ring-offset-background',
16
+ 'disabled:cursor-not-allowed disabled:opacity-50',
17
+ 'data-[state=checked]:bg-primary data-[state=unchecked]:bg-muted',
18
+ className
19
+ )}
20
+ {...props}
21
+ ref={ref}
22
+ >
23
+ <SwitchPrimitive.Thumb
24
+ className={cn(
25
+ 'pointer-events-none block h-6 w-6 md:h-4 md:w-4 rounded-full bg-background shadow-lg ring-0 transition-transform',
26
+ 'data-[state=checked]:translate-x-5 md:data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0'
27
+ )}
28
+ />
29
+ </SwitchPrimitive.Root>
30
+ );
31
+ }
32
+ );
33
+ Switch.displayName = SwitchPrimitive.Root.displayName;
34
+
35
+ export { Switch };