@ponti-studios/ui 1.6.0 → 1.8.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 -64
- 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 +31 -4
- package/src/styles/tokens/color-systems.stories.tsx +173 -25
- package/src/styles/tokens/generated/dark.colors.generated.ts +20 -12
- package/src/styles/tokens/generated/dark.css +20 -12
- package/src/styles/tokens/generated/light.colors.generated.ts +19 -11
- package/src/styles/tokens/generated/light.css +19 -11
- package/src/styles/tokens/index.ts +0 -3
- package/src/styles/tokens/scripts/check-source.mjs +7 -0
- package/src/styles/tokens/source/dark.tokens.json +44 -12
- package/src/styles/tokens/source/light.tokens.json +43 -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 -75
- package/src/components/forms/date-field.stories.tsx +0 -202
- package/src/components/forms/date-field.tsx +0 -179
- 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
|
@@ -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,75 +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: "flex items-center gap-1 text-sm font-medium",
|
|
20
|
-
dropdowns: "flex items-center gap-1",
|
|
21
|
-
dropdown_root: "relative",
|
|
22
|
-
dropdown: "absolute inset-0 z-10 cursor-pointer opacity-0",
|
|
23
|
-
months_dropdown: "",
|
|
24
|
-
years_dropdown: "",
|
|
25
|
-
nav: "absolute inset-x-4 top-4 z-10 flex items-center justify-between",
|
|
26
|
-
button_previous:
|
|
27
|
-
"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",
|
|
28
|
-
button_next:
|
|
29
|
-
"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",
|
|
30
|
-
month_grid: "w-full border-collapse",
|
|
31
|
-
weekdays: "flex",
|
|
32
|
-
weekday:
|
|
33
|
-
"flex size-8 items-center justify-center text-[0.8rem] font-normal text-muted-foreground",
|
|
34
|
-
week: "mt-1 flex w-full",
|
|
35
|
-
day: "relative size-8 p-0 text-center text-lg focus-within:z-20",
|
|
36
|
-
day_button:
|
|
37
|
-
"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",
|
|
38
|
-
selected:
|
|
39
|
-
"[&>button]:bg-primary [&>button]:text-primary-foreground [&>button]:hover:bg-primary/90",
|
|
40
|
-
today: "[&>button]:bg-primary [&>button]:text-primary-foreground",
|
|
41
|
-
outside: "text-muted-foreground [&>button]:text-muted-foreground",
|
|
42
|
-
disabled: "text-muted-foreground opacity-50",
|
|
43
|
-
range_start:
|
|
44
|
-
"rounded-l-md bg-primary [&>button]:bg-primary [&>button]:text-primary-foreground",
|
|
45
|
-
range_middle: "bg-primary [&>button]:rounded-none [&>button]:text-primary-foreground",
|
|
46
|
-
range_end:
|
|
47
|
-
"rounded-r-md bg-primary [&>button]:bg-primary [&>button]:text-primary-foreground",
|
|
48
|
-
hidden: "invisible",
|
|
49
|
-
...classNames,
|
|
50
|
-
}}
|
|
51
|
-
components={{
|
|
52
|
-
Chevron: ({ className: iconClassName, orientation, ...iconProps }) => {
|
|
53
|
-
if (orientation === "left") {
|
|
54
|
-
return <ChevronLeft className={cn("size-4", iconClassName)} {...iconProps} />;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (orientation === "right") {
|
|
58
|
-
return <ChevronRight className={cn("size-4", iconClassName)} {...iconProps} />;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (orientation === "up") {
|
|
62
|
-
return <ChevronUp className={cn("size-4", iconClassName)} {...iconProps} />;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return <ChevronDown className={cn("size-4", iconClassName)} {...iconProps} />;
|
|
66
|
-
},
|
|
67
|
-
}}
|
|
68
|
-
{...props}
|
|
69
|
-
/>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
Calendar.displayName = "Calendar";
|
|
74
|
-
|
|
75
|
-
export { Calendar };
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
import { expect, screen, userEvent, within } from "storybook/test";
|
|
3
|
-
|
|
4
|
-
import { DateField } from "./date-field";
|
|
5
|
-
|
|
6
|
-
const meta = {
|
|
7
|
-
title: "Forms/DateField",
|
|
8
|
-
component: DateField,
|
|
9
|
-
tags: ["autodocs"],
|
|
10
|
-
} satisfies Meta<typeof DateField>;
|
|
11
|
-
|
|
12
|
-
export default meta;
|
|
13
|
-
type Story = StoryObj<typeof meta>;
|
|
14
|
-
|
|
15
|
-
function hiddenValue(canvasElement: HTMLElement, name: string): string | null {
|
|
16
|
-
const input = canvasElement.querySelector(`input[name="${name}"]`) as HTMLInputElement | null;
|
|
17
|
-
return input?.value ?? null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function nextMonth(dialog: HTMLElement) {
|
|
21
|
-
await userEvent.click(within(dialog).getByRole("button", { name: "Go to the Next Month" }));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// The trigger button's accessible name comes from its <label> (htmlFor), so
|
|
25
|
-
// stories query the button by label and assert the visible date text separately.
|
|
26
|
-
export const WithStringDefaultValue: Story = {
|
|
27
|
-
args: {
|
|
28
|
-
name: "startDate",
|
|
29
|
-
defaultValue: "2020-01-15",
|
|
30
|
-
label: "Start date",
|
|
31
|
-
},
|
|
32
|
-
play: async ({ canvasElement }) => {
|
|
33
|
-
const canvas = within(canvasElement);
|
|
34
|
-
|
|
35
|
-
// The hidden input carries the local "YYYY-MM-DD" value, and the trigger
|
|
36
|
-
// shows the formatted date.
|
|
37
|
-
await expect(hiddenValue(canvasElement, "startDate")).toBe("2020-01-15");
|
|
38
|
-
const trigger = canvas.getByRole("button", { name: "Start date" });
|
|
39
|
-
await expect(trigger).toHaveTextContent("Jan 15, 2020");
|
|
40
|
-
|
|
41
|
-
// The calendar opens on the selected month, not the current one.
|
|
42
|
-
await userEvent.click(trigger);
|
|
43
|
-
const dialog = await screen.findByRole("dialog");
|
|
44
|
-
await expect(within(dialog).getByText("January 2020")).toBeInTheDocument();
|
|
45
|
-
|
|
46
|
-
// Real click on the Next chevron navigates the month.
|
|
47
|
-
await nextMonth(dialog);
|
|
48
|
-
await expect(within(dialog).getByText("February 2020")).toBeInTheDocument();
|
|
49
|
-
|
|
50
|
-
// Picking a day updates the trigger and the hidden input.
|
|
51
|
-
await userEvent.click(within(dialog).getByText("15"));
|
|
52
|
-
await expect(canvas.getByRole("button", { name: "Start date" })).toHaveTextContent(
|
|
53
|
-
"Feb 15, 2020",
|
|
54
|
-
);
|
|
55
|
-
await expect(hiddenValue(canvasElement, "startDate")).toBe("2020-02-15");
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export const WithDateDefaultValue: Story = {
|
|
60
|
-
args: {
|
|
61
|
-
name: "endDate",
|
|
62
|
-
defaultValue: new Date(2020, 0, 15),
|
|
63
|
-
label: "End date",
|
|
64
|
-
},
|
|
65
|
-
play: async ({ canvasElement }) => {
|
|
66
|
-
const canvas = within(canvasElement);
|
|
67
|
-
|
|
68
|
-
await expect(hiddenValue(canvasElement, "endDate")).toBe("2020-01-15");
|
|
69
|
-
await expect(canvas.getByRole("button", { name: "End date" })).toHaveTextContent(
|
|
70
|
-
"Jan 15, 2020",
|
|
71
|
-
);
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
export const Empty: Story = {
|
|
76
|
-
args: {
|
|
77
|
-
name: "startDate",
|
|
78
|
-
label: "Start date",
|
|
79
|
-
},
|
|
80
|
-
play: async ({ canvasElement }) => {
|
|
81
|
-
const canvas = within(canvasElement);
|
|
82
|
-
|
|
83
|
-
await expect(hiddenValue(canvasElement, "startDate")).toBe("");
|
|
84
|
-
await expect(canvas.getByRole("button", { name: "Start date" })).toHaveTextContent(
|
|
85
|
-
"Pick a date",
|
|
86
|
-
);
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
export const Disabled: Story = {
|
|
91
|
-
args: {
|
|
92
|
-
name: "startDate",
|
|
93
|
-
defaultValue: "2020-01-15",
|
|
94
|
-
label: "Start date",
|
|
95
|
-
disabled: true,
|
|
96
|
-
},
|
|
97
|
-
play: async ({ canvasElement }) => {
|
|
98
|
-
const canvas = within(canvasElement);
|
|
99
|
-
|
|
100
|
-
await expect(canvas.getByRole("button", { name: "Start date" })).toBeDisabled();
|
|
101
|
-
await expect(hiddenValue(canvasElement, "startDate")).toBe("2020-01-15");
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
// No play function — this one is for manually driving the interaction
|
|
106
|
-
// (e.g. screen recordings), not automated assertions.
|
|
107
|
-
export const DropdownEmpty: Story = {
|
|
108
|
-
args: {
|
|
109
|
-
name: "startDate",
|
|
110
|
-
label: "Start date",
|
|
111
|
-
captionLayout: "dropdown",
|
|
112
|
-
},
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
export const WithDropdownCaption: Story = {
|
|
116
|
-
args: {
|
|
117
|
-
mode: "range",
|
|
118
|
-
startName: "startDate",
|
|
119
|
-
endName: "endDate",
|
|
120
|
-
defaultValue: { from: "2018-03-01" },
|
|
121
|
-
label: "Employment period",
|
|
122
|
-
captionLayout: "dropdown",
|
|
123
|
-
},
|
|
124
|
-
play: async ({ canvasElement }) => {
|
|
125
|
-
const canvas = within(canvasElement);
|
|
126
|
-
|
|
127
|
-
// Jumping back to a date years ago is a month/year select, not repeated
|
|
128
|
-
// prev-month clicks.
|
|
129
|
-
const trigger = canvas.getByRole("button", { name: "Employment period" });
|
|
130
|
-
await userEvent.click(trigger);
|
|
131
|
-
const dialog = await screen.findByRole("dialog");
|
|
132
|
-
|
|
133
|
-
const yearSelect = within(dialog).getByRole("combobox", { name: /year/i });
|
|
134
|
-
await userEvent.selectOptions(yearSelect, "2009");
|
|
135
|
-
const monthSelect = within(dialog).getByRole("combobox", { name: /month/i });
|
|
136
|
-
await userEvent.selectOptions(monthSelect, "May");
|
|
137
|
-
|
|
138
|
-
await userEvent.click(within(dialog).getByText("12"));
|
|
139
|
-
await expect(hiddenValue(canvasElement, "startDate")).toBe("2009-05-12");
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
export const WithRangeDefaultValue: Story = {
|
|
144
|
-
args: {
|
|
145
|
-
mode: "range",
|
|
146
|
-
startName: "startDate",
|
|
147
|
-
endName: "endDate",
|
|
148
|
-
defaultValue: { from: "2020-01-15", to: "2020-06-30" },
|
|
149
|
-
label: "Active period",
|
|
150
|
-
},
|
|
151
|
-
play: async ({ canvasElement }) => {
|
|
152
|
-
const canvas = within(canvasElement);
|
|
153
|
-
|
|
154
|
-
// Both hidden inputs carry local date strings; the trigger shows the range.
|
|
155
|
-
await expect(hiddenValue(canvasElement, "startDate")).toBe("2020-01-15");
|
|
156
|
-
await expect(hiddenValue(canvasElement, "endDate")).toBe("2020-06-30");
|
|
157
|
-
const trigger = canvas.getByRole("button", { name: "Active period" });
|
|
158
|
-
await expect(trigger).toHaveTextContent("Jan 15, 2020");
|
|
159
|
-
await expect(trigger).toHaveTextContent("Jun 30, 2020");
|
|
160
|
-
|
|
161
|
-
// The range calendar opens on the start month.
|
|
162
|
-
await userEvent.click(trigger);
|
|
163
|
-
const dialog = await screen.findByRole("dialog");
|
|
164
|
-
await expect(within(dialog).getByText("January 2020")).toBeInTheDocument();
|
|
165
|
-
|
|
166
|
-
// Real-click the Next chevron, then pick a new from date.
|
|
167
|
-
await nextMonth(dialog);
|
|
168
|
-
await expect(within(dialog).getByText("February 2020")).toBeInTheDocument();
|
|
169
|
-
await userEvent.click(within(dialog).getByText("20"));
|
|
170
|
-
await expect(canvas.getByRole("button", { name: "Active period" })).toHaveTextContent(
|
|
171
|
-
"From Feb 20, 2020",
|
|
172
|
-
);
|
|
173
|
-
await expect(hiddenValue(canvasElement, "startDate")).toBe("2020-02-20");
|
|
174
|
-
await expect(hiddenValue(canvasElement, "endDate")).toBe("");
|
|
175
|
-
|
|
176
|
-
// Picking an end date fills the second hidden input.
|
|
177
|
-
await userEvent.click(within(dialog).getByText("25"));
|
|
178
|
-
await expect(canvas.getByRole("button", { name: "Active period" })).toHaveTextContent(
|
|
179
|
-
"Feb 20, 2020 – Feb 25, 2020",
|
|
180
|
-
);
|
|
181
|
-
await expect(hiddenValue(canvasElement, "startDate")).toBe("2020-02-20");
|
|
182
|
-
await expect(hiddenValue(canvasElement, "endDate")).toBe("2020-02-25");
|
|
183
|
-
},
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
export const EmptyRange: Story = {
|
|
187
|
-
args: {
|
|
188
|
-
mode: "range",
|
|
189
|
-
startName: "startDate",
|
|
190
|
-
endName: "endDate",
|
|
191
|
-
label: "Active period",
|
|
192
|
-
},
|
|
193
|
-
play: async ({ canvasElement }) => {
|
|
194
|
-
const canvas = within(canvasElement);
|
|
195
|
-
|
|
196
|
-
await expect(hiddenValue(canvasElement, "startDate")).toBe("");
|
|
197
|
-
await expect(hiddenValue(canvasElement, "endDate")).toBe("");
|
|
198
|
-
await expect(canvas.getByRole("button", { name: "Active period" })).toHaveTextContent(
|
|
199
|
-
"Pick a date range",
|
|
200
|
-
);
|
|
201
|
-
},
|
|
202
|
-
};
|