@ponti-studios/ui 1.5.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -11
- package/package.json +6 -31
- package/src/components/feedback/alert.tsx +8 -4
- package/src/components/feedback/index.ts +0 -4
- package/src/components/feedback/progress.stories.tsx +15 -0
- package/src/components/feedback/progress.tsx +14 -3
- package/src/components/feedback/update-guard.stories.tsx +4 -8
- package/src/components/feedback/update-guard.tsx +41 -272
- package/src/components/filters/filter-select.tsx +3 -5
- package/src/components/forms/date-picker.stories.tsx +158 -18
- package/src/components/forms/date-picker.tsx +286 -51
- package/src/components/forms/date-utils.ts +7 -2
- package/src/components/forms/drop-zone.tsx +11 -12
- package/src/components/forms/index.ts +2 -6
- package/src/components/forms/passkey-management.stories.tsx +8 -4
- package/src/components/forms/passkey-management.tsx +78 -42
- package/src/components/forms/radio-group.stories.tsx +3 -5
- package/src/components/forms/radio-group.tsx +2 -2
- package/src/components/forms/select.stories.tsx +16 -13
- package/src/components/forms/textarea.stories.tsx +1 -1
- package/src/components/layout/index.ts +2 -0
- package/src/components/layout/particle-background.stories.tsx +38 -0
- package/src/components/layout/particle-background.tsx +378 -0
- package/src/components/layout/scroll-area.stories.tsx +12 -4
- package/src/components/layout/scroll-area.tsx +41 -4
- package/src/components/navigation/tabs.stories.tsx +0 -21
- package/src/components/overlays/dialog.stories.tsx +38 -1
- package/src/components/overlays/dialog.tsx +53 -12
- package/src/components/overlays/index.ts +0 -11
- package/src/components/primitives/avatar-placeholder.svg +13 -0
- package/src/components/primitives/avatar.stories.tsx +2 -1
- package/src/components/primitives/badge.stories.tsx +13 -3
- package/src/components/primitives/badge.tsx +1 -1
- package/src/components/primitives/button.tsx +1 -1
- package/src/components/primitives/card.tsx +6 -2
- package/src/components/primitives/index.ts +0 -3
- package/src/index.ts +1 -3
- package/src/lib/index.ts +2 -3
- package/src/styles/animations.css +62 -0
- package/src/styles/index.css +27 -0
- package/src/styles/tokens/generated/dark.colors.generated.ts +15 -12
- package/src/styles/tokens/generated/dark.css +15 -12
- package/src/styles/tokens/generated/light.colors.generated.ts +14 -11
- package/src/styles/tokens/generated/light.css +14 -11
- package/src/styles/tokens/index.ts +0 -3
- package/src/styles/tokens/source/dark.tokens.json +24 -12
- package/src/styles/tokens/source/light.tokens.json +23 -11
- package/src/styles/tokens/typography.stories.tsx +157 -0
- package/src/components/feedback/file-upload-status.stories.tsx +0 -57
- package/src/components/feedback/file-upload-status.tsx +0 -160
- package/src/components/feedback/progress-bar.stories.tsx +0 -39
- package/src/components/feedback/progress-bar.tsx +0 -22
- package/src/components/forms/calendar.stories.tsx +0 -30
- package/src/components/forms/calendar.tsx +0 -70
- package/src/components/forms/date-field.stories.tsx +0 -164
- package/src/components/forms/date-field.tsx +0 -174
- package/src/components/overlays/alert-dialog.stories.tsx +0 -45
- package/src/components/overlays/alert-dialog.tsx +0 -116
- package/src/components/primitives/label.stories.tsx +0 -29
- package/src/components/primitives/label.tsx +0 -20
- package/src/components/primitives/status-badge.stories.tsx +0 -52
- package/src/components/primitives/status-badge.tsx +0 -33
- package/src/lib/create-memory-storage.ts +0 -24
- package/src/native/card.tsx +0 -36
- package/src/native/icon-button.tsx +0 -41
- package/src/native/index.ts +0 -38
- package/src/native/list-row.tsx +0 -67
- package/src/native/make-styles.ts +0 -32
- package/src/native/shadows.ts +0 -38
- package/src/native/text-field.tsx +0 -34
- package/src/native/uniwind.d.ts +0 -1
- package/src/native/use-theme-colors.ts +0 -19
- package/src/styles/tokens/foundations.stories.tsx +0 -109
- package/src/styles/tokens/generated/native.generated.ts +0 -266
- package/src/styles/tokens/normalize-native.ts +0 -88
- package/src/styles/tokens/source/native.tokens.json +0 -204
- package/src/styles/tokens/style-dictionary.native.mjs +0 -26
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { fontFamilies, fontWeights, textLineHeights, textSizes, tracking } from "./index";
|
|
4
|
+
|
|
5
|
+
const meta: Meta = { title: "Foundations/Typography", parameters: { layout: "fullscreen" } };
|
|
6
|
+
export default meta;
|
|
7
|
+
type Story = StoryObj<typeof meta>;
|
|
8
|
+
|
|
9
|
+
const sizeTokens = Object.keys(textSizes) as Array<keyof typeof textSizes>;
|
|
10
|
+
const weightTokens = Object.keys(fontWeights) as Array<keyof typeof fontWeights>;
|
|
11
|
+
const trackingTokens = Object.keys(tracking) as Array<keyof typeof tracking>;
|
|
12
|
+
const familyTokens = Object.keys(fontFamilies) as Array<keyof typeof fontFamilies>;
|
|
13
|
+
|
|
14
|
+
export const TypeScale: Story = {
|
|
15
|
+
render: () => (
|
|
16
|
+
<main className="bg-background text-foreground grid min-h-screen gap-10 p-6 md:p-10">
|
|
17
|
+
<header className="grid gap-2">
|
|
18
|
+
<h1 className="text-2xl font-semibold">Typography</h1>
|
|
19
|
+
<p className="text-muted-foreground max-w-prose text-sm">
|
|
20
|
+
Font sizes, weights, tracking, and families as defined by the shared design tokens (
|
|
21
|
+
<code className="font-mono">src/styles/tokens</code>).
|
|
22
|
+
</p>
|
|
23
|
+
</header>
|
|
24
|
+
|
|
25
|
+
<section className="grid gap-4">
|
|
26
|
+
<h2 className="text-muted-foreground text-xs font-medium tracking-wider uppercase">
|
|
27
|
+
Type scale
|
|
28
|
+
</h2>
|
|
29
|
+
<div className="border-border divide-border grid divide-y rounded-md border">
|
|
30
|
+
{sizeTokens.map((token) => (
|
|
31
|
+
<div key={token} className="grid grid-cols-[6rem_5rem_1fr] items-baseline gap-4 p-4">
|
|
32
|
+
<span className="text-muted-foreground font-mono text-xs">text-{token}</span>
|
|
33
|
+
<span className="text-muted-foreground font-mono text-xs tabular-nums">
|
|
34
|
+
{textSizes[token]}px / {textLineHeights[token]?.toFixed(2)}
|
|
35
|
+
</span>
|
|
36
|
+
<span
|
|
37
|
+
className="truncate"
|
|
38
|
+
style={{ fontSize: textSizes[token], lineHeight: textLineHeights[token] }}
|
|
39
|
+
>
|
|
40
|
+
The quick brown fox jumps
|
|
41
|
+
</span>
|
|
42
|
+
</div>
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
</section>
|
|
46
|
+
|
|
47
|
+
<section className="grid gap-4">
|
|
48
|
+
<h2 className="text-muted-foreground text-xs font-medium tracking-wider uppercase">
|
|
49
|
+
Font weights
|
|
50
|
+
</h2>
|
|
51
|
+
<div className="border-border divide-border grid divide-y rounded-md border">
|
|
52
|
+
{weightTokens.map((token) => (
|
|
53
|
+
<div key={token} className="grid grid-cols-[8rem_1fr] items-baseline gap-4 p-4">
|
|
54
|
+
<span className="text-muted-foreground font-mono text-xs">
|
|
55
|
+
{token} / {fontWeights[token]}
|
|
56
|
+
</span>
|
|
57
|
+
<span className="text-lg" style={{ fontWeight: fontWeights[token] }}>
|
|
58
|
+
The quick brown fox jumps over the lazy dog
|
|
59
|
+
</span>
|
|
60
|
+
</div>
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
</section>
|
|
64
|
+
|
|
65
|
+
<section className="grid gap-4">
|
|
66
|
+
<h2 className="text-muted-foreground text-xs font-medium tracking-wider uppercase">
|
|
67
|
+
Tracking
|
|
68
|
+
</h2>
|
|
69
|
+
<div className="border-border divide-border grid divide-y rounded-md border">
|
|
70
|
+
{trackingTokens.map((token) => (
|
|
71
|
+
<div key={token} className="grid grid-cols-[8rem_1fr] items-baseline gap-4 p-4">
|
|
72
|
+
<span className="text-muted-foreground font-mono text-xs">
|
|
73
|
+
{token} / {tracking[token]}
|
|
74
|
+
</span>
|
|
75
|
+
<span className="text-lg" style={{ letterSpacing: tracking[token] }}>
|
|
76
|
+
The quick brown fox jumps over the lazy dog
|
|
77
|
+
</span>
|
|
78
|
+
</div>
|
|
79
|
+
))}
|
|
80
|
+
</div>
|
|
81
|
+
</section>
|
|
82
|
+
|
|
83
|
+
<section className="grid gap-4">
|
|
84
|
+
<h2 className="text-muted-foreground text-xs font-medium tracking-wider uppercase">
|
|
85
|
+
Font families
|
|
86
|
+
</h2>
|
|
87
|
+
<div className="border-border divide-border grid divide-y rounded-md border">
|
|
88
|
+
{familyTokens.map((token) => (
|
|
89
|
+
<div key={token} className="grid gap-2 p-4">
|
|
90
|
+
<span className="text-muted-foreground font-mono text-xs">--font-{token}</span>
|
|
91
|
+
<span className="text-2xl" style={{ fontFamily: fontFamilies[token] }}>
|
|
92
|
+
The quick brown fox jumps over the lazy dog. 0123456789
|
|
93
|
+
</span>
|
|
94
|
+
</div>
|
|
95
|
+
))}
|
|
96
|
+
</div>
|
|
97
|
+
</section>
|
|
98
|
+
</main>
|
|
99
|
+
),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const headingSpecimens = [
|
|
103
|
+
{ tag: "h1" as const, className: "text-4xl" },
|
|
104
|
+
{ tag: "h2" as const, className: "text-3xl" },
|
|
105
|
+
{ tag: "h3" as const, className: "text-2xl" },
|
|
106
|
+
{ tag: "h4" as const, className: "text-xl" },
|
|
107
|
+
{ tag: "h5" as const, className: "text-lg" },
|
|
108
|
+
{ tag: "h6" as const, className: "text-base" },
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
export const Headings: Story = {
|
|
112
|
+
render: () => (
|
|
113
|
+
<main className="bg-background text-foreground grid min-h-screen gap-10 p-6 md:p-10">
|
|
114
|
+
<header className="grid gap-2">
|
|
115
|
+
<h1 className="text-2xl font-semibold">Headings</h1>
|
|
116
|
+
<p className="text-muted-foreground max-w-prose text-sm">
|
|
117
|
+
`h1`–`h6` only carry font-family, weight, and tracking from the base layer — size is unset
|
|
118
|
+
by tag, so components pair a heading element with an explicit{" "}
|
|
119
|
+
<code className="font-mono">text-*</code> utility. The sizes below match this
|
|
120
|
+
codebase's own usage, not an enforced per-level scale.
|
|
121
|
+
</p>
|
|
122
|
+
</header>
|
|
123
|
+
|
|
124
|
+
<section className="grid gap-4">
|
|
125
|
+
<h2 className="text-muted-foreground text-xs font-medium tracking-wider uppercase">
|
|
126
|
+
Display utilities
|
|
127
|
+
</h2>
|
|
128
|
+
<div className="border-border divide-border grid divide-y rounded-md border">
|
|
129
|
+
<div className="grid gap-2 p-4">
|
|
130
|
+
<span className="text-muted-foreground font-mono text-xs">display-1</span>
|
|
131
|
+
<p className="display-1">Display one</p>
|
|
132
|
+
</div>
|
|
133
|
+
<div className="grid gap-2 p-4">
|
|
134
|
+
<span className="text-muted-foreground font-mono text-xs">display-2</span>
|
|
135
|
+
<p className="display-2">Display two</p>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</section>
|
|
139
|
+
|
|
140
|
+
<section className="grid gap-4">
|
|
141
|
+
<h2 className="text-muted-foreground text-xs font-medium tracking-wider uppercase">
|
|
142
|
+
Semantic headings
|
|
143
|
+
</h2>
|
|
144
|
+
<div className="border-border divide-border grid divide-y rounded-md border">
|
|
145
|
+
{headingSpecimens.map(({ tag: Tag, className }) => (
|
|
146
|
+
<div key={Tag} className="grid grid-cols-[6rem_1fr] items-baseline gap-4 p-4">
|
|
147
|
+
<span className="text-muted-foreground font-mono text-xs">
|
|
148
|
+
{Tag} / {className}
|
|
149
|
+
</span>
|
|
150
|
+
<Tag className={className}>The quick brown fox jumps over the lazy dog</Tag>
|
|
151
|
+
</div>
|
|
152
|
+
))}
|
|
153
|
+
</div>
|
|
154
|
+
</section>
|
|
155
|
+
</main>
|
|
156
|
+
),
|
|
157
|
+
};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
|
|
3
|
-
import { FileUploadStatus } from "./file-upload-status";
|
|
4
|
-
|
|
5
|
-
const meta = {
|
|
6
|
-
title: "Feedback/FileUploadStatus",
|
|
7
|
-
component: FileUploadStatus,
|
|
8
|
-
tags: ["autodocs"],
|
|
9
|
-
} satisfies Meta<typeof FileUploadStatus>;
|
|
10
|
-
|
|
11
|
-
export default meta;
|
|
12
|
-
type Story = StoryObj<typeof meta>;
|
|
13
|
-
|
|
14
|
-
export const Uploading: Story = {
|
|
15
|
-
render: () => (
|
|
16
|
-
<div className="w-80">
|
|
17
|
-
<FileUploadStatus uploadStatus={{ status: "uploading", stats: { progress: 42 } }} />
|
|
18
|
-
</div>
|
|
19
|
-
),
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export const Queued: Story = {
|
|
23
|
-
render: () => (
|
|
24
|
-
<div className="w-80">
|
|
25
|
-
<FileUploadStatus uploadStatus={{ status: "queued" }} />
|
|
26
|
-
</div>
|
|
27
|
-
),
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export const Done: Story = {
|
|
31
|
-
render: () => (
|
|
32
|
-
<div className="w-80">
|
|
33
|
-
<FileUploadStatus
|
|
34
|
-
uploadStatus={{
|
|
35
|
-
status: "done",
|
|
36
|
-
stats: {
|
|
37
|
-
processingTime: 4200,
|
|
38
|
-
total: 128,
|
|
39
|
-
created: 90,
|
|
40
|
-
updated: 30,
|
|
41
|
-
skipped: 8,
|
|
42
|
-
},
|
|
43
|
-
}}
|
|
44
|
-
/>
|
|
45
|
-
</div>
|
|
46
|
-
),
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export const Error: Story = {
|
|
50
|
-
render: () => (
|
|
51
|
-
<div className="w-80">
|
|
52
|
-
<FileUploadStatus
|
|
53
|
-
uploadStatus={{ status: "error", error: "Could not parse the CSV file." }}
|
|
54
|
-
/>
|
|
55
|
-
</div>
|
|
56
|
-
),
|
|
57
|
-
};
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { XIcon } from "lucide-react";
|
|
2
|
-
import { memo } from "react";
|
|
3
|
-
|
|
4
|
-
import { ProgressBar } from "./progress-bar";
|
|
5
|
-
|
|
6
|
-
export interface FileUploadStats {
|
|
7
|
-
progress?: number;
|
|
8
|
-
processingTime?: number;
|
|
9
|
-
total?: number;
|
|
10
|
-
created?: number;
|
|
11
|
-
updated?: number;
|
|
12
|
-
skipped?: number;
|
|
13
|
-
merged?: number;
|
|
14
|
-
invalid?: number;
|
|
15
|
-
errors?: unknown[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface FileUploadState {
|
|
19
|
-
status: "uploading" | "processing" | "queued" | "done" | "error" | (string & {});
|
|
20
|
-
error?: string;
|
|
21
|
-
stats?: FileUploadStats;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const FileUploadStatus = memo(function FileUploadStatus({
|
|
25
|
-
uploadStatus,
|
|
26
|
-
}: {
|
|
27
|
-
uploadStatus?: FileUploadState;
|
|
28
|
-
}) {
|
|
29
|
-
if (!uploadStatus) return null;
|
|
30
|
-
const { status, error, stats } = uploadStatus;
|
|
31
|
-
|
|
32
|
-
if (status === "uploading" || status === "processing") {
|
|
33
|
-
const progress = stats?.progress;
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<output className="w-full">
|
|
37
|
-
<div className="mb-1.5 flex items-center justify-between">
|
|
38
|
-
<span className="text-muted-foreground text-sm font-medium">
|
|
39
|
-
{status === "uploading" ? "Uploading" : "Processing"}
|
|
40
|
-
</span>
|
|
41
|
-
<span className="text-foreground text-sm font-medium">
|
|
42
|
-
{progress !== undefined ? `${Math.round(progress)}%` : ""}
|
|
43
|
-
</span>
|
|
44
|
-
</div>
|
|
45
|
-
<div className="w-full">
|
|
46
|
-
{typeof progress === "number" ? (
|
|
47
|
-
<ProgressBar
|
|
48
|
-
progress={progress}
|
|
49
|
-
className="bg-muted h-2"
|
|
50
|
-
indicatorClassName="border-primary"
|
|
51
|
-
/>
|
|
52
|
-
) : (
|
|
53
|
-
<div className="bg-muted h-2 overflow-hidden">
|
|
54
|
-
<div className="bg-primary void-anim-breezy-progress h-full" />
|
|
55
|
-
</div>
|
|
56
|
-
)}
|
|
57
|
-
</div>
|
|
58
|
-
</output>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (status === "queued") {
|
|
63
|
-
return (
|
|
64
|
-
<output className="w-full">
|
|
65
|
-
<div className="mb-1.5 flex items-center justify-between">
|
|
66
|
-
<span className="text-muted-foreground text-sm font-medium">Queue position</span>
|
|
67
|
-
</div>
|
|
68
|
-
<div className="w-full">
|
|
69
|
-
<ProgressBar
|
|
70
|
-
progress={0}
|
|
71
|
-
className="bg-warning/10 h-2"
|
|
72
|
-
indicatorClassName="border-warning"
|
|
73
|
-
/>
|
|
74
|
-
</div>
|
|
75
|
-
</output>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (status === "done") {
|
|
80
|
-
return (
|
|
81
|
-
<output className="space-y-3">
|
|
82
|
-
<div className="w-full">
|
|
83
|
-
<ProgressBar
|
|
84
|
-
progress={100}
|
|
85
|
-
className="bg-muted h-2"
|
|
86
|
-
indicatorClassName="border-primary"
|
|
87
|
-
/>
|
|
88
|
-
</div>
|
|
89
|
-
{stats && <ProcessingStats stats={stats} />}
|
|
90
|
-
</output>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (status === "error") {
|
|
95
|
-
return (
|
|
96
|
-
<output className="mt-2" role="alert">
|
|
97
|
-
<div className="bg-destructive/10 text-destructive-text flex items-start gap-2 p-3">
|
|
98
|
-
<XIcon className="mt-0.5 h-5 w-5 shrink-0" aria-hidden="true" />
|
|
99
|
-
<span className="text-sm">{error}</span>
|
|
100
|
-
</div>
|
|
101
|
-
</output>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return <output className="text-muted-foreground text-sm">{status}</output>;
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const ProcessingStats = memo(function ProcessingStats({ stats }: { stats: FileUploadStats }) {
|
|
109
|
-
if (!stats || typeof stats !== "object") return null;
|
|
110
|
-
|
|
111
|
-
return (
|
|
112
|
-
<dl className="divide-border mt-3 divide-y">
|
|
113
|
-
{stats.processingTime !== undefined && (
|
|
114
|
-
<div className="py-2 first:pt-0 last:pb-0">
|
|
115
|
-
<dt className="sr-only">Processing time</dt>
|
|
116
|
-
<dd className="text-foreground text-sm font-medium">
|
|
117
|
-
Completed in {(stats.processingTime / 1000).toFixed(1)}s
|
|
118
|
-
</dd>
|
|
119
|
-
</div>
|
|
120
|
-
)}
|
|
121
|
-
{stats.total !== undefined && Object.values(stats).length ? (
|
|
122
|
-
<>
|
|
123
|
-
<ProcessingStat label="Total" value={stats.total} />
|
|
124
|
-
<ProcessingStat label="Created" value={stats.created} />
|
|
125
|
-
<ProcessingStat label="Updated" value={stats.updated} />
|
|
126
|
-
<ProcessingStat label="Skipped" value={stats.skipped} />
|
|
127
|
-
<ProcessingStat label="Merged" value={stats.merged} />
|
|
128
|
-
<ProcessingStat label="Invalid" value={stats.invalid} />
|
|
129
|
-
</>
|
|
130
|
-
) : null}
|
|
131
|
-
|
|
132
|
-
{stats.errors?.length && stats.errors.length > 0 ? (
|
|
133
|
-
<div className="py-2">
|
|
134
|
-
<dt className="text-foreground text-sm font-medium">Errors</dt>
|
|
135
|
-
<dd className="mt-1">
|
|
136
|
-
<span className="bg-destructive/10 text-destructive-text inline-flex items-center px-2 py-1 text-xs font-medium">
|
|
137
|
-
{stats.errors.length} error{stats.errors.length > 1 ? "s" : ""}
|
|
138
|
-
</span>
|
|
139
|
-
</dd>
|
|
140
|
-
</div>
|
|
141
|
-
) : null}
|
|
142
|
-
</dl>
|
|
143
|
-
);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
const ProcessingStat = memo(function ProcessingStat({
|
|
147
|
-
label,
|
|
148
|
-
value,
|
|
149
|
-
}: {
|
|
150
|
-
label: string;
|
|
151
|
-
value?: number | undefined;
|
|
152
|
-
}) {
|
|
153
|
-
if (value === undefined) return null;
|
|
154
|
-
return (
|
|
155
|
-
<div className="py-2">
|
|
156
|
-
<dt className="text-muted-foreground text-sm font-medium">{label}</dt>
|
|
157
|
-
<dd className="text-foreground mt-1 text-sm">{value.toLocaleString()}</dd>
|
|
158
|
-
</div>
|
|
159
|
-
);
|
|
160
|
-
});
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
|
|
3
|
-
import { ProgressBar } from "./progress-bar";
|
|
4
|
-
|
|
5
|
-
const meta = {
|
|
6
|
-
title: "Feedback/ProgressBar",
|
|
7
|
-
component: ProgressBar,
|
|
8
|
-
tags: ["autodocs"],
|
|
9
|
-
} satisfies Meta<typeof ProgressBar>;
|
|
10
|
-
|
|
11
|
-
export default meta;
|
|
12
|
-
type Story = StoryObj<typeof meta>;
|
|
13
|
-
|
|
14
|
-
export const Default: Story = {
|
|
15
|
-
args: { progress: 60 },
|
|
16
|
-
render: (args) => (
|
|
17
|
-
<div className="w-64">
|
|
18
|
-
<ProgressBar {...args} />
|
|
19
|
-
</div>
|
|
20
|
-
),
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const Empty: Story = {
|
|
24
|
-
args: { progress: 0 },
|
|
25
|
-
render: (args) => (
|
|
26
|
-
<div className="w-64">
|
|
27
|
-
<ProgressBar {...args} />
|
|
28
|
-
</div>
|
|
29
|
-
),
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export const Complete: Story = {
|
|
33
|
-
args: { progress: 100 },
|
|
34
|
-
render: (args) => (
|
|
35
|
-
<div className="w-64">
|
|
36
|
-
<ProgressBar {...args} />
|
|
37
|
-
</div>
|
|
38
|
-
),
|
|
39
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { cn } from "../../lib/utils";
|
|
2
|
-
|
|
3
|
-
export interface ProgressBarProps {
|
|
4
|
-
className?: string;
|
|
5
|
-
indicatorClassName?: string;
|
|
6
|
-
progress?: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/** Thin linear progress indicator, distinct from the Radix-backed `Progress`. */
|
|
10
|
-
export function ProgressBar({ className, indicatorClassName, progress = 0 }: ProgressBarProps) {
|
|
11
|
-
return (
|
|
12
|
-
<div className={cn("border-border h-[2px] w-full overflow-hidden border-t", className)}>
|
|
13
|
-
<div
|
|
14
|
-
className={cn("border-warning h-full border-t", indicatorClassName)}
|
|
15
|
-
style={{
|
|
16
|
-
width: `${Math.min(100, Math.max(0, progress))}%`,
|
|
17
|
-
opacity: progress === 100 ? 0 : 0.8,
|
|
18
|
-
}}
|
|
19
|
-
/>
|
|
20
|
-
</div>
|
|
21
|
-
);
|
|
22
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
import { useState } from "react";
|
|
3
|
-
|
|
4
|
-
import { Calendar } from "./calendar";
|
|
5
|
-
|
|
6
|
-
const meta = {
|
|
7
|
-
title: "Forms/Calendar",
|
|
8
|
-
component: Calendar,
|
|
9
|
-
tags: ["autodocs"],
|
|
10
|
-
} satisfies Meta<typeof Calendar>;
|
|
11
|
-
|
|
12
|
-
export default meta;
|
|
13
|
-
type Story = StoryObj<typeof meta>;
|
|
14
|
-
|
|
15
|
-
export const Default: Story = {
|
|
16
|
-
render: () => {
|
|
17
|
-
const [date, setDate] = useState<Date | undefined>(new Date());
|
|
18
|
-
return (
|
|
19
|
-
<Calendar mode="single" selected={date} onSelect={setDate} className="rounded-md border" />
|
|
20
|
-
);
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const WithoutOutsideDays: Story = {
|
|
25
|
-
render: () => <Calendar mode="single" showOutsideDays={false} className="rounded-md border" />,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export const RangeMode: Story = {
|
|
29
|
-
render: () => <Calendar mode="range" className="rounded-md border" />,
|
|
30
|
-
};
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from "lucide-react";
|
|
2
|
-
import type * as React from "react";
|
|
3
|
-
import { DayPicker } from "react-day-picker";
|
|
4
|
-
|
|
5
|
-
import { cn } from "../../lib/utils";
|
|
6
|
-
|
|
7
|
-
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
|
|
8
|
-
|
|
9
|
-
function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
|
|
10
|
-
return (
|
|
11
|
-
<DayPicker
|
|
12
|
-
showOutsideDays={showOutsideDays}
|
|
13
|
-
className={cn("relative w-fit rounded-md p-4", className)}
|
|
14
|
-
classNames={{
|
|
15
|
-
root: "text-foreground",
|
|
16
|
-
months: "flex flex-col gap-4 sm:flex-row",
|
|
17
|
-
month: "flex flex-col gap-4",
|
|
18
|
-
month_caption: "relative flex h-8 items-center justify-center",
|
|
19
|
-
caption_label: "text-sm font-medium",
|
|
20
|
-
nav: "absolute inset-x-4 top-4 z-10 flex items-center justify-between",
|
|
21
|
-
button_previous:
|
|
22
|
-
"inline-flex size-8 p-2 items-center justify-center rounded-full border bg-background text-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50",
|
|
23
|
-
button_next:
|
|
24
|
-
"inline-flex size-8 p-2 items-center justify-center rounded-full border bg-background text-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50",
|
|
25
|
-
month_grid: "w-full border-collapse",
|
|
26
|
-
weekdays: "flex",
|
|
27
|
-
weekday:
|
|
28
|
-
"flex size-8 items-center justify-center text-[0.8rem] font-normal text-muted-foreground",
|
|
29
|
-
week: "mt-1 flex w-full",
|
|
30
|
-
day: "relative size-8 p-0 text-center text-lg focus-within:z-20",
|
|
31
|
-
day_button:
|
|
32
|
-
"inline-flex size-6 items-center justify-center rounded-md text-sm font-normal disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50",
|
|
33
|
-
selected:
|
|
34
|
-
"[&>button]:bg-primary [&>button]:text-primary-foreground [&>button]:hover:bg-primary/90",
|
|
35
|
-
today: "[&>button]:bg-primary [&>button]:text-primary-foreground",
|
|
36
|
-
outside: "text-muted-foreground [&>button]:text-muted-foreground",
|
|
37
|
-
disabled: "text-muted-foreground opacity-50",
|
|
38
|
-
range_start:
|
|
39
|
-
"rounded-l-md bg-primary [&>button]:bg-primary [&>button]:text-primary-foreground",
|
|
40
|
-
range_middle: "bg-primary [&>button]:rounded-none [&>button]:text-primary-foreground",
|
|
41
|
-
range_end:
|
|
42
|
-
"rounded-r-md bg-primary [&>button]:bg-primary [&>button]:text-primary-foreground",
|
|
43
|
-
hidden: "invisible",
|
|
44
|
-
...classNames,
|
|
45
|
-
}}
|
|
46
|
-
components={{
|
|
47
|
-
Chevron: ({ className: iconClassName, orientation, ...iconProps }) => {
|
|
48
|
-
if (orientation === "left") {
|
|
49
|
-
return <ChevronLeft className={cn("size-4", iconClassName)} {...iconProps} />;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (orientation === "right") {
|
|
53
|
-
return <ChevronRight className={cn("size-4", iconClassName)} {...iconProps} />;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (orientation === "up") {
|
|
57
|
-
return <ChevronUp className={cn("size-4", iconClassName)} {...iconProps} />;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return <ChevronDown className={cn("size-4", iconClassName)} {...iconProps} />;
|
|
61
|
-
},
|
|
62
|
-
}}
|
|
63
|
-
{...props}
|
|
64
|
-
/>
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
Calendar.displayName = "Calendar";
|
|
69
|
-
|
|
70
|
-
export { Calendar };
|