@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,35 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import { renderToStaticMarkup } from "react-dom/server"
|
|
4
|
+
import { DataList, DataListItem, DataListLabel, DataListValue } from "./data-list"
|
|
5
|
+
|
|
6
|
+
const list = (props: React.ComponentProps<typeof DataList>) => renderToStaticMarkup(
|
|
7
|
+
<DataList {...props}>
|
|
8
|
+
<DataListItem><DataListLabel>Trade licence</DataListLabel><DataListValue>2120561384412A</DataListValue></DataListItem>
|
|
9
|
+
</DataList>,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
describe("DataList grid", () => {
|
|
13
|
+
it("lays items out in columns without dividers", () => {
|
|
14
|
+
const html = list({ orientation: "grid", columns: 4 })
|
|
15
|
+
expect(html).toContain("ax:grid ax:gap-x-lg ax:grid-cols-2 ax:sm:grid-cols-4")
|
|
16
|
+
expect(html).not.toContain("ax:divide-y")
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it("stacks label above value and drops the horizontal label width", () => {
|
|
20
|
+
const html = list({ orientation: "grid" })
|
|
21
|
+
expect(html).not.toContain("ax:sm:flex-row")
|
|
22
|
+
expect(html).not.toContain("ax:sm:min-w-32")
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it("keeps the divided list as the default", () => {
|
|
26
|
+
expect(list({})).toContain("ax:divide-y")
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("shares one label column across horizontal rows from sm up", () => {
|
|
30
|
+
const html = list({})
|
|
31
|
+
expect(html).toContain("ax:sm:grid ax:sm:grid-cols-[auto_1fr]")
|
|
32
|
+
expect(html).toContain("ax:sm:grid-cols-subgrid")
|
|
33
|
+
expect(html).toContain("ax:sm:gap-x-sm")
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../lib/cn"
|
|
5
|
+
|
|
6
|
+
export type DataListSize = "sm" | "md" | "lg"
|
|
7
|
+
export type DataListOrientation = "horizontal" | "vertical" | "grid"
|
|
8
|
+
export type DataListColumns = 2 | 3 | 4
|
|
9
|
+
|
|
10
|
+
interface DataListContextValue {
|
|
11
|
+
size: DataListSize
|
|
12
|
+
orientation: DataListOrientation
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const DataListContext = React.createContext<DataListContextValue>({
|
|
16
|
+
size: "md",
|
|
17
|
+
orientation: "horizontal",
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// ─── Root ─────────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export interface DataListProps extends React.HTMLAttributes<HTMLDListElement> {
|
|
23
|
+
size?: DataListSize
|
|
24
|
+
/** `grid` lays items out in columns, label above value, without dividers -
|
|
25
|
+
* for short facts such as licence numbers or key dates. */
|
|
26
|
+
orientation?: DataListOrientation
|
|
27
|
+
/** Columns from `sm` up when `orientation="grid"`; two below `sm`. */
|
|
28
|
+
columns?: DataListColumns
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const GRID_COLUMNS: Record<DataListColumns, string> = {
|
|
32
|
+
2: "ax:grid-cols-2",
|
|
33
|
+
3: "ax:grid-cols-2 ax:sm:grid-cols-3",
|
|
34
|
+
4: "ax:grid-cols-2 ax:sm:grid-cols-4",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const DataList = React.forwardRef<HTMLDListElement, DataListProps>(
|
|
38
|
+
({ className, size = "md", orientation = "horizontal", columns = 2, ...props }, ref) => (
|
|
39
|
+
<DataListContext.Provider value={{ size, orientation }}>
|
|
40
|
+
<dl
|
|
41
|
+
ref={ref}
|
|
42
|
+
className={cn(
|
|
43
|
+
orientation === "grid"
|
|
44
|
+
? cn("ax:grid ax:gap-x-lg", GRID_COLUMNS[columns])
|
|
45
|
+
: cn(
|
|
46
|
+
"ax:divide-y ax:divide-border",
|
|
47
|
+
// Rows share these column tracks via `grid-cols-subgrid` on each
|
|
48
|
+
// DataListItem, so the label column's `auto` track sizes to the
|
|
49
|
+
// single widest label across every row, not a per-row guess.
|
|
50
|
+
orientation === "horizontal" && "ax:sm:grid ax:sm:grid-cols-[auto_1fr]",
|
|
51
|
+
),
|
|
52
|
+
className,
|
|
53
|
+
)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
</DataListContext.Provider>
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
DataList.displayName = "DataList"
|
|
60
|
+
|
|
61
|
+
// ─── Item ─────────────────────────────────────────────────────────────────────
|
|
62
|
+
//
|
|
63
|
+
// The middle (md) row uses `py-2.5` (a non-token half-step) so its visual
|
|
64
|
+
// rhythm matches the Table component's `md` row exactly. sm/lg map cleanly
|
|
65
|
+
// to the spacing scale.
|
|
66
|
+
|
|
67
|
+
const ITEM_PADDING: Record<DataListSize, string> = {
|
|
68
|
+
sm: "ax:py-xs", // 8px
|
|
69
|
+
md: "ax:py-2.5", // 10px (intentional half-step, matches Table md)
|
|
70
|
+
lg: "ax:py-sm", // 12px
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Label↔value gap when the item is stacked (vertical, and horizontal on mobile).
|
|
74
|
+
const ITEM_GAP: Record<DataListSize, string> = {
|
|
75
|
+
sm: "ax:gap-2xs", // 4px
|
|
76
|
+
md: "ax:gap-2xs", // 4px
|
|
77
|
+
lg: "ax:gap-xs", // 8px
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Label↔value gap in the row layout (horizontal from `sm` up) - a bit more
|
|
81
|
+
// than the stacked gap, since side-by-side text reads better with clearer
|
|
82
|
+
// separation than text stacked label-above-value does.
|
|
83
|
+
const ITEM_GAP_X: Record<DataListSize, string> = {
|
|
84
|
+
sm: "ax:sm:gap-x-xs", // 8px
|
|
85
|
+
md: "ax:sm:gap-x-sm", // 12px
|
|
86
|
+
lg: "ax:sm:gap-x-md", // 16px
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const DataListItem = React.forwardRef<
|
|
90
|
+
HTMLDivElement,
|
|
91
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
92
|
+
>(({ className, ...props }, ref) => {
|
|
93
|
+
const { size, orientation } = React.useContext(DataListContext)
|
|
94
|
+
return (
|
|
95
|
+
<div
|
|
96
|
+
ref={ref}
|
|
97
|
+
className={cn(
|
|
98
|
+
"ax:flex ax:flex-col",
|
|
99
|
+
// Horizontal is responsive: stacked (label above value) on mobile, then
|
|
100
|
+
// a baseline-aligned row from `sm` up. The row uses `grid-cols-subgrid`
|
|
101
|
+
// so its label/value cells adopt the DataList's shared column tracks
|
|
102
|
+
// instead of sizing independently per row.
|
|
103
|
+
orientation === "horizontal" && "ax:sm:grid ax:sm:grid-cols-subgrid ax:sm:col-span-full ax:sm:items-baseline",
|
|
104
|
+
ITEM_PADDING[size],
|
|
105
|
+
ITEM_GAP[size],
|
|
106
|
+
orientation === "horizontal" && ITEM_GAP_X[size],
|
|
107
|
+
className
|
|
108
|
+
)}
|
|
109
|
+
{...props}
|
|
110
|
+
/>
|
|
111
|
+
)
|
|
112
|
+
})
|
|
113
|
+
DataListItem.displayName = "DataListItem"
|
|
114
|
+
|
|
115
|
+
// ─── Label ────────────────────────────────────────────────────────────────────
|
|
116
|
+
//
|
|
117
|
+
// The label column's actual width comes from the DataListItem's subgrid (see
|
|
118
|
+
// above): it's an `auto` track, so it sizes to the single widest label across
|
|
119
|
+
// every row. `min-width` here is just a floor for when every label is short,
|
|
120
|
+
// so the column never gets uncomfortably narrow. It applies only from `sm` up;
|
|
121
|
+
// while stacked on mobile the label is a full-width block above the value, so
|
|
122
|
+
// it needs no minimum.
|
|
123
|
+
|
|
124
|
+
const LABEL_TEXT: Record<DataListSize, string> = {
|
|
125
|
+
sm: "ax:t-caption",
|
|
126
|
+
md: "ax:t-body-sm",
|
|
127
|
+
lg: "ax:t-body-sm",
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const LABEL_MIN_WIDTH: Record<DataListSize, string> = {
|
|
131
|
+
sm: "ax:sm:min-w-25", // 100px
|
|
132
|
+
md: "ax:sm:min-w-32", // 128px
|
|
133
|
+
lg: "ax:sm:min-w-36", // 144px
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export const DataListLabel = React.forwardRef<
|
|
137
|
+
HTMLElement,
|
|
138
|
+
React.HTMLAttributes<HTMLElement>
|
|
139
|
+
>(({ className, ...props }, ref) => {
|
|
140
|
+
const { size, orientation } = React.useContext(DataListContext)
|
|
141
|
+
return (
|
|
142
|
+
<dt
|
|
143
|
+
ref={ref as React.Ref<HTMLElement>}
|
|
144
|
+
className={cn(
|
|
145
|
+
"ax:text-muted-foreground",
|
|
146
|
+
LABEL_TEXT[size],
|
|
147
|
+
orientation === "horizontal" && LABEL_MIN_WIDTH[size],
|
|
148
|
+
className
|
|
149
|
+
)}
|
|
150
|
+
{...props}
|
|
151
|
+
/>
|
|
152
|
+
)
|
|
153
|
+
})
|
|
154
|
+
DataListLabel.displayName = "DataListLabel"
|
|
155
|
+
|
|
156
|
+
// ─── Value ────────────────────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
const VALUE_TEXT: Record<DataListSize, string> = {
|
|
159
|
+
sm: "ax:t-caption",
|
|
160
|
+
md: "ax:t-body-sm",
|
|
161
|
+
lg: "ax:t-body-md",
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const DataListValue = React.forwardRef<
|
|
165
|
+
HTMLElement,
|
|
166
|
+
React.HTMLAttributes<HTMLElement>
|
|
167
|
+
>(({ className, ...props }, ref) => {
|
|
168
|
+
const { size } = React.useContext(DataListContext)
|
|
169
|
+
return (
|
|
170
|
+
<dd
|
|
171
|
+
ref={ref as React.Ref<HTMLElement>}
|
|
172
|
+
className={cn(
|
|
173
|
+
"ax:text-foreground ax:font-medium ax:min-w-0",
|
|
174
|
+
VALUE_TEXT[size],
|
|
175
|
+
className
|
|
176
|
+
)}
|
|
177
|
+
{...props}
|
|
178
|
+
/>
|
|
179
|
+
)
|
|
180
|
+
})
|
|
181
|
+
DataListValue.displayName = "DataListValue"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { DataTable, rowStyle, type ColumnDef } from "./data-table"
|
|
4
|
+
|
|
5
|
+
type Row = { name: string; role: string; status: string }
|
|
6
|
+
|
|
7
|
+
const columns: ColumnDef<Row>[] = [
|
|
8
|
+
{ key: "name", header: "Name", accessor: (r) => r.name, sortable: true },
|
|
9
|
+
{ key: "role", header: "Role", accessor: (r) => r.role },
|
|
10
|
+
{ key: "status", header: "Status", accessor: (r) => <span className={rowStyle.muted}>{r.status}</span> },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
const data: Row[] = [
|
|
14
|
+
{ name: "Aisha Khan", role: "Admin", status: "Active" },
|
|
15
|
+
{ name: "Marco Silva", role: "Editor", status: "Active" },
|
|
16
|
+
{ name: "Leila Hassan", role: "Viewer", status: "Invited" },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
const meta: Meta<typeof DataTable<Row>> = {
|
|
20
|
+
title: "Components/DataTable",
|
|
21
|
+
component: DataTable,
|
|
22
|
+
tags: ["autodocs"],
|
|
23
|
+
args: { columns, data },
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default meta
|
|
27
|
+
type Story = StoryObj<typeof DataTable<Row>>
|
|
28
|
+
|
|
29
|
+
export const Default: Story = {}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { ChevronUp, ChevronDown, ChevronsUpDown } from "lucide-react"
|
|
5
|
+
import { cn } from "../lib/cn"
|
|
6
|
+
|
|
7
|
+
export type SortDirection = "asc" | "desc" | null
|
|
8
|
+
export type DataTableSize = "sm" | "md" | "lg"
|
|
9
|
+
export type DataTableAlign = "start" | "center" | "end"
|
|
10
|
+
|
|
11
|
+
export interface ColumnDef<T> {
|
|
12
|
+
key: string
|
|
13
|
+
header: React.ReactNode
|
|
14
|
+
accessor: (row: T) => React.ReactNode
|
|
15
|
+
sortable?: boolean
|
|
16
|
+
/** Logical inline alignment. `start` and `end` flip in RTL. */
|
|
17
|
+
align?: DataTableAlign
|
|
18
|
+
className?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DataTableProps<T> extends Omit<React.HTMLAttributes<HTMLDivElement>, "onClick"> {
|
|
22
|
+
columns: ColumnDef<T>[]
|
|
23
|
+
data: T[]
|
|
24
|
+
/** Visual density. Matches the Table component's size scale. */
|
|
25
|
+
size?: DataTableSize
|
|
26
|
+
/** Key-accessor for React list rendering. Defaults to row index. */
|
|
27
|
+
rowKey?: (row: T, index: number) => string | number
|
|
28
|
+
/** Per-row className. Lets callers visually mark rows (placeholders, status-specific tinting, etc.). */
|
|
29
|
+
rowClassName?: (row: T, index: number) => string | undefined
|
|
30
|
+
/** Row click handler. When provided, rows become clickable (cursor + role).
|
|
31
|
+
* Interactive children (buttons, links, menu triggers) should stopPropagation
|
|
32
|
+
* if they need to bypass the row click. */
|
|
33
|
+
onRowClick?: (row: T, index: number) => void
|
|
34
|
+
/** Shown when data is empty. */
|
|
35
|
+
emptyMessage?: string
|
|
36
|
+
/** Hide the header row entirely. */
|
|
37
|
+
headless?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Matches the Table component's CELL_PADDING map exactly so the two stay in
|
|
41
|
+
// visual sync: `py-1.5` and `py-2.5` are intentional half-steps.
|
|
42
|
+
const CELL_PADDING: Record<DataTableSize, string> = {
|
|
43
|
+
sm: "ax:px-sm ax:py-1.5",
|
|
44
|
+
md: "ax:px-md ax:py-2.5",
|
|
45
|
+
lg: "ax:px-lg ax:py-md",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Font-size + line-height per density. Applied to every <td> so column
|
|
49
|
+
// accessors inherit the right base and only need to layer weight/colour.
|
|
50
|
+
const CELL_TYPOGRAPHY: Record<DataTableSize, string> = {
|
|
51
|
+
sm: "ax:text-xs ax:leading-tight",
|
|
52
|
+
md: "ax:text-sm ax:leading-tight",
|
|
53
|
+
lg: "ax:text-base ax:leading-tight",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Named row-text variants for column accessors. They intentionally carry no
|
|
57
|
+
// font-size or line-height: those come from the table's size prop above.
|
|
58
|
+
export const rowStyle = {
|
|
59
|
+
title: "ax:font-medium ax:text-foreground-strong",
|
|
60
|
+
default: "ax:font-normal ax:text-foreground",
|
|
61
|
+
muted: "ax:font-normal ax:text-muted-foreground",
|
|
62
|
+
} as const
|
|
63
|
+
|
|
64
|
+
function SortIcon({ direction }: { direction: SortDirection }) {
|
|
65
|
+
if (direction === "asc") return <ChevronUp className="ax:size-3.5 ax:shrink-0" />
|
|
66
|
+
if (direction === "desc") return <ChevronDown className="ax:size-3.5 ax:shrink-0" />
|
|
67
|
+
return <ChevronsUpDown className="ax:size-3.5 ax:shrink-0 ax:text-muted-foreground/50" />
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function alignClass(align?: DataTableAlign): string {
|
|
71
|
+
if (align === "center") return "ax:text-center"
|
|
72
|
+
if (align === "end") return "ax:text-end"
|
|
73
|
+
return "ax:text-start"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function DataTable<T>({
|
|
77
|
+
columns,
|
|
78
|
+
data,
|
|
79
|
+
size = "md",
|
|
80
|
+
className,
|
|
81
|
+
rowKey,
|
|
82
|
+
rowClassName,
|
|
83
|
+
onRowClick,
|
|
84
|
+
emptyMessage = "No results.",
|
|
85
|
+
headless = false,
|
|
86
|
+
...props
|
|
87
|
+
}: DataTableProps<T>) {
|
|
88
|
+
const [sortKey, setSortKey] = React.useState<string | null>(null)
|
|
89
|
+
const [sortDir, setSortDir] = React.useState<SortDirection>(null)
|
|
90
|
+
|
|
91
|
+
const handleSort = (key: string) => {
|
|
92
|
+
if (sortKey !== key) {
|
|
93
|
+
setSortKey(key)
|
|
94
|
+
setSortDir("asc")
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
if (sortDir === "asc") { setSortDir("desc"); return }
|
|
98
|
+
if (sortDir === "desc") { setSortKey(null); setSortDir(null); return }
|
|
99
|
+
setSortDir("asc")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const sortedData = React.useMemo(() => {
|
|
103
|
+
if (!sortKey || !sortDir) return data
|
|
104
|
+
const col = columns.find((c) => c.key === sortKey)
|
|
105
|
+
if (!col) return data
|
|
106
|
+
return [...data].sort((a, b) => {
|
|
107
|
+
const av = col.accessor(a)
|
|
108
|
+
const bv = col.accessor(b)
|
|
109
|
+
const as = String(av ?? "")
|
|
110
|
+
const bs = String(bv ?? "")
|
|
111
|
+
const cmp = as.localeCompare(bs, undefined, { numeric: true })
|
|
112
|
+
return sortDir === "asc" ? cmp : -cmp
|
|
113
|
+
})
|
|
114
|
+
}, [data, sortKey, sortDir, columns])
|
|
115
|
+
|
|
116
|
+
const cellPad = CELL_PADDING[size]
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<div
|
|
120
|
+
data-slot="data-table"
|
|
121
|
+
className={cn("ax:w-full ax:overflow-auto ax:rounded-lg ax:border ax:border-border ax:bg-card", className)}
|
|
122
|
+
{...props}
|
|
123
|
+
>
|
|
124
|
+
<table className="ax:w-full">
|
|
125
|
+
{!headless && (
|
|
126
|
+
<thead className="ax:sticky ax:top-0 ax:z-10 ax:border-b ax:border-border ax:bg-muted/50">
|
|
127
|
+
<tr>
|
|
128
|
+
{columns.map((col) => (
|
|
129
|
+
<th
|
|
130
|
+
key={col.key}
|
|
131
|
+
className={cn(
|
|
132
|
+
cellPad,
|
|
133
|
+
CELL_TYPOGRAPHY[size],
|
|
134
|
+
"ax:font-medium ax:text-muted-foreground",
|
|
135
|
+
alignClass(col.align),
|
|
136
|
+
col.sortable && "ax:cursor-pointer ax:select-none ax:hover:text-foreground ax:transition-colors",
|
|
137
|
+
col.className
|
|
138
|
+
)}
|
|
139
|
+
onClick={col.sortable ? () => handleSort(col.key) : undefined}
|
|
140
|
+
aria-sort={
|
|
141
|
+
sortKey === col.key
|
|
142
|
+
? sortDir === "asc"
|
|
143
|
+
? "ascending"
|
|
144
|
+
: "descending"
|
|
145
|
+
: undefined
|
|
146
|
+
}
|
|
147
|
+
>
|
|
148
|
+
<span className="ax:inline-flex ax:items-center ax:gap-1.5">
|
|
149
|
+
{col.header}
|
|
150
|
+
{col.sortable && (
|
|
151
|
+
<SortIcon direction={sortKey === col.key ? sortDir : null} />
|
|
152
|
+
)}
|
|
153
|
+
</span>
|
|
154
|
+
</th>
|
|
155
|
+
))}
|
|
156
|
+
</tr>
|
|
157
|
+
</thead>
|
|
158
|
+
)}
|
|
159
|
+
<tbody className="ax:divide-y ax:divide-border">
|
|
160
|
+
{sortedData.length === 0 ? (
|
|
161
|
+
<tr>
|
|
162
|
+
<td
|
|
163
|
+
colSpan={columns.length}
|
|
164
|
+
className={cn(cellPad, "ax:text-center ax:text-muted-foreground ax:py-2xl")}
|
|
165
|
+
>
|
|
166
|
+
{emptyMessage}
|
|
167
|
+
</td>
|
|
168
|
+
</tr>
|
|
169
|
+
) : (
|
|
170
|
+
sortedData.map((row, i) => (
|
|
171
|
+
<tr
|
|
172
|
+
key={rowKey ? rowKey(row, i) : i}
|
|
173
|
+
className={cn(
|
|
174
|
+
"ax:hover:bg-muted/40 ax:transition-colors",
|
|
175
|
+
onRowClick && "ax:cursor-pointer",
|
|
176
|
+
rowClassName?.(row, i),
|
|
177
|
+
)}
|
|
178
|
+
onClick={onRowClick ? () => onRowClick(row, i) : undefined}
|
|
179
|
+
>
|
|
180
|
+
{columns.map((col) => (
|
|
181
|
+
<td
|
|
182
|
+
key={col.key}
|
|
183
|
+
className={cn(
|
|
184
|
+
cellPad,
|
|
185
|
+
CELL_TYPOGRAPHY[size],
|
|
186
|
+
alignClass(col.align),
|
|
187
|
+
col.className
|
|
188
|
+
)}
|
|
189
|
+
>
|
|
190
|
+
{col.accessor(row)}
|
|
191
|
+
</td>
|
|
192
|
+
))}
|
|
193
|
+
</tr>
|
|
194
|
+
))
|
|
195
|
+
)}
|
|
196
|
+
</tbody>
|
|
197
|
+
</table>
|
|
198
|
+
</div>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { Button } from "./button"
|
|
4
|
+
import {
|
|
5
|
+
Dialog,
|
|
6
|
+
DialogContent,
|
|
7
|
+
DialogDescription,
|
|
8
|
+
DialogFooter,
|
|
9
|
+
DialogHeader,
|
|
10
|
+
DialogTitle,
|
|
11
|
+
DialogTrigger,
|
|
12
|
+
} from "./dialog"
|
|
13
|
+
|
|
14
|
+
const meta: Meta<typeof Dialog> = {
|
|
15
|
+
title: "Components/Dialog",
|
|
16
|
+
component: Dialog,
|
|
17
|
+
tags: ["autodocs"],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default meta
|
|
21
|
+
type Story = StoryObj<typeof Dialog>
|
|
22
|
+
|
|
23
|
+
export const Default: Story = {
|
|
24
|
+
render: () => (
|
|
25
|
+
<Dialog>
|
|
26
|
+
<DialogTrigger asChild>
|
|
27
|
+
<Button>Open dialog</Button>
|
|
28
|
+
</DialogTrigger>
|
|
29
|
+
<DialogContent>
|
|
30
|
+
<DialogHeader>
|
|
31
|
+
<DialogTitle>Edit profile</DialogTitle>
|
|
32
|
+
<DialogDescription>Make changes to your profile here.</DialogDescription>
|
|
33
|
+
</DialogHeader>
|
|
34
|
+
<p className="ax:t-body-sm ax:text-muted-foreground">Dialog body content.</p>
|
|
35
|
+
<DialogFooter>
|
|
36
|
+
<Button variant="outline">Cancel</Button>
|
|
37
|
+
<Button>Save</Button>
|
|
38
|
+
</DialogFooter>
|
|
39
|
+
</DialogContent>
|
|
40
|
+
</Dialog>
|
|
41
|
+
),
|
|
42
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
|
5
|
+
import { X } from "lucide-react"
|
|
6
|
+
import { cn } from "../lib/cn"
|
|
7
|
+
|
|
8
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
export type DialogSize = "sm" | "md" | "lg"
|
|
11
|
+
|
|
12
|
+
const DialogSizeContext = React.createContext<DialogSize>("md")
|
|
13
|
+
|
|
14
|
+
// ─── Size tokens ──────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
const MAX_WIDTH: Record<DialogSize, string> = {
|
|
17
|
+
sm: "ax:max-w-xs", // 320px - compact confirmations / alerts
|
|
18
|
+
md: "ax:max-w-md", // 448px - DEFAULT - typical form dialogs
|
|
19
|
+
lg: "ax:max-w-3xl", // 768px - wide layouts / multi-column content
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const PADDING: Record<DialogSize, string> = {
|
|
23
|
+
sm: "ax:p-lg", // 20px
|
|
24
|
+
md: "ax:p-xl", // 24px
|
|
25
|
+
lg: "ax:p-2xl", // 32px
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const GAP: Record<DialogSize, string> = {
|
|
29
|
+
sm: "ax:gap-sm", // 12px
|
|
30
|
+
md: "ax:gap-md", // 16px
|
|
31
|
+
lg: "ax:gap-xl", // 24px
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const TITLE_SIZE: Record<DialogSize, string> = {
|
|
35
|
+
sm: "ax:t-h3",
|
|
36
|
+
md: "ax:t-h3",
|
|
37
|
+
lg: "ax:t-h2",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const DESCRIPTION_SIZE: Record<DialogSize, string> = {
|
|
41
|
+
sm: "ax:t-body-sm",
|
|
42
|
+
md: "ax:t-body-sm",
|
|
43
|
+
lg: "ax:t-body-md",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const CLOSE_ICON: Record<DialogSize, string> = {
|
|
47
|
+
sm: "ax:size-4",
|
|
48
|
+
md: "ax:size-4",
|
|
49
|
+
lg: "ax:size-5",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Footer top-margin scales with size so the gap above the action row feels
|
|
53
|
+
// proportional to the dialog's interior padding.
|
|
54
|
+
const FOOTER_MT: Record<DialogSize, string> = {
|
|
55
|
+
sm: "ax:mt-sm",
|
|
56
|
+
md: "ax:mt-md",
|
|
57
|
+
lg: "ax:mt-md",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── Primitives (unchanged) ───────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
export const Dialog = DialogPrimitive.Root
|
|
63
|
+
export const DialogTrigger = DialogPrimitive.Trigger
|
|
64
|
+
export const DialogPortal = DialogPrimitive.Portal
|
|
65
|
+
export const DialogClose = DialogPrimitive.Close
|
|
66
|
+
|
|
67
|
+
// ─── Overlay ─────────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
export const DialogOverlay = React.forwardRef<
|
|
70
|
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
71
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
72
|
+
>(({ className, ...props }, ref) => (
|
|
73
|
+
<DialogPrimitive.Overlay
|
|
74
|
+
ref={ref}
|
|
75
|
+
className={cn(
|
|
76
|
+
// `--foreground` flips light in dark mode, which would tint the scrim
|
|
77
|
+
// whitish, so use a black scrim in dark at the same opacity + blur.
|
|
78
|
+
"ax:fixed ax:inset-0 ax:z-[1100] ax:bg-foreground/40 ax:dark:bg-black/40 ax:backdrop-blur-sm",
|
|
79
|
+
"ax:data-[state=open]:animate-in ax:data-[state=closed]:animate-out",
|
|
80
|
+
"ax:data-[state=open]:fade-in-0 ax:data-[state=closed]:fade-out-0",
|
|
81
|
+
"ax:[animation-duration:var(--motion-base)] ax:[animation-timing-function:var(--motion-ease-out)]",
|
|
82
|
+
"ax:motion-reduce:animate-none",
|
|
83
|
+
className
|
|
84
|
+
)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
))
|
|
88
|
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
|
89
|
+
|
|
90
|
+
// ─── Content ─────────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
export interface DialogContentProps
|
|
93
|
+
extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
|
|
94
|
+
size?: DialogSize
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const DialogContent = React.forwardRef<
|
|
98
|
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
99
|
+
DialogContentProps
|
|
100
|
+
>(({ className, children, size = "md", ...props }, ref) => (
|
|
101
|
+
<DialogSizeContext.Provider value={size}>
|
|
102
|
+
<DialogPortal>
|
|
103
|
+
<DialogOverlay />
|
|
104
|
+
<DialogPrimitive.Content
|
|
105
|
+
ref={ref}
|
|
106
|
+
className={cn(
|
|
107
|
+
"ax:fixed ax:left-1/2 ax:top-1/2 ax:z-[1100] ax:grid ax:w-full ax:-translate-x-1/2 ax:-translate-y-1/2",
|
|
108
|
+
"ax:border ax:border-border ax:bg-card ax:shadow-lg ax:rounded-lg",
|
|
109
|
+
MAX_WIDTH[size],
|
|
110
|
+
PADDING[size],
|
|
111
|
+
GAP[size],
|
|
112
|
+
"ax:data-[state=open]:animate-in ax:data-[state=closed]:animate-out",
|
|
113
|
+
"ax:data-[state=open]:fade-in-0 ax:data-[state=closed]:fade-out-0",
|
|
114
|
+
"ax:data-[state=open]:zoom-in-[0.96] ax:data-[state=closed]:zoom-out-[0.96]",
|
|
115
|
+
"ax:data-[state=open]:slide-in-from-bottom-2 ax:data-[state=closed]:slide-out-to-bottom-1",
|
|
116
|
+
"ax:[animation-duration:var(--motion-base)] ax:[animation-timing-function:var(--motion-ease-out)]",
|
|
117
|
+
"ax:motion-reduce:animate-none",
|
|
118
|
+
className
|
|
119
|
+
)}
|
|
120
|
+
{...props}
|
|
121
|
+
>
|
|
122
|
+
{children}
|
|
123
|
+
<DialogPrimitive.Close
|
|
124
|
+
className={cn(
|
|
125
|
+
"ax:absolute ax:right-md ax:top-md ax:rounded-sm ax:opacity-70 ax:transition-opacity",
|
|
126
|
+
"ax:hover:opacity-100 ax:focus:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring ax:focus-visible:ring-offset-2",
|
|
127
|
+
"ax:disabled:pointer-events-none"
|
|
128
|
+
)}
|
|
129
|
+
>
|
|
130
|
+
<X className={CLOSE_ICON[size]} aria-hidden="true" />
|
|
131
|
+
<span className="ax:sr-only">Close</span>
|
|
132
|
+
</DialogPrimitive.Close>
|
|
133
|
+
</DialogPrimitive.Content>
|
|
134
|
+
</DialogPortal>
|
|
135
|
+
</DialogSizeContext.Provider>
|
|
136
|
+
))
|
|
137
|
+
DialogContent.displayName = DialogPrimitive.Content.displayName
|
|
138
|
+
|
|
139
|
+
// ─── Header / Footer ─────────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
export const DialogHeader = React.forwardRef<
|
|
142
|
+
HTMLDivElement,
|
|
143
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
144
|
+
>(({ className, ...props }, ref) => (
|
|
145
|
+
<div ref={ref} className={cn("ax:flex ax:flex-col ax:gap-md ax:text-left", className)} {...props} />
|
|
146
|
+
))
|
|
147
|
+
DialogHeader.displayName = "DialogHeader"
|
|
148
|
+
|
|
149
|
+
export const DialogFooter = React.forwardRef<
|
|
150
|
+
HTMLDivElement,
|
|
151
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
152
|
+
>(({ className, ...props }, ref) => {
|
|
153
|
+
const size = React.useContext(DialogSizeContext)
|
|
154
|
+
return (
|
|
155
|
+
<div
|
|
156
|
+
ref={ref}
|
|
157
|
+
className={cn(
|
|
158
|
+
"ax:flex ax:flex-col-reverse ax:gap-xs ax:sm:flex-row ax:sm:justify-end",
|
|
159
|
+
FOOTER_MT[size],
|
|
160
|
+
className
|
|
161
|
+
)}
|
|
162
|
+
{...props}
|
|
163
|
+
/>
|
|
164
|
+
)
|
|
165
|
+
})
|
|
166
|
+
DialogFooter.displayName = "DialogFooter"
|
|
167
|
+
|
|
168
|
+
// ─── Title ───────────────────────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
export const DialogTitle = React.forwardRef<
|
|
171
|
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
172
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
173
|
+
>(({ className, ...props }, ref) => {
|
|
174
|
+
const size = React.useContext(DialogSizeContext)
|
|
175
|
+
return (
|
|
176
|
+
<DialogPrimitive.Title
|
|
177
|
+
ref={ref}
|
|
178
|
+
className={cn(TITLE_SIZE[size], className)}
|
|
179
|
+
{...props}
|
|
180
|
+
/>
|
|
181
|
+
)
|
|
182
|
+
})
|
|
183
|
+
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
|
184
|
+
|
|
185
|
+
// ─── Description ─────────────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
export const DialogDescription = React.forwardRef<
|
|
188
|
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
189
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
190
|
+
>(({ className, ...props }, ref) => {
|
|
191
|
+
const size = React.useContext(DialogSizeContext)
|
|
192
|
+
return (
|
|
193
|
+
<DialogPrimitive.Description
|
|
194
|
+
ref={ref}
|
|
195
|
+
className={cn("ax:text-foreground", DESCRIPTION_SIZE[size], className)}
|
|
196
|
+
{...props}
|
|
197
|
+
/>
|
|
198
|
+
)
|
|
199
|
+
})
|
|
200
|
+
DialogDescription.displayName = DialogPrimitive.Description.displayName
|