@nationaldesignstudio/react 0.2.0 → 0.5.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 (97) hide show
  1. package/dist/component-registry.md +1310 -127
  2. package/dist/components/atoms/background/background.d.ts +13 -27
  3. package/dist/components/atoms/button/button.d.ts +64 -72
  4. package/dist/components/atoms/button/button.figma.d.ts +1 -0
  5. package/dist/components/atoms/button/icon-button.d.ts +62 -110
  6. package/dist/components/atoms/input/input-group.d.ts +278 -0
  7. package/dist/components/atoms/input/input.d.ts +121 -0
  8. package/dist/components/atoms/popover/popover.d.ts +195 -0
  9. package/dist/components/atoms/select/select.d.ts +131 -0
  10. package/dist/components/atoms/tooltip/tooltip.d.ts +161 -0
  11. package/dist/components/organisms/card/card.d.ts +3 -3
  12. package/dist/components/sections/hero/hero.d.ts +2 -2
  13. package/dist/components/sections/prose/prose.d.ts +3 -3
  14. package/dist/components/sections/river/river.d.ts +1 -1
  15. package/dist/components/sections/tout/tout.d.ts +4 -4
  16. package/dist/components/shared/floating-arrow.d.ts +34 -0
  17. package/dist/index.d.ts +12 -0
  18. package/dist/index.js +13935 -7622
  19. package/dist/index.js.map +1 -1
  20. package/dist/lib/form-control.d.ts +106 -0
  21. package/dist/tokens.css +4725 -19065
  22. package/package.json +2 -1
  23. package/src/components/atoms/accordion/accordion.stories.tsx +1 -1
  24. package/src/components/atoms/accordion/accordion.tsx +2 -2
  25. package/src/components/atoms/background/background.tsx +71 -109
  26. package/src/components/atoms/button/button.figma.tsx +37 -0
  27. package/src/components/atoms/button/button.stories.tsx +253 -115
  28. package/src/components/atoms/button/button.test.tsx +289 -5
  29. package/src/components/atoms/button/button.tsx +40 -101
  30. package/src/components/atoms/button/button.visual.test.tsx +28 -32
  31. package/src/components/atoms/button/icon-button.stories.tsx +44 -101
  32. package/src/components/atoms/button/icon-button.test.tsx +26 -94
  33. package/src/components/atoms/button/icon-button.tsx +81 -224
  34. package/src/components/atoms/input/index.ts +17 -0
  35. package/src/components/atoms/input/input-group.stories.tsx +646 -0
  36. package/src/components/atoms/input/input-group.test.tsx +362 -0
  37. package/src/components/atoms/input/input-group.tsx +409 -0
  38. package/src/components/atoms/input/input.stories.tsx +228 -0
  39. package/src/components/atoms/input/input.test.tsx +167 -0
  40. package/src/components/atoms/input/input.tsx +104 -0
  41. package/src/components/atoms/pager-control/pager-control.stories.tsx +6 -8
  42. package/src/components/atoms/pager-control/pager-control.tsx +12 -12
  43. package/src/components/atoms/popover/index.ts +30 -0
  44. package/src/components/atoms/popover/popover.stories.tsx +531 -0
  45. package/src/components/atoms/popover/popover.test.tsx +486 -0
  46. package/src/components/atoms/popover/popover.tsx +488 -0
  47. package/src/components/atoms/select/index.ts +18 -0
  48. package/src/components/atoms/select/select.stories.tsx +455 -0
  49. package/src/components/atoms/select/select.tsx +324 -0
  50. package/src/components/atoms/tooltip/index.ts +24 -0
  51. package/src/components/atoms/tooltip/tooltip.stories.tsx +348 -0
  52. package/src/components/atoms/tooltip/tooltip.test.tsx +363 -0
  53. package/src/components/atoms/tooltip/tooltip.tsx +347 -0
  54. package/src/components/dev-tools/dev-toolbar/dev-toolbar.stories.tsx +8 -17
  55. package/src/components/dev-tools/dev-toolbar/dev-toolbar.tsx +3 -3
  56. package/src/components/foundation/typography/typography.stories.tsx +401 -0
  57. package/src/components/organisms/card/card.stories.tsx +19 -19
  58. package/src/components/organisms/card/card.test.tsx +1 -1
  59. package/src/components/organisms/card/card.tsx +3 -3
  60. package/src/components/organisms/card/card.visual.test.tsx +11 -11
  61. package/src/components/organisms/navbar/navbar.tsx +2 -2
  62. package/src/components/organisms/navbar/navbar.visual.test.tsx +2 -2
  63. package/src/components/organisms/us-gov-banner/us-gov-banner.tsx +2 -2
  64. package/src/components/sections/banner/banner.stories.tsx +1 -5
  65. package/src/components/sections/banner/banner.test.tsx +2 -2
  66. package/src/components/sections/banner/banner.tsx +6 -6
  67. package/src/components/sections/card-grid/card-grid.tsx +5 -5
  68. package/src/components/sections/faq-section/faq-section.tsx +2 -2
  69. package/src/components/sections/hero/hero.stories.tsx +7 -7
  70. package/src/components/sections/hero/hero.test.tsx +5 -5
  71. package/src/components/sections/hero/hero.tsx +10 -11
  72. package/src/components/sections/prose/prose.test.tsx +2 -2
  73. package/src/components/sections/prose/prose.tsx +6 -7
  74. package/src/components/sections/river/river.stories.tsx +8 -8
  75. package/src/components/sections/river/river.test.tsx +4 -4
  76. package/src/components/sections/river/river.tsx +8 -16
  77. package/src/components/sections/tout/tout.stories.tsx +7 -31
  78. package/src/components/sections/tout/tout.test.tsx +1 -1
  79. package/src/components/sections/tout/tout.tsx +11 -11
  80. package/src/components/sections/two-column-section/two-column-section.tsx +7 -9
  81. package/src/components/shared/floating-arrow.tsx +78 -0
  82. package/src/components/shared/index.ts +5 -0
  83. package/src/index.ts +98 -0
  84. package/src/lib/form-control.ts +71 -0
  85. package/src/stories/grid-system.stories.tsx +309 -0
  86. package/src/stories/{Introduction.mdx → introduction.mdx} +29 -15
  87. package/src/stories/{ThemeProvider.stories.tsx → theme-provider.stories.tsx} +8 -22
  88. package/src/stories/{TokenShowcase.stories.tsx → token-showcase.stories.tsx} +1 -20
  89. package/src/stories/token-showcase.tsx +777 -0
  90. package/src/styles.css +3 -0
  91. package/src/tests/token-resolution.test.tsx +298 -0
  92. package/src/theme/hooks.ts +1 -1
  93. package/src/theme/index.ts +1 -1
  94. package/src/theme/theme-provider.test.tsx +270 -0
  95. package/src/theme/{ThemeProvider.tsx → theme-provider.tsx} +18 -2
  96. package/src/stories/GridSystem.stories.tsx +0 -84
  97. package/src/stories/TokenShowcase.tsx +0 -1429
@@ -0,0 +1,324 @@
1
+ "use client";
2
+
3
+ import { Select as BaseSelect } from "@base-ui-components/react/select";
4
+ import * as React from "react";
5
+ import { tv, type VariantProps } from "tailwind-variants";
6
+ import {
7
+ formControlBase,
8
+ formControlError,
9
+ formControlSizes,
10
+ } from "@/lib/form-control";
11
+ import { cn } from "@/lib/utils";
12
+
13
+ /**
14
+ * Select trigger variants based on Figma BaseKit / Interface / Dropdown
15
+ *
16
+ * States:
17
+ * - Default: White background, subtle border
18
+ * - Hover: Light gray background
19
+ * - Focus/Open: Accent border with focus ring
20
+ * - Selected: Has a value selected (darker text)
21
+ * - Disabled: Reduced opacity, not interactive
22
+ */
23
+ const selectTriggerVariants = tv({
24
+ base: [
25
+ ...formControlBase,
26
+ // Select-specific styles
27
+ "justify-between cursor-pointer",
28
+ // Override disabled to use data attribute (Base UI pattern)
29
+ "data-[disabled]:bg-ui-control-background-disabled data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50",
30
+ // Open state styling
31
+ "data-[popup-open]:border-ui-accent-base data-[popup-open]:ring-4 data-[popup-open]:ring-ui-color-focus data-[popup-open]:bg-ui-control-background",
32
+ ],
33
+ variants: {
34
+ size: formControlSizes,
35
+ error: formControlError,
36
+ },
37
+ defaultVariants: {
38
+ size: "default",
39
+ error: false,
40
+ },
41
+ });
42
+
43
+ /**
44
+ * Select popup/menu variants
45
+ */
46
+ const selectPopupVariants = tv({
47
+ base: [
48
+ // Layout - match trigger width using CSS custom property from Base UI
49
+ "flex flex-col gap-2 p-10",
50
+ "w-[var(--anchor-width)]",
51
+ // Background and border - uses surface ui radius for theming support
52
+ "bg-ui-control-background border border-solid border-ui-color-border-active rounded-surface-ui-medium",
53
+ // Focus ring shadow (ui-focus-state from Figma)
54
+ "ring-4 ring-ui-color-focus",
55
+ // Animation
56
+ "origin-[var(--transform-origin)]",
57
+ "transition-[transform,scale,opacity] duration-150",
58
+ "data-[starting-style]:scale-95 data-[starting-style]:opacity-0",
59
+ "data-[ending-style]:scale-95 data-[ending-style]:opacity-0",
60
+ // Ensure it's above other content
61
+ "z-50",
62
+ ],
63
+ });
64
+
65
+ /**
66
+ * Select option/item variants based on Figma Menu Items
67
+ *
68
+ * States:
69
+ * - Default: White background
70
+ * - Hover/Highlighted: Light indigo tint background
71
+ * - Selected: Stronger indigo tint with blue text and checkmark
72
+ * - Disabled: Reduced opacity
73
+ */
74
+ const selectOptionVariants = tv({
75
+ base: [
76
+ // Layout
77
+ "flex items-center justify-between px-12 py-8 h-36",
78
+ // Typography - use semantic tokens
79
+ "typography-body-md-md font-medium text-ui-menu-item-text",
80
+ // Background - default
81
+ "bg-ui-menu-item-bg",
82
+ // Border radius - uses surface ui radius for theming support
83
+ "rounded-surface-ui-medium",
84
+ // Cursor
85
+ "cursor-pointer outline-none",
86
+ // Transitions
87
+ "transition-colors duration-150",
88
+ // Hover/highlighted state - use semantic token
89
+ "data-[highlighted]:bg-ui-menu-item-bg-hover",
90
+ // Selected state - use semantic tokens
91
+ "data-[selected]:bg-ui-menu-item-bg-selected data-[selected]:text-ui-menu-item-text-selected",
92
+ // Disabled state
93
+ "data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed",
94
+ ],
95
+ });
96
+
97
+ /**
98
+ * Chevron icon for the select trigger
99
+ */
100
+ const SelectChevronIcon = ({ className }: { className?: string }) => (
101
+ <svg
102
+ className={cn("size-16 text-gray-500 shrink-0", className)}
103
+ viewBox="0 0 16 16"
104
+ fill="none"
105
+ xmlns="http://www.w3.org/2000/svg"
106
+ aria-hidden="true"
107
+ >
108
+ <path
109
+ d="M4 6L8 10L12 6"
110
+ stroke="currentColor"
111
+ strokeWidth="1.5"
112
+ strokeLinecap="round"
113
+ strokeLinejoin="round"
114
+ />
115
+ </svg>
116
+ );
117
+
118
+ /**
119
+ * Checkmark icon for selected options
120
+ */
121
+ const CheckIcon = ({ className }: { className?: string }) => (
122
+ <svg
123
+ className={cn("size-14 shrink-0", className)}
124
+ viewBox="0 0 14 14"
125
+ fill="none"
126
+ xmlns="http://www.w3.org/2000/svg"
127
+ aria-hidden="true"
128
+ >
129
+ <path
130
+ d="M11.6666 3.5L5.24992 9.91667L2.33325 7"
131
+ stroke="currentColor"
132
+ strokeWidth="1.5"
133
+ strokeLinecap="round"
134
+ strokeLinejoin="round"
135
+ />
136
+ </svg>
137
+ );
138
+
139
+ // ============================================================================
140
+ // Select Root
141
+ // ============================================================================
142
+
143
+ export interface SelectProps<Value = string>
144
+ extends BaseSelect.Root.Props<Value> {
145
+ children: React.ReactNode;
146
+ }
147
+
148
+ const SelectRoot = <Value = string>({
149
+ children,
150
+ ...props
151
+ }: SelectProps<Value>) => {
152
+ return <BaseSelect.Root {...props}>{children}</BaseSelect.Root>;
153
+ };
154
+
155
+ // ============================================================================
156
+ // Select Trigger
157
+ // ============================================================================
158
+
159
+ export interface SelectTriggerProps
160
+ extends Omit<React.ComponentProps<typeof BaseSelect.Trigger>, "className">,
161
+ VariantProps<typeof selectTriggerVariants> {
162
+ className?: string;
163
+ placeholder?: string;
164
+ }
165
+
166
+ const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(
167
+ (
168
+ { className, size, error, placeholder = "Select option...", ...props },
169
+ ref,
170
+ ) => {
171
+ return (
172
+ <BaseSelect.Trigger
173
+ ref={ref}
174
+ className={cn(selectTriggerVariants({ size, error }), className)}
175
+ {...props}
176
+ >
177
+ <BaseSelect.Value>
178
+ {(value) =>
179
+ value ? (
180
+ value
181
+ ) : (
182
+ <span className="text-text-muted">{placeholder}</span>
183
+ )
184
+ }
185
+ </BaseSelect.Value>
186
+ <BaseSelect.Icon>
187
+ <SelectChevronIcon />
188
+ </BaseSelect.Icon>
189
+ </BaseSelect.Trigger>
190
+ );
191
+ },
192
+ );
193
+ SelectTrigger.displayName = "SelectTrigger";
194
+
195
+ // ============================================================================
196
+ // Select Portal & Popup
197
+ // ============================================================================
198
+
199
+ export interface SelectPopupProps
200
+ extends Omit<React.ComponentProps<typeof BaseSelect.Popup>, "className"> {
201
+ className?: string;
202
+ }
203
+
204
+ const SelectPopup = React.forwardRef<HTMLDivElement, SelectPopupProps>(
205
+ ({ className, children, ...props }, ref) => {
206
+ return (
207
+ <BaseSelect.Portal>
208
+ <BaseSelect.Positioner side="bottom" sideOffset={4} align="start">
209
+ <BaseSelect.Popup
210
+ ref={ref}
211
+ className={cn(selectPopupVariants(), className)}
212
+ {...props}
213
+ >
214
+ {children}
215
+ </BaseSelect.Popup>
216
+ </BaseSelect.Positioner>
217
+ </BaseSelect.Portal>
218
+ );
219
+ },
220
+ );
221
+ SelectPopup.displayName = "SelectPopup";
222
+
223
+ // ============================================================================
224
+ // Select Option (wraps Base UI's Select.Item)
225
+ // ============================================================================
226
+
227
+ export interface SelectOptionProps
228
+ extends Omit<React.ComponentProps<typeof BaseSelect.Item>, "className"> {
229
+ className?: string;
230
+ }
231
+
232
+ const SelectOption = React.forwardRef<HTMLDivElement, SelectOptionProps>(
233
+ ({ className, children, ...props }, ref) => {
234
+ return (
235
+ <BaseSelect.Item
236
+ ref={ref}
237
+ className={cn(selectOptionVariants(), className)}
238
+ {...props}
239
+ >
240
+ <BaseSelect.ItemText>{children}</BaseSelect.ItemText>
241
+ <BaseSelect.ItemIndicator>
242
+ <CheckIcon />
243
+ </BaseSelect.ItemIndicator>
244
+ </BaseSelect.Item>
245
+ );
246
+ },
247
+ );
248
+ SelectOption.displayName = "SelectOption";
249
+
250
+ // ============================================================================
251
+ // Select Group
252
+ // ============================================================================
253
+
254
+ export interface SelectGroupProps
255
+ extends Omit<React.ComponentProps<typeof BaseSelect.Group>, "className"> {
256
+ className?: string;
257
+ }
258
+
259
+ const SelectGroup = React.forwardRef<HTMLDivElement, SelectGroupProps>(
260
+ ({ className, children, ...props }, ref) => {
261
+ return (
262
+ <BaseSelect.Group ref={ref} className={className} {...props}>
263
+ {children}
264
+ </BaseSelect.Group>
265
+ );
266
+ },
267
+ );
268
+ SelectGroup.displayName = "SelectGroup";
269
+
270
+ // ============================================================================
271
+ // Select Group Label
272
+ // ============================================================================
273
+
274
+ export interface SelectGroupLabelProps
275
+ extends Omit<
276
+ React.ComponentProps<typeof BaseSelect.GroupLabel>,
277
+ "className"
278
+ > {
279
+ className?: string;
280
+ }
281
+
282
+ const SelectGroupLabel = React.forwardRef<
283
+ HTMLDivElement,
284
+ SelectGroupLabelProps
285
+ >(({ className, children, ...props }, ref) => {
286
+ return (
287
+ <BaseSelect.GroupLabel
288
+ ref={ref}
289
+ className={cn(
290
+ "px-12 py-6 typography-body-sm-sm font-medium text-text-muted uppercase tracking-wide",
291
+ className,
292
+ )}
293
+ {...props}
294
+ >
295
+ {children}
296
+ </BaseSelect.GroupLabel>
297
+ );
298
+ });
299
+ SelectGroupLabel.displayName = "SelectGroupLabel";
300
+
301
+ // ============================================================================
302
+ // Compound Component Export
303
+ // ============================================================================
304
+
305
+ export const Select = Object.assign(SelectRoot, {
306
+ Trigger: SelectTrigger,
307
+ Popup: SelectPopup,
308
+ Option: SelectOption,
309
+ Group: SelectGroup,
310
+ GroupLabel: SelectGroupLabel,
311
+ });
312
+
313
+ // Also export individual components for flexibility
314
+ export {
315
+ SelectRoot,
316
+ SelectTrigger,
317
+ SelectPopup,
318
+ SelectOption,
319
+ SelectGroup,
320
+ SelectGroupLabel,
321
+ selectTriggerVariants,
322
+ selectPopupVariants,
323
+ selectOptionVariants,
324
+ };
@@ -0,0 +1,24 @@
1
+ export type {
2
+ TooltipArrowProps,
3
+ TooltipPopupProps,
4
+ TooltipPortalProps,
5
+ TooltipPositionerProps,
6
+ TooltipProps,
7
+ TooltipProviderProps,
8
+ TooltipRootProps,
9
+ TooltipTriggerProps,
10
+ } from "./tooltip";
11
+
12
+ export {
13
+ Tooltip,
14
+ TooltipArrow,
15
+ TooltipParts,
16
+ TooltipPopup,
17
+ TooltipPortal,
18
+ TooltipPositioner,
19
+ TooltipProvider,
20
+ TooltipRoot,
21
+ TooltipTrigger,
22
+ tooltipArrowVariants,
23
+ tooltipPopupVariants,
24
+ } from "./tooltip";
@@ -0,0 +1,348 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { Button } from "../button";
3
+ import {
4
+ Tooltip,
5
+ TooltipArrow,
6
+ TooltipParts,
7
+ TooltipPopup,
8
+ TooltipPortal,
9
+ TooltipPositioner,
10
+ TooltipProvider,
11
+ TooltipTrigger,
12
+ } from ".";
13
+
14
+ const meta: Meta<typeof Tooltip> = {
15
+ title: "Atoms/Tooltip",
16
+ component: Tooltip,
17
+ argTypes: {
18
+ side: {
19
+ control: { type: "select" },
20
+ options: ["top", "bottom", "left", "right"],
21
+ },
22
+ align: {
23
+ control: { type: "select" },
24
+ options: ["start", "center", "end"],
25
+ },
26
+ sideOffset: {
27
+ control: { type: "number" },
28
+ },
29
+ delay: {
30
+ control: { type: "number" },
31
+ },
32
+ closeDelay: {
33
+ control: { type: "number" },
34
+ },
35
+ showArrow: {
36
+ control: { type: "boolean" },
37
+ },
38
+ },
39
+ args: {
40
+ side: "top",
41
+ align: "center",
42
+ sideOffset: 8,
43
+ showArrow: true,
44
+ },
45
+ decorators: [
46
+ (Story) => (
47
+ <div className="flex min-h-[200px] items-center justify-center p-48">
48
+ <Story />
49
+ </div>
50
+ ),
51
+ ],
52
+ } satisfies Meta<typeof Tooltip>;
53
+
54
+ export default meta;
55
+ type Story = StoryObj<typeof Tooltip>;
56
+
57
+ // =============================================================================
58
+ // Playground
59
+ // =============================================================================
60
+
61
+ export const Playground: Story = {
62
+ render: (args) => (
63
+ <Tooltip {...args} content="This is a tooltip">
64
+ <Button>Hover me</Button>
65
+ </Tooltip>
66
+ ),
67
+ };
68
+
69
+ // =============================================================================
70
+ // Basic Examples
71
+ // =============================================================================
72
+
73
+ /**
74
+ * Basic tooltip with default settings.
75
+ */
76
+ export const Default: Story = {
77
+ render: () => (
78
+ <Tooltip content="Save your changes">
79
+ <Button>Save</Button>
80
+ </Tooltip>
81
+ ),
82
+ };
83
+
84
+ /**
85
+ * Tooltip without arrow.
86
+ */
87
+ export const NoArrow: Story = {
88
+ render: () => (
89
+ <Tooltip content="No arrow here" showArrow={false}>
90
+ <Button>Hover me</Button>
91
+ </Tooltip>
92
+ ),
93
+ };
94
+
95
+ /**
96
+ * Tooltip with longer content.
97
+ */
98
+ export const LongContent: Story = {
99
+ render: () => (
100
+ <Tooltip content="This is a longer tooltip that provides more detailed information about the element.">
101
+ <Button>Hover for details</Button>
102
+ </Tooltip>
103
+ ),
104
+ };
105
+
106
+ // =============================================================================
107
+ // Positioning
108
+ // =============================================================================
109
+
110
+ /**
111
+ * Tooltip positioned on all four sides.
112
+ */
113
+ export const AllPositions: Story = {
114
+ render: () => (
115
+ <div className="flex flex-col items-center gap-48">
116
+ <Tooltip content="Top tooltip" side="top">
117
+ <Button variant="outline">Top</Button>
118
+ </Tooltip>
119
+ <div className="flex items-center gap-48">
120
+ <Tooltip content="Left tooltip" side="left">
121
+ <Button variant="outline">Left</Button>
122
+ </Tooltip>
123
+ <Tooltip content="Right tooltip" side="right">
124
+ <Button variant="outline">Right</Button>
125
+ </Tooltip>
126
+ </div>
127
+ <Tooltip content="Bottom tooltip" side="bottom">
128
+ <Button variant="outline">Bottom</Button>
129
+ </Tooltip>
130
+ </div>
131
+ ),
132
+ };
133
+
134
+ export const Top: Story = {
135
+ render: () => (
136
+ <Tooltip content="Top position" side="top">
137
+ <Button>Top</Button>
138
+ </Tooltip>
139
+ ),
140
+ };
141
+
142
+ export const Bottom: Story = {
143
+ render: () => (
144
+ <Tooltip content="Bottom position" side="bottom">
145
+ <Button>Bottom</Button>
146
+ </Tooltip>
147
+ ),
148
+ };
149
+
150
+ export const Left: Story = {
151
+ render: () => (
152
+ <Tooltip content="Left position" side="left">
153
+ <Button>Left</Button>
154
+ </Tooltip>
155
+ ),
156
+ };
157
+
158
+ export const Right: Story = {
159
+ render: () => (
160
+ <Tooltip content="Right position" side="right">
161
+ <Button>Right</Button>
162
+ </Tooltip>
163
+ ),
164
+ };
165
+
166
+ // =============================================================================
167
+ // Alignment
168
+ // =============================================================================
169
+
170
+ /**
171
+ * Tooltip alignment options.
172
+ */
173
+ export const Alignment: Story = {
174
+ render: () => (
175
+ <div className="flex flex-col gap-24">
176
+ <div className="flex items-center gap-24">
177
+ <Tooltip content="Aligned to start" side="bottom" align="start">
178
+ <Button variant="secondary" className="w-144">
179
+ Start
180
+ </Button>
181
+ </Tooltip>
182
+ <Tooltip content="Aligned to center" side="bottom" align="center">
183
+ <Button variant="secondary" className="w-144">
184
+ Center
185
+ </Button>
186
+ </Tooltip>
187
+ <Tooltip content="Aligned to end" side="bottom" align="end">
188
+ <Button variant="secondary" className="w-144">
189
+ End
190
+ </Button>
191
+ </Tooltip>
192
+ </div>
193
+ </div>
194
+ ),
195
+ };
196
+
197
+ // =============================================================================
198
+ // Delays
199
+ // =============================================================================
200
+
201
+ /**
202
+ * Tooltip with custom open delay.
203
+ */
204
+ export const CustomDelay: Story = {
205
+ render: () => (
206
+ <Tooltip content="Appears after 1 second" delay={1000}>
207
+ <Button>1s delay</Button>
208
+ </Tooltip>
209
+ ),
210
+ };
211
+
212
+ /**
213
+ * Tooltip with close delay.
214
+ */
215
+ export const CloseDelay: Story = {
216
+ render: () => (
217
+ <Tooltip content="Stays visible for 500ms" closeDelay={500}>
218
+ <Button>Close delay</Button>
219
+ </Tooltip>
220
+ ),
221
+ };
222
+
223
+ // =============================================================================
224
+ // With Provider (Shared Delays)
225
+ // =============================================================================
226
+
227
+ /**
228
+ * Multiple tooltips with shared delay using TooltipProvider.
229
+ * After opening one tooltip, others open instantly.
230
+ */
231
+ export const WithProvider: Story = {
232
+ render: () => (
233
+ <TooltipProvider delay={300} closeDelay={0}>
234
+ <div className="flex items-center gap-16">
235
+ <Tooltip content="First tooltip">
236
+ <Button variant="outline">First</Button>
237
+ </Tooltip>
238
+ <Tooltip content="Second tooltip">
239
+ <Button variant="outline">Second</Button>
240
+ </Tooltip>
241
+ <Tooltip content="Third tooltip">
242
+ <Button variant="outline">Third</Button>
243
+ </Tooltip>
244
+ </div>
245
+ </TooltipProvider>
246
+ ),
247
+ };
248
+
249
+ // =============================================================================
250
+ // Compound Component API
251
+ // =============================================================================
252
+
253
+ /**
254
+ * Using the compound component API for full control.
255
+ */
256
+ export const CompoundAPI: Story = {
257
+ render: () => (
258
+ <TooltipParts>
259
+ <TooltipTrigger>
260
+ <Button variant="primary">Custom Tooltip</Button>
261
+ </TooltipTrigger>
262
+ <TooltipPortal>
263
+ <TooltipPositioner side="bottom" sideOffset={12}>
264
+ <TooltipPopup>
265
+ <TooltipArrow />
266
+ Built with compound components
267
+ </TooltipPopup>
268
+ </TooltipPositioner>
269
+ </TooltipPortal>
270
+ </TooltipParts>
271
+ ),
272
+ };
273
+
274
+ // =============================================================================
275
+ // On Different Elements
276
+ // =============================================================================
277
+
278
+ /**
279
+ * Tooltip on various trigger elements.
280
+ */
281
+ export const OnDifferentElements: Story = {
282
+ render: () => (
283
+ <div className="flex items-center gap-24">
284
+ <Tooltip content="Button tooltip">
285
+ <Button>Button</Button>
286
+ </Tooltip>
287
+
288
+ <Tooltip content="Icon tooltip">
289
+ <button
290
+ type="button"
291
+ className="flex size-36 items-center justify-center rounded-surface-ui-small bg-bg-section text-text-primary hover:bg-bg-section-secondary"
292
+ >
293
+ <svg
294
+ className="size-20"
295
+ fill="none"
296
+ viewBox="0 0 24 24"
297
+ stroke="currentColor"
298
+ aria-hidden="true"
299
+ >
300
+ <path
301
+ strokeLinecap="round"
302
+ strokeLinejoin="round"
303
+ strokeWidth={2}
304
+ d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
305
+ />
306
+ </svg>
307
+ <span className="sr-only">Info</span>
308
+ </button>
309
+ </Tooltip>
310
+
311
+ <Tooltip content="Text tooltip">
312
+ <span className="cursor-help border-b border-dashed border-fg-muted text-text-secondary">
313
+ Hover this text
314
+ </span>
315
+ </Tooltip>
316
+ </div>
317
+ ),
318
+ };
319
+
320
+ // =============================================================================
321
+ // Controlled
322
+ // =============================================================================
323
+
324
+ /**
325
+ * Controlled tooltip with external state management.
326
+ */
327
+ export const Controlled: Story = {
328
+ render: function ControlledStory() {
329
+ const [open, setOpen] = React.useState(false);
330
+
331
+ return (
332
+ <div className="flex flex-col items-center gap-16">
333
+ <Tooltip
334
+ content="Controlled tooltip"
335
+ open={open}
336
+ onOpenChange={setOpen}
337
+ >
338
+ <Button>Hover or click button below</Button>
339
+ </Tooltip>
340
+ <Button variant="outline" onClick={() => setOpen(!open)}>
341
+ {open ? "Close tooltip" : "Open tooltip"}
342
+ </Button>
343
+ </div>
344
+ );
345
+ },
346
+ };
347
+
348
+ import * as React from "react";