@adres/mgda-ui 0.0.0-stage → 0.0.2
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,64 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "../lib/cn"
|
|
3
|
+
|
|
4
|
+
export interface PageHeaderProps
|
|
5
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
|
|
6
|
+
/** Page or section title. Rendered as an `<h1>` with `t-h1` typography. */
|
|
7
|
+
title: React.ReactNode
|
|
8
|
+
/** Optional subtitle / context line shown directly under the title. */
|
|
9
|
+
subtitle?: React.ReactNode
|
|
10
|
+
/**
|
|
11
|
+
* Right-aligned slot - typically `<Button>`s, a search input, a `Select`,
|
|
12
|
+
* or any other action(s) the page exposes at the top. Wraps to a new line
|
|
13
|
+
* on narrow viewports rather than truncating.
|
|
14
|
+
*/
|
|
15
|
+
children?: React.ReactNode
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* PageHeader - top-of-content heading row.
|
|
20
|
+
*
|
|
21
|
+
* Pairs an h1 title (using `t-h1`) with an optional subtitle on the start
|
|
22
|
+
* side and a free-form actions slot on the end side. Designed to sit at the
|
|
23
|
+
* top of a page or major section inside an app shell.
|
|
24
|
+
*
|
|
25
|
+
* Layout:
|
|
26
|
+
* • Title + subtitle take the available inline space (`flex-1`).
|
|
27
|
+
* • Actions sit on the inline-end (`shrink-0`) and wrap onto a new line on
|
|
28
|
+
* narrow viewports.
|
|
29
|
+
* • Title and actions are baseline-aligned to the bottom of the row so a
|
|
30
|
+
* tall title (multi-line wrap) keeps actions anchored to its baseline.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* <PageHeader title="Students" subtitle="38 active learners">
|
|
34
|
+
* <Button variant="outline">Import CSV</Button>
|
|
35
|
+
* <Button>Invite students</Button>
|
|
36
|
+
* </PageHeader>
|
|
37
|
+
*/
|
|
38
|
+
export const PageHeader = React.forwardRef<HTMLDivElement, PageHeaderProps>(
|
|
39
|
+
({ className, title, subtitle, children, ...props }, ref) => {
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
ref={ref}
|
|
43
|
+
className={cn(
|
|
44
|
+
"ax:pb-lg ax:flex ax:flex-wrap ax:items-start ax:gap-md ax:justify-between",
|
|
45
|
+
className,
|
|
46
|
+
)}
|
|
47
|
+
{...props}
|
|
48
|
+
>
|
|
49
|
+
<div className="ax:flex ax:flex-col ax:gap-xs ax:flex-1 ax:min-w-0">
|
|
50
|
+
<h1 className="ax:t-h1">{title}</h1>
|
|
51
|
+
{subtitle && (
|
|
52
|
+
<p className="ax:t-body-sm ax:text-muted-foreground">{subtitle}</p>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
{children && (
|
|
56
|
+
<div className="ax:flex ax:items-center ax:gap-md ax:shrink-0 ax:flex-wrap">
|
|
57
|
+
{children}
|
|
58
|
+
</div>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
PageHeader.displayName = "PageHeader"
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import {
|
|
4
|
+
Pagination, PaginationContent, PaginationItem, PaginationLink,
|
|
5
|
+
PaginationPrevious, PaginationNext, PaginationEllipsis,
|
|
6
|
+
} from "./pagination"
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof Pagination> = {
|
|
9
|
+
title: "Components/Pagination",
|
|
10
|
+
component: Pagination,
|
|
11
|
+
tags: ["autodocs"],
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default meta
|
|
15
|
+
type Story = StoryObj<typeof Pagination>
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
render: () => {
|
|
19
|
+
const [page, setPage] = React.useState(1)
|
|
20
|
+
const pageCount = 5
|
|
21
|
+
return (
|
|
22
|
+
<Pagination>
|
|
23
|
+
<PaginationContent>
|
|
24
|
+
<PaginationItem>
|
|
25
|
+
<PaginationPrevious disabled={page === 1} onClick={() => setPage((p) => p - 1)} />
|
|
26
|
+
</PaginationItem>
|
|
27
|
+
{Array.from({ length: pageCount }, (_, i) => i + 1).map((n) => (
|
|
28
|
+
<PaginationItem key={n}>
|
|
29
|
+
<PaginationLink isActive={n === page} onClick={() => setPage(n)}>
|
|
30
|
+
{n}
|
|
31
|
+
</PaginationLink>
|
|
32
|
+
</PaginationItem>
|
|
33
|
+
))}
|
|
34
|
+
<PaginationItem>
|
|
35
|
+
<PaginationNext disabled={page === pageCount} onClick={() => setPage((p) => p + 1)} />
|
|
36
|
+
</PaginationItem>
|
|
37
|
+
</PaginationContent>
|
|
38
|
+
</Pagination>
|
|
39
|
+
)
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const WithEllipsis: Story = {
|
|
44
|
+
render: () => {
|
|
45
|
+
const [page, setPage] = React.useState(6)
|
|
46
|
+
const pageCount = 20
|
|
47
|
+
|
|
48
|
+
const link = (n: number) => (
|
|
49
|
+
<PaginationItem key={n}>
|
|
50
|
+
<PaginationLink isActive={n === page} onClick={() => setPage(n)}>{n}</PaginationLink>
|
|
51
|
+
</PaginationItem>
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Pagination>
|
|
56
|
+
<PaginationContent>
|
|
57
|
+
<PaginationItem>
|
|
58
|
+
<PaginationPrevious disabled={page === 1} onClick={() => setPage((p) => p - 1)} />
|
|
59
|
+
</PaginationItem>
|
|
60
|
+
{link(1)}
|
|
61
|
+
{page > 3 && (
|
|
62
|
+
<PaginationItem><PaginationEllipsis /></PaginationItem>
|
|
63
|
+
)}
|
|
64
|
+
{[page - 1, page, page + 1]
|
|
65
|
+
.filter((n) => n > 1 && n < pageCount)
|
|
66
|
+
.map(link)}
|
|
67
|
+
{page < pageCount - 2 && (
|
|
68
|
+
<PaginationItem><PaginationEllipsis /></PaginationItem>
|
|
69
|
+
)}
|
|
70
|
+
{link(pageCount)}
|
|
71
|
+
<PaginationItem>
|
|
72
|
+
<PaginationNext disabled={page === pageCount} onClick={() => setPage((p) => p + 1)} />
|
|
73
|
+
</PaginationItem>
|
|
74
|
+
</PaginationContent>
|
|
75
|
+
</Pagination>
|
|
76
|
+
)
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const Small: Story = {
|
|
81
|
+
render: () => (
|
|
82
|
+
<Pagination>
|
|
83
|
+
<PaginationContent>
|
|
84
|
+
<PaginationItem><PaginationPrevious size="sm" /></PaginationItem>
|
|
85
|
+
{[1, 2, 3].map((n) => (
|
|
86
|
+
<PaginationItem key={n}>
|
|
87
|
+
<PaginationLink size="sm" isActive={n === 1}>{n}</PaginationLink>
|
|
88
|
+
</PaginationItem>
|
|
89
|
+
))}
|
|
90
|
+
<PaginationItem><PaginationNext size="sm" /></PaginationItem>
|
|
91
|
+
</PaginationContent>
|
|
92
|
+
</Pagination>
|
|
93
|
+
),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const DisabledEdges: Story = {
|
|
97
|
+
render: () => (
|
|
98
|
+
<Pagination>
|
|
99
|
+
<PaginationContent>
|
|
100
|
+
<PaginationItem><PaginationPrevious disabled /></PaginationItem>
|
|
101
|
+
{[1, 2, 3].map((n) => (
|
|
102
|
+
<PaginationItem key={n}>
|
|
103
|
+
<PaginationLink isActive={n === 1}>{n}</PaginationLink>
|
|
104
|
+
</PaginationItem>
|
|
105
|
+
))}
|
|
106
|
+
<PaginationItem><PaginationNext /></PaginationItem>
|
|
107
|
+
</PaginationContent>
|
|
108
|
+
</Pagination>
|
|
109
|
+
),
|
|
110
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import { renderToStaticMarkup } from "react-dom/server"
|
|
4
|
+
import {
|
|
5
|
+
Pagination, PaginationContent, PaginationItem, PaginationLink,
|
|
6
|
+
PaginationPrevious, PaginationNext,
|
|
7
|
+
} from "./pagination"
|
|
8
|
+
|
|
9
|
+
describe("Pagination", () => {
|
|
10
|
+
it("renders a nav landmark labelled for pagination", () => {
|
|
11
|
+
const html = renderToStaticMarkup(
|
|
12
|
+
<Pagination>
|
|
13
|
+
<PaginationContent>
|
|
14
|
+
<PaginationItem><PaginationLink>1</PaginationLink></PaginationItem>
|
|
15
|
+
</PaginationContent>
|
|
16
|
+
</Pagination>,
|
|
17
|
+
)
|
|
18
|
+
expect(html).toContain('role="navigation"')
|
|
19
|
+
expect(html).toContain('aria-label="pagination"')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it("marks the active page with aria-current and the active fill", () => {
|
|
23
|
+
const html = renderToStaticMarkup(<PaginationLink isActive>3</PaginationLink>)
|
|
24
|
+
expect(html).toContain('aria-current="page"')
|
|
25
|
+
expect(html).toContain("ax:bg-primary-weaker")
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("leaves aria-current off inactive pages", () => {
|
|
29
|
+
const html = renderToStaticMarkup(<PaginationLink>3</PaginationLink>)
|
|
30
|
+
expect(html).not.toContain("aria-current")
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it("defaults Previous/Next labels and lets them be overridden", () => {
|
|
34
|
+
const defaults = renderToStaticMarkup(<PaginationPrevious />)
|
|
35
|
+
expect(defaults).toContain("Previous")
|
|
36
|
+
|
|
37
|
+
const custom = renderToStaticMarkup(<PaginationNext aria-label="Older results">Older</PaginationNext>)
|
|
38
|
+
expect(custom).toContain("Older")
|
|
39
|
+
expect(custom).toContain('aria-label="Older results"')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it("passes disabled through to the native button", () => {
|
|
43
|
+
const html = renderToStaticMarkup(<PaginationPrevious disabled />)
|
|
44
|
+
expect(html).toContain("disabled")
|
|
45
|
+
})
|
|
46
|
+
})
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
5
|
+
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
|
|
6
|
+
import { tv, type VariantProps } from "tailwind-variants"
|
|
7
|
+
import { cn } from "../lib/cn"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Pagination - a composable set of parts (Pagination, PaginationContent,
|
|
11
|
+
* PaginationItem, PaginationLink, PaginationPrevious, PaginationNext,
|
|
12
|
+
* PaginationEllipsis), following the shadcn/ui pattern: plain semantic
|
|
13
|
+
* elements (nav/ul/li/button) styled with design-system tokens, no Radix
|
|
14
|
+
* primitive underneath - page navigation doesn't need focus-trapping or
|
|
15
|
+
* portal behaviour, just a nav landmark and native buttons.
|
|
16
|
+
*
|
|
17
|
+
* Ships the primitives only, not a page-range calculator: compose the
|
|
18
|
+
* `PaginationItem`/`PaginationEllipsis` list yourself from your own page
|
|
19
|
+
* count, the same way shadcn's docs do.
|
|
20
|
+
*/
|
|
21
|
+
export interface PaginationProps extends React.ComponentPropsWithoutRef<"nav"> {}
|
|
22
|
+
|
|
23
|
+
export const Pagination = React.forwardRef<HTMLElement, PaginationProps>(
|
|
24
|
+
({ className, ...props }, ref) => (
|
|
25
|
+
<nav
|
|
26
|
+
ref={ref}
|
|
27
|
+
role="navigation"
|
|
28
|
+
aria-label="pagination"
|
|
29
|
+
className={cn("ax:mx-auto ax:flex ax:w-full ax:justify-center", className)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
Pagination.displayName = "Pagination"
|
|
35
|
+
|
|
36
|
+
export const PaginationContent = React.forwardRef<HTMLUListElement, React.ComponentPropsWithoutRef<"ul">>(
|
|
37
|
+
({ className, ...props }, ref) => (
|
|
38
|
+
<ul ref={ref} className={cn("ax:flex ax:flex-row ax:items-center ax:gap-1", className)} {...props} />
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
PaginationContent.displayName = "PaginationContent"
|
|
42
|
+
|
|
43
|
+
export const PaginationItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<"li">>(
|
|
44
|
+
(props, ref) => <li ref={ref} {...props} />
|
|
45
|
+
)
|
|
46
|
+
PaginationItem.displayName = "PaginationItem"
|
|
47
|
+
|
|
48
|
+
export const paginationLinkVariants = tv({
|
|
49
|
+
base: [
|
|
50
|
+
"ax:inline-flex ax:items-center ax:justify-center ax:gap-xs ax:whitespace-nowrap ax:select-none ax:cursor-pointer",
|
|
51
|
+
"ax:font-medium ax:leading-none ax:text-sm ax:rounded-(--button-radius)",
|
|
52
|
+
"ax:transition-colors ax:transition-snappy",
|
|
53
|
+
"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",
|
|
54
|
+
"ax:disabled:pointer-events-none ax:disabled:opacity-50",
|
|
55
|
+
"ax:[&_svg]:pointer-events-none ax:[&_svg]:size-4 ax:[&_svg]:shrink-0",
|
|
56
|
+
],
|
|
57
|
+
variants: {
|
|
58
|
+
isActive: {
|
|
59
|
+
// Mirrors Button's primary fill: emphasis aliases auto-invert in dark.
|
|
60
|
+
true: "ax:bg-primary-weaker ax:text-primary-foreground ax:dark:bg-primary-strong ax:dark:text-primary-weaker",
|
|
61
|
+
false: "ax:bg-transparent ax:text-foreground ax:hover:bg-muted",
|
|
62
|
+
},
|
|
63
|
+
size: {
|
|
64
|
+
sm: "ax:size-2xl",
|
|
65
|
+
md: "ax:size-3xl",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
defaultVariants: {
|
|
69
|
+
isActive: false,
|
|
70
|
+
size: "md",
|
|
71
|
+
},
|
|
72
|
+
}, { twMerge: false })
|
|
73
|
+
|
|
74
|
+
export interface PaginationLinkProps
|
|
75
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
76
|
+
VariantProps<typeof paginationLinkVariants> {
|
|
77
|
+
/** Render the child element instead of a button (e.g. a router `<Link>`). */
|
|
78
|
+
asChild?: boolean
|
|
79
|
+
/** Marks the current page: sets `aria-current="page"` and the active fill. */
|
|
80
|
+
isActive?: boolean
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const PaginationLink = React.forwardRef<HTMLButtonElement, PaginationLinkProps>(
|
|
84
|
+
({ className, isActive, size, asChild = false, type = "button", ...props }, ref) => {
|
|
85
|
+
const Comp = asChild ? Slot : "button"
|
|
86
|
+
return (
|
|
87
|
+
<Comp
|
|
88
|
+
ref={ref}
|
|
89
|
+
type={asChild ? undefined : type}
|
|
90
|
+
aria-current={isActive ? "page" : undefined}
|
|
91
|
+
className={cn(paginationLinkVariants({ isActive, size }), className)}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
PaginationLink.displayName = "PaginationLink"
|
|
98
|
+
|
|
99
|
+
export interface PaginationNavLinkProps extends Omit<PaginationLinkProps, "isActive"> {}
|
|
100
|
+
|
|
101
|
+
export const PaginationPrevious = React.forwardRef<HTMLButtonElement, PaginationNavLinkProps>(
|
|
102
|
+
({ className, children, "aria-label": ariaLabel = "Go to previous page", ...props }, ref) => (
|
|
103
|
+
<PaginationLink
|
|
104
|
+
ref={ref}
|
|
105
|
+
aria-label={ariaLabel}
|
|
106
|
+
className={cn("ax:w-auto ax:px-sm", className)}
|
|
107
|
+
{...props}
|
|
108
|
+
>
|
|
109
|
+
<ChevronLeft />
|
|
110
|
+
{children !== undefined ? children : <span className="ax:hidden ax:sm:inline">Previous</span>}
|
|
111
|
+
</PaginationLink>
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
PaginationPrevious.displayName = "PaginationPrevious"
|
|
115
|
+
|
|
116
|
+
export const PaginationNext = React.forwardRef<HTMLButtonElement, PaginationNavLinkProps>(
|
|
117
|
+
({ className, children, "aria-label": ariaLabel = "Go to next page", ...props }, ref) => (
|
|
118
|
+
<PaginationLink
|
|
119
|
+
ref={ref}
|
|
120
|
+
aria-label={ariaLabel}
|
|
121
|
+
className={cn("ax:w-auto ax:px-sm", className)}
|
|
122
|
+
{...props}
|
|
123
|
+
>
|
|
124
|
+
{children !== undefined ? children : <span className="ax:hidden ax:sm:inline">Next</span>}
|
|
125
|
+
<ChevronRight />
|
|
126
|
+
</PaginationLink>
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
PaginationNext.displayName = "PaginationNext"
|
|
130
|
+
|
|
131
|
+
export const PaginationEllipsis = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<"span">>(
|
|
132
|
+
({ className, ...props }, ref) => (
|
|
133
|
+
<span
|
|
134
|
+
ref={ref}
|
|
135
|
+
aria-hidden="true"
|
|
136
|
+
className={cn("ax:flex ax:size-3xl ax:items-center ax:justify-center ax:text-muted-foreground", className)}
|
|
137
|
+
{...props}
|
|
138
|
+
>
|
|
139
|
+
<MoreHorizontal className="ax:size-4" />
|
|
140
|
+
<span className="ax:sr-only">More pages</span>
|
|
141
|
+
</span>
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
PaginationEllipsis.displayName = "PaginationEllipsis"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { Button } from "./button"
|
|
4
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./popover"
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Popover> = {
|
|
7
|
+
title: "Components/Popover",
|
|
8
|
+
component: Popover,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default meta
|
|
13
|
+
type Story = StoryObj<typeof Popover>
|
|
14
|
+
|
|
15
|
+
export const Default: Story = {
|
|
16
|
+
render: () => (
|
|
17
|
+
<Popover>
|
|
18
|
+
<PopoverTrigger asChild>
|
|
19
|
+
<Button variant="outline">Open popover</Button>
|
|
20
|
+
</PopoverTrigger>
|
|
21
|
+
<PopoverContent className="ax:w-64">
|
|
22
|
+
<p className="ax:t-body-sm">Popover content with helpful context.</p>
|
|
23
|
+
</PopoverContent>
|
|
24
|
+
</Popover>
|
|
25
|
+
),
|
|
26
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
6
|
+
import { cn } from "../lib/cn"
|
|
7
|
+
|
|
8
|
+
export type PopoverSize = "sm" | "md" | "lg"
|
|
9
|
+
|
|
10
|
+
export const Popover = PopoverPrimitive.Root
|
|
11
|
+
export const PopoverTrigger = PopoverPrimitive.Trigger
|
|
12
|
+
export const PopoverAnchor = PopoverPrimitive.Anchor
|
|
13
|
+
export const PopoverClose = PopoverPrimitive.Close
|
|
14
|
+
|
|
15
|
+
// Padding scale is now smooth: sm → md → lg (was sm → md → xl, which skipped lg).
|
|
16
|
+
// Each size sets only width + padding; text sizing is left to the consumer's
|
|
17
|
+
// child elements (headings, body text, fields) so the popover doesn't impose
|
|
18
|
+
// an ambient text-size that fights nested typography.
|
|
19
|
+
const popoverContentVariants = cva(
|
|
20
|
+
[
|
|
21
|
+
"ax:z-[1100] ax:rounded-(--input-radius) ax:border ax:border-border ax:bg-popover ax:text-popover-foreground ax:shadow-md ax:outline-none",
|
|
22
|
+
"ax:data-[state=open]:animate-in ax:data-[state=closed]:animate-out",
|
|
23
|
+
"ax:data-[state=closed]:fade-out-0 ax:data-[state=open]:fade-in-0",
|
|
24
|
+
"ax:data-[state=closed]:zoom-out-[0.96] ax:data-[state=open]:zoom-in-[0.96]",
|
|
25
|
+
"ax:data-[side=bottom]:slide-in-from-top-1",
|
|
26
|
+
"ax:data-[side=left]:slide-in-from-right-1",
|
|
27
|
+
"ax:data-[side=right]:slide-in-from-left-1",
|
|
28
|
+
"ax:data-[side=top]:slide-in-from-bottom-1",
|
|
29
|
+
"ax:[animation-duration:var(--motion-fast)] ax:[animation-timing-function:var(--motion-ease-out)]",
|
|
30
|
+
"ax:motion-reduce:animate-none",
|
|
31
|
+
],
|
|
32
|
+
{
|
|
33
|
+
variants: {
|
|
34
|
+
size: {
|
|
35
|
+
sm: "ax:w-56 ax:p-sm", // 224px · 12px
|
|
36
|
+
md: "ax:w-72 ax:p-md", // 288px · 16px
|
|
37
|
+
lg: "ax:w-96 ax:p-lg", // 384px · 20px
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
defaultVariants: { size: "md" },
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
export interface PopoverContentProps
|
|
45
|
+
extends React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>,
|
|
46
|
+
VariantProps<typeof popoverContentVariants> {}
|
|
47
|
+
|
|
48
|
+
export const PopoverContent = React.forwardRef<
|
|
49
|
+
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
50
|
+
PopoverContentProps
|
|
51
|
+
>(({ className, align = "center", sideOffset = 6, size, ...props }, ref) => (
|
|
52
|
+
<PopoverPrimitive.Portal>
|
|
53
|
+
<PopoverPrimitive.Content
|
|
54
|
+
ref={ref}
|
|
55
|
+
align={align}
|
|
56
|
+
sideOffset={sideOffset}
|
|
57
|
+
className={cn(popoverContentVariants({ size }), className)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
</PopoverPrimitive.Portal>
|
|
61
|
+
))
|
|
62
|
+
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { Button } from "./button"
|
|
4
|
+
import { Pricing } from "./pricing"
|
|
5
|
+
|
|
6
|
+
const tiers = [
|
|
7
|
+
{
|
|
8
|
+
name: "Starter",
|
|
9
|
+
price: "$0",
|
|
10
|
+
period: "/mo",
|
|
11
|
+
description: "For individuals getting started.",
|
|
12
|
+
features: ["1 project", "Basic support"],
|
|
13
|
+
cta: <Button variant="outline" className="ax:w-full">Get started</Button>,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "Pro",
|
|
17
|
+
price: "$29",
|
|
18
|
+
period: "/mo",
|
|
19
|
+
description: "For growing teams.",
|
|
20
|
+
features: ["Unlimited projects", "Priority support"],
|
|
21
|
+
highlighted: true,
|
|
22
|
+
badge: "Popular",
|
|
23
|
+
cta: <Button className="ax:w-full">Start trial</Button>,
|
|
24
|
+
},
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
const meta: Meta<typeof Pricing> = {
|
|
28
|
+
title: "Components/Pricing",
|
|
29
|
+
component: Pricing,
|
|
30
|
+
tags: ["autodocs"],
|
|
31
|
+
args: {
|
|
32
|
+
eyebrow: "Pricing",
|
|
33
|
+
title: "Simple, transparent pricing",
|
|
34
|
+
description: "Choose the plan that fits your team.",
|
|
35
|
+
tiers,
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default meta
|
|
40
|
+
type Story = StoryObj<typeof Pricing>
|
|
41
|
+
|
|
42
|
+
export const Default: Story = {}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Check } from "lucide-react"
|
|
5
|
+
import { motion } from "motion/react"
|
|
6
|
+
import { cn } from "../lib/cn"
|
|
7
|
+
import { useReducedMotion } from "../hooks/use-reduced-motion"
|
|
8
|
+
import { stagger, variants, viewport as defaultViewport } from "../lib/motion"
|
|
9
|
+
|
|
10
|
+
export interface PricingTier {
|
|
11
|
+
name: string
|
|
12
|
+
price: React.ReactNode
|
|
13
|
+
period?: string
|
|
14
|
+
description?: string
|
|
15
|
+
features: string[]
|
|
16
|
+
cta: React.ReactNode
|
|
17
|
+
highlighted?: boolean
|
|
18
|
+
badge?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PricingProps extends Omit<React.HTMLAttributes<HTMLElement>, "title"> {
|
|
22
|
+
eyebrow?: React.ReactNode
|
|
23
|
+
title?: React.ReactNode
|
|
24
|
+
description?: React.ReactNode
|
|
25
|
+
tiers: PricingTier[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function Pricing({ className, eyebrow, title, description, tiers, ...props }: PricingProps) {
|
|
29
|
+
const reduce = useReducedMotion()
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<section className={cn("ax:w-full ax:px-xl ax:md:px-3xl ax:py-5xl ax:md:py-6xl", className)} {...props}>
|
|
33
|
+
<div className="ax:mx-auto ax:max-w-7xl">
|
|
34
|
+
{(eyebrow || title || description) && (
|
|
35
|
+
<div className="ax:mx-auto ax:max-w-2xl ax:text-center ax:mb-4xl ax:md:mb-5xl ax:space-y-md">
|
|
36
|
+
{eyebrow && (
|
|
37
|
+
<span className="ax:inline-block ax:t-overline ax:text-primary">
|
|
38
|
+
{eyebrow}
|
|
39
|
+
</span>
|
|
40
|
+
)}
|
|
41
|
+
{title && (
|
|
42
|
+
<h2 className="ax:t-display-md ax:md:t-display-lg ax:text-foreground ax:[text-wrap:balance]">
|
|
43
|
+
{title}
|
|
44
|
+
</h2>
|
|
45
|
+
)}
|
|
46
|
+
{description && (
|
|
47
|
+
<p className="ax:t-body-md ax:md:t-body-lg ax:text-muted-foreground ax:[text-wrap:pretty]">
|
|
48
|
+
{description}
|
|
49
|
+
</p>
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
53
|
+
|
|
54
|
+
<motion.div
|
|
55
|
+
className={cn(
|
|
56
|
+
"ax:grid ax:gap-xl",
|
|
57
|
+
tiers.length === 2 && "ax:md:grid-cols-2 ax:max-w-3xl ax:mx-auto",
|
|
58
|
+
tiers.length === 3 && "ax:md:grid-cols-3",
|
|
59
|
+
tiers.length >= 4 && "ax:md:grid-cols-2 ax:lg:grid-cols-4",
|
|
60
|
+
)}
|
|
61
|
+
{...(reduce ? {} : {
|
|
62
|
+
variants: stagger(0, 0.1),
|
|
63
|
+
initial: "hidden",
|
|
64
|
+
whileInView: "visible",
|
|
65
|
+
viewport: defaultViewport,
|
|
66
|
+
})}
|
|
67
|
+
>
|
|
68
|
+
{tiers.map((tier, i) => (
|
|
69
|
+
<motion.div
|
|
70
|
+
key={tier.name}
|
|
71
|
+
{...(reduce ? {} : { variants: variants.fadeUp })}
|
|
72
|
+
className={cn(
|
|
73
|
+
"ax:relative ax:flex ax:flex-col ax:rounded-xl ax:border ax:p-xl ax:gap-xl",
|
|
74
|
+
tier.highlighted
|
|
75
|
+
? "ax:border-primary ax:bg-primary ax:text-primary-foreground ax:shadow-lg"
|
|
76
|
+
: "ax:border-border ax:bg-card ax:text-card-foreground ax:shadow-sm"
|
|
77
|
+
)}
|
|
78
|
+
>
|
|
79
|
+
{tier.badge && (
|
|
80
|
+
<span
|
|
81
|
+
className={cn(
|
|
82
|
+
"ax:absolute ax:-top-3 ax:left-1/2 ax:-translate-x-1/2 ax:rounded-full ax:px-3 ax:py-0.5 ax:t-label-sm",
|
|
83
|
+
tier.highlighted
|
|
84
|
+
? "ax:bg-background ax:text-foreground"
|
|
85
|
+
: "ax:bg-primary ax:text-primary-foreground"
|
|
86
|
+
)}
|
|
87
|
+
>
|
|
88
|
+
{tier.badge}
|
|
89
|
+
</span>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
<div className="ax:space-y-1.5">
|
|
93
|
+
<div className="ax:t-overline ax:opacity-80">{tier.name}</div>
|
|
94
|
+
<div className="ax:flex ax:items-end ax:gap-2xs">
|
|
95
|
+
<span className="ax:t-display-lg">{tier.price}</span>
|
|
96
|
+
{tier.period && (
|
|
97
|
+
<span className="ax:t-body-sm ax:opacity-70 ax:mb-1">/{tier.period}</span>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
{tier.description && (
|
|
101
|
+
<p className={cn("ax:t-body-sm", tier.highlighted ? "ax:opacity-80" : "ax:text-muted-foreground")}>
|
|
102
|
+
{tier.description}
|
|
103
|
+
</p>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<ul className="ax:flex-1 ax:space-y-2.5">
|
|
108
|
+
{tier.features.map((feature) => (
|
|
109
|
+
<li key={feature} className="ax:flex ax:items-start ax:gap-2.5 ax:t-body-sm">
|
|
110
|
+
<Check
|
|
111
|
+
className={cn(
|
|
112
|
+
"ax:size-4 ax:shrink-0 ax:mt-0.5",
|
|
113
|
+
tier.highlighted ? "ax:text-primary-foreground" : "ax:text-primary"
|
|
114
|
+
)}
|
|
115
|
+
/>
|
|
116
|
+
<span className={tier.highlighted ? "ax:opacity-90" : "ax:text-foreground"}>
|
|
117
|
+
{feature}
|
|
118
|
+
</span>
|
|
119
|
+
</li>
|
|
120
|
+
))}
|
|
121
|
+
</ul>
|
|
122
|
+
|
|
123
|
+
<div>{tier.cta}</div>
|
|
124
|
+
</motion.div>
|
|
125
|
+
))}
|
|
126
|
+
</motion.div>
|
|
127
|
+
</div>
|
|
128
|
+
</section>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { Progress } from "./progress"
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Progress> = {
|
|
6
|
+
title: "Components/Progress",
|
|
7
|
+
component: Progress,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
args: {
|
|
10
|
+
value: 65,
|
|
11
|
+
size: "md",
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default meta
|
|
16
|
+
type Story = StoryObj<typeof Progress>
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {}
|
|
19
|
+
|
|
20
|
+
export const Ring: Story = {
|
|
21
|
+
args: { shape: "ring", value: 72 },
|
|
22
|
+
}
|