@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.
- package/dist/component-registry.md +1310 -127
- package/dist/components/atoms/background/background.d.ts +13 -27
- package/dist/components/atoms/button/button.d.ts +64 -72
- package/dist/components/atoms/button/button.figma.d.ts +1 -0
- package/dist/components/atoms/button/icon-button.d.ts +62 -110
- package/dist/components/atoms/input/input-group.d.ts +278 -0
- package/dist/components/atoms/input/input.d.ts +121 -0
- package/dist/components/atoms/popover/popover.d.ts +195 -0
- package/dist/components/atoms/select/select.d.ts +131 -0
- package/dist/components/atoms/tooltip/tooltip.d.ts +161 -0
- package/dist/components/organisms/card/card.d.ts +3 -3
- package/dist/components/sections/hero/hero.d.ts +2 -2
- package/dist/components/sections/prose/prose.d.ts +3 -3
- package/dist/components/sections/river/river.d.ts +1 -1
- package/dist/components/sections/tout/tout.d.ts +4 -4
- package/dist/components/shared/floating-arrow.d.ts +34 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +13935 -7622
- package/dist/index.js.map +1 -1
- package/dist/lib/form-control.d.ts +106 -0
- package/dist/tokens.css +4725 -19065
- package/package.json +2 -1
- package/src/components/atoms/accordion/accordion.stories.tsx +1 -1
- package/src/components/atoms/accordion/accordion.tsx +2 -2
- package/src/components/atoms/background/background.tsx +71 -109
- package/src/components/atoms/button/button.figma.tsx +37 -0
- package/src/components/atoms/button/button.stories.tsx +253 -115
- package/src/components/atoms/button/button.test.tsx +289 -5
- package/src/components/atoms/button/button.tsx +40 -101
- package/src/components/atoms/button/button.visual.test.tsx +28 -32
- package/src/components/atoms/button/icon-button.stories.tsx +44 -101
- package/src/components/atoms/button/icon-button.test.tsx +26 -94
- package/src/components/atoms/button/icon-button.tsx +81 -224
- package/src/components/atoms/input/index.ts +17 -0
- package/src/components/atoms/input/input-group.stories.tsx +646 -0
- package/src/components/atoms/input/input-group.test.tsx +362 -0
- package/src/components/atoms/input/input-group.tsx +409 -0
- package/src/components/atoms/input/input.stories.tsx +228 -0
- package/src/components/atoms/input/input.test.tsx +167 -0
- package/src/components/atoms/input/input.tsx +104 -0
- package/src/components/atoms/pager-control/pager-control.stories.tsx +6 -8
- package/src/components/atoms/pager-control/pager-control.tsx +12 -12
- package/src/components/atoms/popover/index.ts +30 -0
- package/src/components/atoms/popover/popover.stories.tsx +531 -0
- package/src/components/atoms/popover/popover.test.tsx +486 -0
- package/src/components/atoms/popover/popover.tsx +488 -0
- package/src/components/atoms/select/index.ts +18 -0
- package/src/components/atoms/select/select.stories.tsx +455 -0
- package/src/components/atoms/select/select.tsx +324 -0
- package/src/components/atoms/tooltip/index.ts +24 -0
- package/src/components/atoms/tooltip/tooltip.stories.tsx +348 -0
- package/src/components/atoms/tooltip/tooltip.test.tsx +363 -0
- package/src/components/atoms/tooltip/tooltip.tsx +347 -0
- package/src/components/dev-tools/dev-toolbar/dev-toolbar.stories.tsx +8 -17
- package/src/components/dev-tools/dev-toolbar/dev-toolbar.tsx +3 -3
- package/src/components/foundation/typography/typography.stories.tsx +401 -0
- package/src/components/organisms/card/card.stories.tsx +19 -19
- package/src/components/organisms/card/card.test.tsx +1 -1
- package/src/components/organisms/card/card.tsx +3 -3
- package/src/components/organisms/card/card.visual.test.tsx +11 -11
- package/src/components/organisms/navbar/navbar.tsx +2 -2
- package/src/components/organisms/navbar/navbar.visual.test.tsx +2 -2
- package/src/components/organisms/us-gov-banner/us-gov-banner.tsx +2 -2
- package/src/components/sections/banner/banner.stories.tsx +1 -5
- package/src/components/sections/banner/banner.test.tsx +2 -2
- package/src/components/sections/banner/banner.tsx +6 -6
- package/src/components/sections/card-grid/card-grid.tsx +5 -5
- package/src/components/sections/faq-section/faq-section.tsx +2 -2
- package/src/components/sections/hero/hero.stories.tsx +7 -7
- package/src/components/sections/hero/hero.test.tsx +5 -5
- package/src/components/sections/hero/hero.tsx +10 -11
- package/src/components/sections/prose/prose.test.tsx +2 -2
- package/src/components/sections/prose/prose.tsx +6 -7
- package/src/components/sections/river/river.stories.tsx +8 -8
- package/src/components/sections/river/river.test.tsx +4 -4
- package/src/components/sections/river/river.tsx +8 -16
- package/src/components/sections/tout/tout.stories.tsx +7 -31
- package/src/components/sections/tout/tout.test.tsx +1 -1
- package/src/components/sections/tout/tout.tsx +11 -11
- package/src/components/sections/two-column-section/two-column-section.tsx +7 -9
- package/src/components/shared/floating-arrow.tsx +78 -0
- package/src/components/shared/index.ts +5 -0
- package/src/index.ts +98 -0
- package/src/lib/form-control.ts +71 -0
- package/src/stories/grid-system.stories.tsx +309 -0
- package/src/stories/{Introduction.mdx → introduction.mdx} +29 -15
- package/src/stories/{ThemeProvider.stories.tsx → theme-provider.stories.tsx} +8 -22
- package/src/stories/{TokenShowcase.stories.tsx → token-showcase.stories.tsx} +1 -20
- package/src/stories/token-showcase.tsx +777 -0
- package/src/styles.css +3 -0
- package/src/tests/token-resolution.test.tsx +298 -0
- package/src/theme/hooks.ts +1 -1
- package/src/theme/index.ts +1 -1
- package/src/theme/theme-provider.test.tsx +270 -0
- package/src/theme/{ThemeProvider.tsx → theme-provider.tsx} +18 -2
- package/src/stories/GridSystem.stories.tsx +0 -84
- 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";
|