@adres/mgda-ui 0.0.0-stage → 0.0.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.
- package/brand-knowledge.json +451 -0
- package/knowledge/accordion.md +135 -0
- package/knowledge/activity-rings.md +123 -0
- package/knowledge/alert.md +184 -0
- package/knowledge/answer-actions.md +217 -0
- package/knowledge/aspect-ratio.md +82 -0
- package/knowledge/avatar.md +158 -0
- package/knowledge/badge.md +172 -0
- package/knowledge/bar-graph.md +167 -0
- package/knowledge/bento-grid.md +115 -0
- package/knowledge/blockquote.md +153 -0
- package/knowledge/box-list.md +177 -0
- package/knowledge/bullet-list.md +128 -0
- package/knowledge/button-group.md +179 -0
- package/knowledge/button.md +231 -0
- package/knowledge/calendar.md +168 -0
- package/knowledge/card.md +321 -0
- package/knowledge/cards-nav.md +170 -0
- package/knowledge/chain-of-thought.md +164 -0
- package/knowledge/chat-container.md +135 -0
- package/knowledge/chat-message.md +224 -0
- package/knowledge/checkbox-card.md +172 -0
- package/knowledge/checkbox.md +111 -0
- package/knowledge/citations.md +241 -0
- package/knowledge/combobox.md +265 -0
- package/knowledge/countdown-pill.md +153 -0
- package/knowledge/cta.md +118 -0
- package/knowledge/data-list.md +173 -0
- package/knowledge/data-table.md +220 -0
- package/knowledge/dialog.md +249 -0
- package/knowledge/drawer.md +236 -0
- package/knowledge/dropdown-menu.md +238 -0
- package/knowledge/dropzone.md +153 -0
- package/knowledge/email-chips-input.md +161 -0
- package/knowledge/empty-state.md +106 -0
- package/knowledge/entity-header.md +166 -0
- package/knowledge/feature-grid.md +120 -0
- package/knowledge/feedback-bar.md +127 -0
- package/knowledge/footer.md +116 -0
- package/knowledge/heading.md +189 -0
- package/knowledge/hero.md +361 -0
- package/knowledge/input-button.md +187 -0
- package/knowledge/input-group.md +185 -0
- package/knowledge/input.md +256 -0
- package/knowledge/inset.md +177 -0
- package/knowledge/kpi-card.md +206 -0
- package/knowledge/list-nav.md +181 -0
- package/knowledge/logo-cloud.md +102 -0
- package/knowledge/media-gallery.md +158 -0
- package/knowledge/message-actions.md +175 -0
- package/knowledge/nav-sidebar.md +225 -0
- package/knowledge/number-stepper.md +159 -0
- package/knowledge/page-header.md +109 -0
- package/knowledge/pagination.md +225 -0
- package/knowledge/popover.md +159 -0
- package/knowledge/pricing.md +132 -0
- package/knowledge/progress.md +156 -0
- package/knowledge/prompt-input.md +337 -0
- package/knowledge/prompt-suggestions.md +168 -0
- package/knowledge/quota-bar.md +160 -0
- package/knowledge/quote.md +116 -0
- package/knowledge/radio-card.md +172 -0
- package/knowledge/radio-group.md +135 -0
- package/knowledge/reasoning.md +142 -0
- package/knowledge/search.md +131 -0
- package/knowledge/section-nav.md +162 -0
- package/knowledge/segmented-control.md +181 -0
- package/knowledge/select-button.md +205 -0
- package/knowledge/select.md +210 -0
- package/knowledge/separator.md +75 -0
- package/knowledge/shimmer-text.md +140 -0
- package/knowledge/sidebar.md +227 -0
- package/knowledge/skeleton.md +142 -0
- package/knowledge/slider.md +133 -0
- package/knowledge/slideshow.md +159 -0
- package/knowledge/source.md +156 -0
- package/knowledge/spinner.md +120 -0
- package/knowledge/stat-strip.md +128 -0
- package/knowledge/stats.md +117 -0
- package/knowledge/step-indicator.md +157 -0
- package/knowledge/switch.md +88 -0
- package/knowledge/table.md +190 -0
- package/knowledge/tabs.md +169 -0
- package/knowledge/testimonials.md +115 -0
- package/knowledge/text-link.md +192 -0
- package/knowledge/text.md +194 -0
- package/knowledge/thinking-bar.md +115 -0
- package/knowledge/thinking-steps.md +140 -0
- package/knowledge/toaster.md +127 -0
- package/knowledge/toggle.md +113 -0
- package/knowledge/tool-call-card.md +241 -0
- package/knowledge/tooltip.md +120 -0
- package/knowledge/top-nav.md +138 -0
- package/knowledge.json +19854 -0
- package/package.json +94 -4
- package/src/components/accordion.stories.tsx +46 -0
- package/src/components/accordion.tsx +74 -0
- package/src/components/activity-rings.stories.tsx +73 -0
- package/src/components/activity-rings.tsx +121 -0
- package/src/components/alert.stories.tsx +43 -0
- package/src/components/alert.tsx +132 -0
- package/src/components/answer-actions.stories.tsx +54 -0
- package/src/components/answer-actions.test.tsx +37 -0
- package/src/components/answer-actions.tsx +313 -0
- package/src/components/aspect-ratio.stories.tsx +32 -0
- package/src/components/aspect-ratio.tsx +5 -0
- package/src/components/avatar.stories.tsx +40 -0
- package/src/components/avatar.tsx +154 -0
- package/src/components/badge.stories.tsx +27 -0
- package/src/components/badge.tsx +63 -0
- package/src/components/bar-graph.stories.tsx +28 -0
- package/src/components/bar-graph.tsx +204 -0
- package/src/components/bento-grid.stories.tsx +23 -0
- package/src/components/bento-grid.tsx +67 -0
- package/src/components/blockquote.stories.tsx +28 -0
- package/src/components/blockquote.tsx +88 -0
- package/src/components/box-list.stories.tsx +25 -0
- package/src/components/box-list.tsx +108 -0
- package/src/components/bullet-list.stories.tsx +22 -0
- package/src/components/bullet-list.tsx +101 -0
- package/src/components/button-group.stories.tsx +31 -0
- package/src/components/button-group.tsx +156 -0
- package/src/components/button.stories.tsx +66 -0
- package/src/components/button.test.ts +17 -0
- package/src/components/button.tsx +139 -0
- package/src/components/calendar.stories.tsx +25 -0
- package/src/components/calendar.tsx +367 -0
- package/src/components/card.stories.tsx +44 -0
- package/src/components/card.tsx +392 -0
- package/src/components/cards-nav.stories.tsx +35 -0
- package/src/components/cards-nav.tsx +115 -0
- package/src/components/chain-of-thought.stories.tsx +67 -0
- package/src/components/chain-of-thought.test.tsx +50 -0
- package/src/components/chain-of-thought.tsx +170 -0
- package/src/components/chat-container.stories.tsx +47 -0
- package/src/components/chat-container.test.tsx +49 -0
- package/src/components/chat-container.tsx +222 -0
- package/src/components/chat-message.stories.tsx +52 -0
- package/src/components/chat-message.test.tsx +36 -0
- package/src/components/chat-message.tsx +163 -0
- package/src/components/checkbox-card.stories.tsx +27 -0
- package/src/components/checkbox-card.tsx +200 -0
- package/src/components/checkbox.stories.tsx +26 -0
- package/src/components/checkbox.tsx +61 -0
- package/src/components/citations.stories.tsx +64 -0
- package/src/components/citations.test.tsx +61 -0
- package/src/components/citations.tsx +220 -0
- package/src/components/combobox.stories.tsx +184 -0
- package/src/components/combobox.test.tsx +82 -0
- package/src/components/combobox.tsx +407 -0
- package/src/components/countdown-pill.stories.tsx +24 -0
- package/src/components/countdown-pill.tsx +129 -0
- package/src/components/cta.stories.tsx +29 -0
- package/src/components/cta.tsx +52 -0
- package/src/components/data-list.stories.tsx +50 -0
- package/src/components/data-list.test.tsx +35 -0
- package/src/components/data-list.tsx +181 -0
- package/src/components/data-table.stories.tsx +29 -0
- package/src/components/data-table.tsx +200 -0
- package/src/components/dialog.stories.tsx +42 -0
- package/src/components/dialog.tsx +200 -0
- package/src/components/drawer.stories.tsx +48 -0
- package/src/components/drawer.tsx +385 -0
- package/src/components/dropdown-menu.stories.tsx +41 -0
- package/src/components/dropdown-menu.tsx +252 -0
- package/src/components/dropzone.stories.tsx +19 -0
- package/src/components/dropzone.tsx +250 -0
- package/src/components/email-chips-input.stories.tsx +135 -0
- package/src/components/email-chips-input.tsx +210 -0
- package/src/components/empty-state.stories.tsx +22 -0
- package/src/components/empty-state.tsx +35 -0
- package/src/components/entity-header.stories.tsx +43 -0
- package/src/components/entity-header.test.tsx +35 -0
- package/src/components/entity-header.tsx +92 -0
- package/src/components/feature-grid.stories.tsx +25 -0
- package/src/components/feature-grid.tsx +87 -0
- package/src/components/feedback-bar.stories.tsx +25 -0
- package/src/components/feedback-bar.test.tsx +24 -0
- package/src/components/feedback-bar.tsx +62 -0
- package/src/components/footer.stories.tsx +23 -0
- package/src/components/footer.tsx +78 -0
- package/src/components/heading.stories.tsx +45 -0
- package/src/components/heading.test.tsx +22 -0
- package/src/components/heading.tsx +77 -0
- package/src/components/hero.stories.tsx +38 -0
- package/src/components/hero.tsx +498 -0
- package/src/components/input-button.stories.tsx +19 -0
- package/src/components/input-button.tsx +112 -0
- package/src/components/input-group.stories.tsx +38 -0
- package/src/components/input-group.tsx +157 -0
- package/src/components/input.stories.tsx +31 -0
- package/src/components/input.tsx +284 -0
- package/src/components/inset.stories.tsx +29 -0
- package/src/components/inset.tsx +70 -0
- package/src/components/kpi-card.stories.tsx +29 -0
- package/src/components/kpi-card.tsx +284 -0
- package/src/components/list-nav.stories.tsx +22 -0
- package/src/components/list-nav.tsx +145 -0
- package/src/components/logo-cloud.stories.tsx +23 -0
- package/src/components/logo-cloud.tsx +42 -0
- package/src/components/media-gallery.stories.tsx +25 -0
- package/src/components/media-gallery.tsx +347 -0
- package/src/components/message-actions.stories.tsx +45 -0
- package/src/components/message-actions.test.tsx +32 -0
- package/src/components/message-actions.tsx +119 -0
- package/src/components/nav-sidebar.stories.tsx +58 -0
- package/src/components/nav-sidebar.test.tsx +54 -0
- package/src/components/nav-sidebar.tsx +462 -0
- package/src/components/number-stepper.stories.tsx +19 -0
- package/src/components/number-stepper.tsx +158 -0
- package/src/components/page-header.stories.tsx +25 -0
- package/src/components/page-header.tsx +64 -0
- package/src/components/pagination.stories.tsx +110 -0
- package/src/components/pagination.test.tsx +46 -0
- package/src/components/pagination.tsx +144 -0
- package/src/components/popover.stories.tsx +26 -0
- package/src/components/popover.tsx +62 -0
- package/src/components/pricing.stories.tsx +42 -0
- package/src/components/pricing.tsx +130 -0
- package/src/components/progress.stories.tsx +22 -0
- package/src/components/progress.tsx +189 -0
- package/src/components/prompt-input.stories.tsx +62 -0
- package/src/components/prompt-input.test.tsx +106 -0
- package/src/components/prompt-input.tsx +343 -0
- package/src/components/prompt-suggestions.stories.tsx +53 -0
- package/src/components/prompt-suggestions.test.tsx +37 -0
- package/src/components/prompt-suggestions.tsx +122 -0
- package/src/components/quota-bar.stories.tsx +19 -0
- package/src/components/quota-bar.tsx +191 -0
- package/src/components/quote.stories.tsx +18 -0
- package/src/components/quote.tsx +43 -0
- package/src/components/radio-card.stories.tsx +27 -0
- package/src/components/radio-card.tsx +225 -0
- package/src/components/radio-group.stories.tsx +32 -0
- package/src/components/radio-group.tsx +68 -0
- package/src/components/reasoning.stories.tsx +50 -0
- package/src/components/reasoning.test.tsx +48 -0
- package/src/components/reasoning.tsx +143 -0
- package/src/components/search.stories.tsx +18 -0
- package/src/components/search.tsx +133 -0
- package/src/components/section-nav.stories.tsx +59 -0
- package/src/components/section-nav.test.tsx +36 -0
- package/src/components/section-nav.tsx +146 -0
- package/src/components/segmented-control.stories.tsx +22 -0
- package/src/components/segmented-control.tsx +301 -0
- package/src/components/select-button.stories.tsx +27 -0
- package/src/components/select-button.tsx +174 -0
- package/src/components/select.stories.tsx +27 -0
- package/src/components/select.tsx +198 -0
- package/src/components/separator.stories.tsx +22 -0
- package/src/components/separator.tsx +23 -0
- package/src/components/shimmer-text.stories.tsx +23 -0
- package/src/components/shimmer-text.test.tsx +35 -0
- package/src/components/shimmer-text.tsx +43 -0
- package/src/components/sidebar.stories.tsx +25 -0
- package/src/components/sidebar.tsx +234 -0
- package/src/components/skeleton.stories.tsx +22 -0
- package/src/components/skeleton.tsx +96 -0
- package/src/components/slider.stories.tsx +20 -0
- package/src/components/slider.tsx +176 -0
- package/src/components/slideshow.stories.tsx +30 -0
- package/src/components/slideshow.tsx +291 -0
- package/src/components/source.stories.tsx +32 -0
- package/src/components/source.test.tsx +40 -0
- package/src/components/source.tsx +161 -0
- package/src/components/spinner.stories.tsx +19 -0
- package/src/components/spinner.tsx +76 -0
- package/src/components/stat-strip.stories.tsx +36 -0
- package/src/components/stat-strip.test.tsx +25 -0
- package/src/components/stat-strip.tsx +85 -0
- package/src/components/stats.stories.tsx +24 -0
- package/src/components/stats.tsx +83 -0
- package/src/components/step-indicator.stories.tsx +18 -0
- package/src/components/step-indicator.test.tsx +18 -0
- package/src/components/step-indicator.tsx +286 -0
- package/src/components/switch.stories.tsx +26 -0
- package/src/components/switch.tsx +32 -0
- package/src/components/table.stories.tsx +38 -0
- package/src/components/table.tsx +220 -0
- package/src/components/tabs.stories.tsx +25 -0
- package/src/components/tabs.tsx +276 -0
- package/src/components/testimonials.stories.tsx +22 -0
- package/src/components/testimonials.tsx +109 -0
- package/src/components/text-link.stories.tsx +47 -0
- package/src/components/text-link.test.tsx +18 -0
- package/src/components/text-link.tsx +88 -0
- package/src/components/text.stories.tsx +49 -0
- package/src/components/text.tsx +105 -0
- package/src/components/thinking-bar.stories.tsx +19 -0
- package/src/components/thinking-bar.test.tsx +27 -0
- package/src/components/thinking-bar.tsx +64 -0
- package/src/components/thinking-steps.stories.tsx +44 -0
- package/src/components/thinking-steps.test.tsx +42 -0
- package/src/components/thinking-steps.tsx +92 -0
- package/src/components/toaster.stories.tsx +80 -0
- package/src/components/toaster.tsx +36 -0
- package/src/components/toggle.stories.tsx +21 -0
- package/src/components/toggle.tsx +103 -0
- package/src/components/tool-call-card.stories.tsx +97 -0
- package/src/components/tool-call-card.test.tsx +88 -0
- package/src/components/tool-call-card.tsx +231 -0
- package/src/components/tooltip.stories.tsx +25 -0
- package/src/components/tooltip.tsx +43 -0
- package/src/components/top-nav.stories.tsx +20 -0
- package/src/components/top-nav.tsx +76 -0
- package/src/docs/Colors.stories.tsx +192 -0
- package/src/docs/GettingStarted.mdx +72 -0
- package/src/docs/Mcp.mdx +52 -0
- package/src/docs/Typography.stories.tsx +114 -0
- package/src/hooks/use-in-view.ts +32 -0
- package/src/hooks/use-reduced-motion.ts +26 -0
- package/src/index.ts +343 -0
- package/src/lib/cn.test.ts +67 -0
- package/src/lib/cn.ts +81 -0
- package/src/lib/motion.ts +109 -0
- package/src/lib/prefix-utilities.test.ts +56 -0
- package/src/lib/prefix-utilities.ts +50 -0
- package/src/styles/base.css +489 -0
- package/src/styles/motion.css +69 -0
- package/src/styles/overrides.css +44 -0
- package/src/styles/typography.css +173 -0
- package/src/theme/theme.css +391 -0
- package/src/theme/tokens.ts +29 -0
- package/src/theme/types.ts +26 -0
- package/README.md +0 -3
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
|
5
|
+
import { cn } from "../lib/cn"
|
|
6
|
+
|
|
7
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export type TabsSize = "sm" | "md" | "lg"
|
|
10
|
+
export type TabsOrientation = "horizontal" | "vertical"
|
|
11
|
+
|
|
12
|
+
// Sizing mirrors SegmentedControl + Button exactly so all three primitives can
|
|
13
|
+
// sit side-by-side in a toolbar without visual conflict. Heights:
|
|
14
|
+
// sm → 32px (h-2xl) md → 40px (h-3xl) lg → 48px (h-4xl)
|
|
15
|
+
const LIST_HEIGHT: Record<TabsSize, string> = {
|
|
16
|
+
sm: "ax:h-2xl",
|
|
17
|
+
md: "ax:h-3xl",
|
|
18
|
+
lg: "ax:h-4xl",
|
|
19
|
+
}
|
|
20
|
+
const TRIGGER_PADDING: Record<TabsSize, string> = {
|
|
21
|
+
sm: "ax:px-sm",
|
|
22
|
+
md: "ax:px-md",
|
|
23
|
+
lg: "ax:px-xl",
|
|
24
|
+
}
|
|
25
|
+
const TRIGGER_TEXT: Record<TabsSize, string> = {
|
|
26
|
+
sm: "ax:t-label-sm",
|
|
27
|
+
md: "ax:t-label-sm",
|
|
28
|
+
lg: "ax:t-label-lg",
|
|
29
|
+
}
|
|
30
|
+
// Vertical triggers own their height (no fixed list height), so they need
|
|
31
|
+
// their own vertical padding. Horizontal triggers get height from LIST_HEIGHT.
|
|
32
|
+
const TRIGGER_PADDING_Y: Record<TabsSize, string> = {
|
|
33
|
+
sm: "ax:py-xs", // 8px
|
|
34
|
+
md: "ax:py-sm", // 12px
|
|
35
|
+
lg: "ax:py-md", // 16px
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── Context ──────────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
interface TabsContextValue {
|
|
41
|
+
size: TabsSize
|
|
42
|
+
orientation: TabsOrientation
|
|
43
|
+
currentValue: string
|
|
44
|
+
registerTrigger: (value: string, el: HTMLButtonElement | null) => void
|
|
45
|
+
triggerRefs: React.MutableRefObject<Map<string, HTMLButtonElement>>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const TabsContext = React.createContext<TabsContextValue>({
|
|
49
|
+
size: "md",
|
|
50
|
+
orientation: "horizontal",
|
|
51
|
+
currentValue: "",
|
|
52
|
+
registerTrigger: () => {},
|
|
53
|
+
triggerRefs: { current: new Map() },
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// ─── Root ─────────────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
export interface TabsProps
|
|
59
|
+
extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root> {
|
|
60
|
+
size?: TabsSize
|
|
61
|
+
/** `horizontal` (default) is a segmented row; `vertical` stacks the triggers
|
|
62
|
+
* in a left-aligned column (labels may wrap). */
|
|
63
|
+
orientation?: TabsOrientation
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const Tabs = React.forwardRef<
|
|
67
|
+
React.ElementRef<typeof TabsPrimitive.Root>,
|
|
68
|
+
TabsProps
|
|
69
|
+
>(({ size = "md", orientation = "horizontal", value, defaultValue, onValueChange, children, ...props }, ref) => {
|
|
70
|
+
// Trigger refs are kept in a Map so TabsList can measure the active one for
|
|
71
|
+
// the floating indicator. The Map identity is stable across renders.
|
|
72
|
+
const triggerRefs = React.useRef(new Map<string, HTMLButtonElement>())
|
|
73
|
+
|
|
74
|
+
// Mirror Radix's value internally so the indicator has something to point at
|
|
75
|
+
// even when the consumer hasn't provided value/onValueChange (uncontrolled).
|
|
76
|
+
const [internalValue, setInternalValue] = React.useState<string>(
|
|
77
|
+
value ?? defaultValue ?? ""
|
|
78
|
+
)
|
|
79
|
+
const currentValue = value ?? internalValue
|
|
80
|
+
|
|
81
|
+
const handleValueChange = React.useCallback(
|
|
82
|
+
(v: string) => {
|
|
83
|
+
setInternalValue(v)
|
|
84
|
+
onValueChange?.(v)
|
|
85
|
+
},
|
|
86
|
+
[onValueChange]
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
const registerTrigger = React.useCallback(
|
|
90
|
+
(val: string, el: HTMLButtonElement | null) => {
|
|
91
|
+
if (el) triggerRefs.current.set(val, el)
|
|
92
|
+
else triggerRefs.current.delete(val)
|
|
93
|
+
},
|
|
94
|
+
[]
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const ctx = React.useMemo<TabsContextValue>(
|
|
98
|
+
() => ({ size, orientation, currentValue, registerTrigger, triggerRefs }),
|
|
99
|
+
[size, orientation, currentValue, registerTrigger]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<TabsContext.Provider value={ctx}>
|
|
104
|
+
<TabsPrimitive.Root
|
|
105
|
+
ref={ref}
|
|
106
|
+
orientation={orientation}
|
|
107
|
+
value={value}
|
|
108
|
+
defaultValue={defaultValue}
|
|
109
|
+
onValueChange={handleValueChange}
|
|
110
|
+
{...props}
|
|
111
|
+
>
|
|
112
|
+
{children}
|
|
113
|
+
</TabsPrimitive.Root>
|
|
114
|
+
</TabsContext.Provider>
|
|
115
|
+
)
|
|
116
|
+
})
|
|
117
|
+
Tabs.displayName = TabsPrimitive.Root.displayName
|
|
118
|
+
|
|
119
|
+
// ─── List ─────────────────────────────────────────────────────────────────────
|
|
120
|
+
//
|
|
121
|
+
// Hosts the floating indicator: a single absolute-positioned div that slides
|
|
122
|
+
// behind the active trigger. Position is measured via getBoundingClientRect on
|
|
123
|
+
// every value change and on parent resize (ResizeObserver). The transition is
|
|
124
|
+
// suppressed on the very first measurement so the indicator doesn't slide in
|
|
125
|
+
// from 0,0 on mount.
|
|
126
|
+
|
|
127
|
+
export const TabsList = React.forwardRef<
|
|
128
|
+
React.ElementRef<typeof TabsPrimitive.List>,
|
|
129
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
|
130
|
+
>(({ className, children, ...props }, ref) => {
|
|
131
|
+
const { size, orientation, currentValue, triggerRefs } = React.useContext(TabsContext)
|
|
132
|
+
|
|
133
|
+
const rootRef = React.useRef<HTMLDivElement>(null)
|
|
134
|
+
const positioned = React.useRef(false)
|
|
135
|
+
const [indicatorStyle, setIndicatorStyle] = React.useState<React.CSSProperties>({ opacity: 0 })
|
|
136
|
+
|
|
137
|
+
const updateIndicator = React.useCallback((val: string) => {
|
|
138
|
+
const el = triggerRefs.current.get(val)
|
|
139
|
+
const root = rootRef.current
|
|
140
|
+
if (!el || !root) return
|
|
141
|
+
|
|
142
|
+
const rootRect = root.getBoundingClientRect()
|
|
143
|
+
const itemRect = el.getBoundingClientRect()
|
|
144
|
+
|
|
145
|
+
setIndicatorStyle({
|
|
146
|
+
left: itemRect.left - rootRect.left,
|
|
147
|
+
width: itemRect.width,
|
|
148
|
+
top: itemRect.top - rootRect.top,
|
|
149
|
+
height: itemRect.height,
|
|
150
|
+
opacity: 1,
|
|
151
|
+
transition: positioned.current
|
|
152
|
+
? "left 150ms ease, width 150ms ease, top 150ms ease, height 150ms ease"
|
|
153
|
+
: undefined,
|
|
154
|
+
})
|
|
155
|
+
positioned.current = true
|
|
156
|
+
}, [triggerRefs])
|
|
157
|
+
|
|
158
|
+
React.useLayoutEffect(() => {
|
|
159
|
+
updateIndicator(currentValue)
|
|
160
|
+
}, [currentValue, updateIndicator])
|
|
161
|
+
|
|
162
|
+
React.useEffect(() => {
|
|
163
|
+
const root = rootRef.current
|
|
164
|
+
if (!root) return
|
|
165
|
+
const observer = new ResizeObserver(() => updateIndicator(currentValue))
|
|
166
|
+
observer.observe(root)
|
|
167
|
+
return () => observer.disconnect()
|
|
168
|
+
}, [currentValue, updateIndicator])
|
|
169
|
+
|
|
170
|
+
// Merge the forwarded ref with the local root ref so consumers can still
|
|
171
|
+
// attach their own ref while we measure the same node.
|
|
172
|
+
const mergedRef = React.useCallback(
|
|
173
|
+
(node: HTMLDivElement | null) => {
|
|
174
|
+
(rootRef as React.MutableRefObject<HTMLDivElement | null>).current = node
|
|
175
|
+
if (typeof ref === "function") ref(node)
|
|
176
|
+
else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node
|
|
177
|
+
},
|
|
178
|
+
[ref]
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<TabsPrimitive.List
|
|
183
|
+
ref={mergedRef}
|
|
184
|
+
className={cn(
|
|
185
|
+
"ax:relative ax:items-stretch ax:bg-muted ax:rounded-(--input-radius) ax:gap-px",
|
|
186
|
+
orientation === "vertical"
|
|
187
|
+
// Stacks full-width in a column; height is content-driven. Set a width
|
|
188
|
+
// on this element (or its container) to let long labels wrap.
|
|
189
|
+
? "ax:flex ax:flex-col ax:w-full"
|
|
190
|
+
: cn("ax:inline-flex", LIST_HEIGHT[size]),
|
|
191
|
+
className
|
|
192
|
+
)}
|
|
193
|
+
{...props}
|
|
194
|
+
>
|
|
195
|
+
{/* Floating indicator - slides behind the active trigger */}
|
|
196
|
+
<div
|
|
197
|
+
aria-hidden="true"
|
|
198
|
+
// Dark mode: a raised neutral-stronger thumb, lighter than the `bg-muted`
|
|
199
|
+
// track, so the active tab pops off the track (mirrors the light-mode
|
|
200
|
+
// white-on-grey lift). Matches SegmentedControl. Light stays --card.
|
|
201
|
+
className="ax:absolute ax:bg-card ax:dark:bg-neutral-weaker ax:shadow-sm ax:rounded-(--input-radius) ax:pointer-events-none"
|
|
202
|
+
style={indicatorStyle}
|
|
203
|
+
/>
|
|
204
|
+
{children}
|
|
205
|
+
</TabsPrimitive.List>
|
|
206
|
+
)
|
|
207
|
+
})
|
|
208
|
+
TabsList.displayName = TabsPrimitive.List.displayName
|
|
209
|
+
|
|
210
|
+
// ─── Trigger ──────────────────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
export const TabsTrigger = React.forwardRef<
|
|
213
|
+
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
|
214
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
|
215
|
+
>(({ className, value, children, ...props }, ref) => {
|
|
216
|
+
const { size, orientation, currentValue, registerTrigger } = React.useContext(TabsContext)
|
|
217
|
+
const isActive = currentValue === value
|
|
218
|
+
const localRef = React.useRef<HTMLButtonElement>(null)
|
|
219
|
+
|
|
220
|
+
React.useImperativeHandle(ref, () => localRef.current!)
|
|
221
|
+
|
|
222
|
+
React.useLayoutEffect(() => {
|
|
223
|
+
registerTrigger(value, localRef.current)
|
|
224
|
+
return () => registerTrigger(value, null)
|
|
225
|
+
}, [value, registerTrigger])
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<TabsPrimitive.Trigger
|
|
229
|
+
ref={localRef}
|
|
230
|
+
value={value}
|
|
231
|
+
className={cn(
|
|
232
|
+
"ax:relative ax:z-10 ax:flex ax:items-center ax:cursor-pointer ax:select-none",
|
|
233
|
+
"ax:rounded-(--input-radius) ax:leading-snug",
|
|
234
|
+
"ax:transition-colors ax:transition-snappy",
|
|
235
|
+
"ax:focus-visible:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring ax:focus-visible:ring-offset-1 ax:focus-visible:ring-offset-muted",
|
|
236
|
+
"ax:disabled:cursor-not-allowed ax:disabled:opacity-50",
|
|
237
|
+
TRIGGER_PADDING[size],
|
|
238
|
+
TRIGGER_TEXT[size],
|
|
239
|
+
// Horizontal: centered single line. Vertical: full-width, start-aligned,
|
|
240
|
+
// owns its vertical padding and may wrap (hence leading-snug above).
|
|
241
|
+
orientation === "vertical"
|
|
242
|
+
? cn("ax:w-full ax:justify-start ax:text-start", TRIGGER_PADDING_Y[size])
|
|
243
|
+
: "ax:justify-center ax:whitespace-nowrap",
|
|
244
|
+
isActive ? "ax:text-primary-foreground" : "ax:text-muted-foreground ax:hover:text-foreground",
|
|
245
|
+
className
|
|
246
|
+
)}
|
|
247
|
+
{...props}
|
|
248
|
+
>
|
|
249
|
+
{children}
|
|
250
|
+
</TabsPrimitive.Trigger>
|
|
251
|
+
)
|
|
252
|
+
})
|
|
253
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
|
254
|
+
|
|
255
|
+
// ─── Content ──────────────────────────────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
export const TabsContent = React.forwardRef<
|
|
258
|
+
React.ElementRef<typeof TabsPrimitive.Content>,
|
|
259
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
|
260
|
+
>(({ className, ...props }, ref) => {
|
|
261
|
+
const { orientation } = React.useContext(TabsContext)
|
|
262
|
+
return (
|
|
263
|
+
<TabsPrimitive.Content
|
|
264
|
+
ref={ref}
|
|
265
|
+
className={cn(
|
|
266
|
+
// Horizontal stacks content under the list; vertical places it beside
|
|
267
|
+
// the list, so spacing is left to the consumer's row layout.
|
|
268
|
+
orientation === "vertical" ? "" : "ax:mt-4",
|
|
269
|
+
"ax:focus-visible:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring ax:focus-visible:ring-offset-2 ax:focus-visible:ring-offset-background",
|
|
270
|
+
className
|
|
271
|
+
)}
|
|
272
|
+
{...props}
|
|
273
|
+
/>
|
|
274
|
+
)
|
|
275
|
+
})
|
|
276
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { Testimonials } from "./testimonials"
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Testimonials> = {
|
|
6
|
+
title: "Components/Testimonials",
|
|
7
|
+
component: Testimonials,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
args: {
|
|
10
|
+
eyebrow: "Testimonials",
|
|
11
|
+
title: "Loved by teams everywhere",
|
|
12
|
+
items: [
|
|
13
|
+
{ quote: "Adres helped us ship a consistent UI across three brands.", name: "Sarah Chen", role: "Design Lead", company: "Madhmoun", rating: 5 },
|
|
14
|
+
{ quote: "The component library saved us months of work.", name: "Omar Ali", role: "Engineering Manager", company: "ThinkProp", rating: 5 },
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default meta
|
|
20
|
+
type Story = StoryObj<typeof Testimonials>
|
|
21
|
+
|
|
22
|
+
export const Default: Story = {}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Star } from "lucide-react"
|
|
5
|
+
import { motion } from "motion/react"
|
|
6
|
+
import { cn } from "../lib/cn"
|
|
7
|
+
import { cardCn } from "./card"
|
|
8
|
+
import { useReducedMotion } from "../hooks/use-reduced-motion"
|
|
9
|
+
import { stagger, variants, viewport as defaultViewport } from "../lib/motion"
|
|
10
|
+
|
|
11
|
+
export interface Testimonial {
|
|
12
|
+
quote: string
|
|
13
|
+
name: string
|
|
14
|
+
role?: string
|
|
15
|
+
company?: string
|
|
16
|
+
avatar?: string
|
|
17
|
+
rating?: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TestimonialsProps extends Omit<React.HTMLAttributes<HTMLElement>, "title"> {
|
|
21
|
+
eyebrow?: React.ReactNode
|
|
22
|
+
title?: React.ReactNode
|
|
23
|
+
items: Testimonial[]
|
|
24
|
+
columns?: 2 | 3
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function Testimonials({ className, eyebrow, title, items, columns = 3, ...props }: TestimonialsProps) {
|
|
28
|
+
const reduce = useReducedMotion()
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<section className={cn("ax:w-full ax:px-xl ax:md:px-3xl ax:py-5xl ax:md:py-6xl", className)} {...props}>
|
|
32
|
+
<div className="ax:mx-auto ax:max-w-7xl">
|
|
33
|
+
{(eyebrow || title) && (
|
|
34
|
+
<div className="ax:max-w-2xl ax:mb-4xl ax:space-y-sm">
|
|
35
|
+
{eyebrow && (
|
|
36
|
+
<span className="ax:inline-block ax:t-overline ax:text-primary">
|
|
37
|
+
{eyebrow}
|
|
38
|
+
</span>
|
|
39
|
+
)}
|
|
40
|
+
{title && (
|
|
41
|
+
<h2 className="ax:t-display-md ax:md:t-display-lg ax:text-foreground ax:[text-wrap:balance]">
|
|
42
|
+
{title}
|
|
43
|
+
</h2>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
)}
|
|
47
|
+
|
|
48
|
+
<motion.div
|
|
49
|
+
className={cn(
|
|
50
|
+
"ax:grid ax:gap-xl",
|
|
51
|
+
columns === 2 ? "ax:md:grid-cols-2" : "ax:md:grid-cols-2 ax:lg:grid-cols-3"
|
|
52
|
+
)}
|
|
53
|
+
{...(reduce ? {} : {
|
|
54
|
+
variants: stagger(0, 0.07),
|
|
55
|
+
initial: "hidden",
|
|
56
|
+
whileInView: "visible",
|
|
57
|
+
viewport: defaultViewport,
|
|
58
|
+
})}
|
|
59
|
+
>
|
|
60
|
+
{items.map((item, i) => (
|
|
61
|
+
<motion.figure
|
|
62
|
+
key={i}
|
|
63
|
+
{...(reduce ? {} : { variants: variants.fadeUp })}
|
|
64
|
+
className={cardCn("default", "xl", "ax:flex ax:flex-col ax:gap-md")}
|
|
65
|
+
>
|
|
66
|
+
{item.rating && (
|
|
67
|
+
<div className="ax:flex ax:gap-0.5">
|
|
68
|
+
{Array.from({ length: 5 }).map((_, j) => (
|
|
69
|
+
<Star
|
|
70
|
+
key={j}
|
|
71
|
+
className={cn(
|
|
72
|
+
"ax:size-4",
|
|
73
|
+
j < item.rating! ? "ax:fill-warning ax:text-warning-strong" : "ax:fill-muted ax:text-muted"
|
|
74
|
+
)}
|
|
75
|
+
/>
|
|
76
|
+
))}
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
<blockquote className="ax:flex-1 ax:t-body-sm ax:text-foreground">
|
|
80
|
+
"{item.quote}"
|
|
81
|
+
</blockquote>
|
|
82
|
+
<figcaption className="ax:flex ax:items-center ax:gap-sm">
|
|
83
|
+
{item.avatar ? (
|
|
84
|
+
<img
|
|
85
|
+
src={item.avatar}
|
|
86
|
+
alt={item.name}
|
|
87
|
+
className="ax:size-9 ax:rounded-full ax:object-cover ax:bg-muted"
|
|
88
|
+
/>
|
|
89
|
+
) : (
|
|
90
|
+
<div className="ax:size-9 ax:rounded-full ax:bg-primary/10 ax:text-primary ax:flex ax:items-center ax:justify-center ax:t-label-sm">
|
|
91
|
+
{item.name.charAt(0)}
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
<div>
|
|
95
|
+
<div className="ax:t-label-sm ax:text-foreground">{item.name}</div>
|
|
96
|
+
{(item.role || item.company) && (
|
|
97
|
+
<div className="ax:t-caption ax:text-muted-foreground">
|
|
98
|
+
{[item.role, item.company].filter(Boolean).join(" · ")}
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
</figcaption>
|
|
103
|
+
</motion.figure>
|
|
104
|
+
))}
|
|
105
|
+
</motion.div>
|
|
106
|
+
</div>
|
|
107
|
+
</section>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { TextLink } from "./text-link"
|
|
4
|
+
import { Text } from "./text"
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof TextLink> = {
|
|
7
|
+
title: "Components/TextLink",
|
|
8
|
+
component: TextLink,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
argTypes: {
|
|
11
|
+
color: { control: "select", options: ["primary", "default", "muted"] },
|
|
12
|
+
underline: { control: "select", options: ["hover", "always", "none"] },
|
|
13
|
+
weight: { control: "select", options: ["regular", "medium"] },
|
|
14
|
+
},
|
|
15
|
+
args: {
|
|
16
|
+
href: "#",
|
|
17
|
+
children: "Forgot password?",
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default meta
|
|
22
|
+
type Story = StoryObj<typeof TextLink>
|
|
23
|
+
|
|
24
|
+
export const Default: Story = {}
|
|
25
|
+
|
|
26
|
+
/** Inherits the surrounding size when `size` is omitted, so it never shifts the line. */
|
|
27
|
+
export const InSentence: Story = {
|
|
28
|
+
render: (args) => (
|
|
29
|
+
<Text as="p" size="2" color="muted">
|
|
30
|
+
Don't have an account? <TextLink {...args}>Create one</TextLink>
|
|
31
|
+
</Text>
|
|
32
|
+
),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const Colors: Story = {
|
|
36
|
+
render: () => (
|
|
37
|
+
<div style={{ display: "flex", gap: "1.5rem" }}>
|
|
38
|
+
<TextLink href="#">Primary</TextLink>
|
|
39
|
+
<TextLink href="#" color="default">Default</TextLink>
|
|
40
|
+
<TextLink href="#" color="muted">Muted</TextLink>
|
|
41
|
+
</div>
|
|
42
|
+
),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const External: Story = {
|
|
46
|
+
args: { href: "https://example.com", external: true, underline: "always", children: "Read the privacy policy" },
|
|
47
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import { renderToStaticMarkup } from "react-dom/server"
|
|
4
|
+
import { TextLink } from "./text-link"
|
|
5
|
+
|
|
6
|
+
describe("TextLink", () => {
|
|
7
|
+
it("adds safe new-tab attributes and a screen-reader hint when external", () => {
|
|
8
|
+
const html = renderToStaticMarkup(<TextLink href="https://example.com" external>Docs</TextLink>)
|
|
9
|
+
expect(html).toContain('target="_blank"')
|
|
10
|
+
expect(html).toContain('rel="noopener noreferrer"')
|
|
11
|
+
expect(html).toContain("(opens in a new tab)")
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it("inherits the surrounding text size unless size is set", () => {
|
|
15
|
+
expect(renderToStaticMarkup(<TextLink href="#">Link</TextLink>)).not.toContain("ax:t-body")
|
|
16
|
+
expect(renderToStaticMarkup(<TextLink href="#" size="2">Link</TextLink>)).toContain("ax:t-body-sm")
|
|
17
|
+
})
|
|
18
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
5
|
+
import { tv, type VariantProps } from "tailwind-variants"
|
|
6
|
+
import { cn } from "../lib/cn"
|
|
7
|
+
|
|
8
|
+
export type TextLinkColor = "primary" | "default" | "muted"
|
|
9
|
+
export type TextLinkUnderline = "hover" | "always" | "none"
|
|
10
|
+
export type TextLinkSize = "1" | "2" | "3" | "4" | "5"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Inline text link. Unlike Button's `link` variant it carries no height or
|
|
14
|
+
* padding, so it sits inside a sentence without shifting the line. `size`
|
|
15
|
+
* shares Text's body scale and is omitted by default so the link inherits the
|
|
16
|
+
* surrounding text size.
|
|
17
|
+
*/
|
|
18
|
+
export const textLinkVariants = tv({
|
|
19
|
+
base: [
|
|
20
|
+
"ax:cursor-pointer ax:rounded-xs ax:underline-offset-4 ax:decoration-1",
|
|
21
|
+
"ax:transition-colors ax:transition-snappy",
|
|
22
|
+
"ax:focus-visible:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring ax:focus-visible:ring-offset-2 ax:focus-visible:ring-offset-background",
|
|
23
|
+
],
|
|
24
|
+
variants: {
|
|
25
|
+
color: {
|
|
26
|
+
primary: "ax:text-primary-foreground",
|
|
27
|
+
default: "ax:text-foreground",
|
|
28
|
+
muted: "ax:text-muted-foreground ax:hover:text-foreground",
|
|
29
|
+
},
|
|
30
|
+
underline: {
|
|
31
|
+
hover: "ax:no-underline ax:hover:underline",
|
|
32
|
+
always: "ax:underline",
|
|
33
|
+
none: "ax:no-underline",
|
|
34
|
+
},
|
|
35
|
+
size: {
|
|
36
|
+
"1": "ax:t-body-xs",
|
|
37
|
+
"2": "ax:t-body-sm",
|
|
38
|
+
"3": "ax:t-body-md",
|
|
39
|
+
"4": "ax:t-body-lg",
|
|
40
|
+
"5": "ax:t-body-xl",
|
|
41
|
+
},
|
|
42
|
+
weight: {
|
|
43
|
+
regular: "ax:font-normal",
|
|
44
|
+
medium: "ax:font-medium",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
defaultVariants: {
|
|
48
|
+
color: "primary",
|
|
49
|
+
underline: "hover",
|
|
50
|
+
weight: "medium",
|
|
51
|
+
},
|
|
52
|
+
}, { twMerge: false })
|
|
53
|
+
|
|
54
|
+
export interface TextLinkProps
|
|
55
|
+
extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "color">,
|
|
56
|
+
VariantProps<typeof textLinkVariants> {
|
|
57
|
+
/** Render the child instead of an `<a>`, e.g. a framework router link. */
|
|
58
|
+
asChild?: boolean
|
|
59
|
+
/** Opens in a new tab with `rel="noopener noreferrer"` and announces that to screen readers. */
|
|
60
|
+
external?: boolean
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const TextLink = React.forwardRef<HTMLAnchorElement, TextLinkProps>(
|
|
64
|
+
({ className, color, underline, size, weight, asChild = false, external = false, children, ...props }, ref) => {
|
|
65
|
+
const Comp = asChild ? Slot : "a"
|
|
66
|
+
const externalProps = external ? { target: "_blank", rel: "noopener noreferrer" } : {}
|
|
67
|
+
return (
|
|
68
|
+
<Comp
|
|
69
|
+
ref={ref}
|
|
70
|
+
data-slot="text-link"
|
|
71
|
+
className={cn(textLinkVariants({ color, underline, size, weight }), className)}
|
|
72
|
+
{...externalProps}
|
|
73
|
+
{...props}
|
|
74
|
+
>
|
|
75
|
+
{/* Slot needs a single child to merge into, so the external hint only renders on a plain <a>. */}
|
|
76
|
+
{external && !asChild ? (
|
|
77
|
+
<>
|
|
78
|
+
{children}
|
|
79
|
+
<span className="ax:sr-only"> (opens in a new tab)</span>
|
|
80
|
+
</>
|
|
81
|
+
) : (
|
|
82
|
+
children
|
|
83
|
+
)}
|
|
84
|
+
</Comp>
|
|
85
|
+
)
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
TextLink.displayName = "TextLink"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { Text, type TextSize } from "./text"
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Text> = {
|
|
6
|
+
title: "Components/Text",
|
|
7
|
+
component: Text,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
args: {
|
|
10
|
+
children: "The quick brown fox jumps over the lazy dog.",
|
|
11
|
+
size: "3",
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default meta
|
|
16
|
+
type Story = StoryObj<typeof Text>
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {}
|
|
19
|
+
|
|
20
|
+
export const Muted: Story = {
|
|
21
|
+
args: { color: "muted", size: "2" },
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const SIZE_TO_T_CLASS: Record<TextSize, string> = {
|
|
25
|
+
"1": "ax:t-body-xs",
|
|
26
|
+
"2": "ax:t-body-sm",
|
|
27
|
+
"3": "ax:t-body-md",
|
|
28
|
+
"4": "ax:t-body-lg",
|
|
29
|
+
"5": "ax:t-body-xl",
|
|
30
|
+
"6": "ax:t-h1",
|
|
31
|
+
"7": "ax:t-display-sm",
|
|
32
|
+
"8": "ax:t-display-md",
|
|
33
|
+
"9": "ax:t-display-xl",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const Scale: Story = {
|
|
37
|
+
render: () => (
|
|
38
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
|
|
39
|
+
{Object.entries(SIZE_TO_T_CLASS).map(([size, tClass]) => (
|
|
40
|
+
<div key={size} style={{ display: "flex", alignItems: "baseline", gap: "1rem" }}>
|
|
41
|
+
<code style={{ minWidth: "8rem" }}>
|
|
42
|
+
size="{size}" -> {tClass}
|
|
43
|
+
</code>
|
|
44
|
+
<Text size={size as TextSize}>The quick brown fox jumps over the lazy dog.</Text>
|
|
45
|
+
</div>
|
|
46
|
+
))}
|
|
47
|
+
</div>
|
|
48
|
+
),
|
|
49
|
+
}
|