@gsk_poc/untitled-ui-base 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/components/application/app-navigation/base-components/featured-cards.demo.tsx +86 -0
- package/components/application/app-navigation/base-components/featured-cards.story.tsx +49 -0
- package/components/application/app-navigation/header-navigation.demo.tsx +59 -0
- package/components/application/app-navigation/header-navigation.story.tsx +23 -0
- package/components/application/app-navigation/sidebar-navigation.demo.tsx +409 -0
- package/components/application/app-navigation/sidebar-navigation.story.tsx +47 -0
- package/components/application/carousel/carousel.demo.tsx +107 -0
- package/components/application/carousel/carousel.story.tsx +21 -0
- package/components/application/charts/activity-gauges.demo.tsx +251 -0
- package/components/application/charts/activity-gauges.story.tsx +27 -0
- package/components/application/charts/bar-charts.demo.tsx +506 -0
- package/components/application/charts/bar-charts.story.tsx +36 -0
- package/components/application/charts/line-charts.demo.tsx +604 -0
- package/components/application/charts/line-charts.story.tsx +43 -0
- package/components/application/charts/pie-charts.demo.tsx +193 -0
- package/components/application/charts/pie-charts.story.tsx +30 -0
- package/components/application/charts/progress-circles.demo.tsx +110 -0
- package/components/application/charts/progress-circles.story.tsx +33 -0
- package/components/application/charts/radar-charts.demo.tsx +203 -0
- package/components/application/charts/radar-charts.story.tsx +18 -0
- package/components/application/date-picker/date-picker.demo.tsx +217 -0
- package/components/application/date-picker/date-picker.story.tsx +44 -0
- package/components/application/file-upload/file-upload.demo.tsx +292 -0
- package/components/application/file-upload/file-upload.story.tsx +70 -0
- package/components/application/loading-indicator/loading-indicator.demo.tsx +73 -0
- package/components/application/loading-indicator/loading-indicator.story.tsx +22 -0
- package/components/application/pagination/pagination.demo.tsx +104 -0
- package/components/application/pagination/pagination.story.tsx +54 -0
- package/components/application/table/table.demo.tsx +1038 -0
- package/components/application/table/table.story.tsx +119 -0
- package/components/application/tabs/tabs.demo.tsx +322 -0
- package/components/application/tabs/tabs.story.tsx +43 -0
- package/components/base/avatar/avatar.demo.tsx +865 -0
- package/components/base/avatar/avatar.story.tsx +27 -0
- package/components/base/badges/badge-groups.demo.tsx +357 -0
- package/components/base/badges/badge-groups.story.tsx +25 -0
- package/components/base/badges/badges.demo.tsx +497 -0
- package/components/base/badges/badges.story.tsx +83 -0
- package/components/base/button-group/button-group.demo.tsx +131 -0
- package/components/base/button-group/button-group.story.tsx +16 -0
- package/components/base/buttons/app-store-buttons.demo.tsx +129 -0
- package/components/base/buttons/app-store-buttons.story.tsx +13 -0
- package/components/base/buttons/buttons.demo.tsx +1022 -0
- package/components/base/buttons/buttons.story.tsx +42 -0
- package/components/base/buttons/social-buttons.demo.tsx +432 -0
- package/components/base/buttons/social-buttons.story.tsx +20 -0
- package/components/base/checkbox/checkbox.demo.tsx +62 -0
- package/components/base/checkbox/checkbox.story.tsx +18 -0
- package/components/base/dropdown/dropdown.demo.tsx +137 -0
- package/components/base/dropdown/dropdown.story.tsx +22 -0
- package/components/base/input/inputs.demo.tsx +758 -0
- package/components/base/input/inputs.story.tsx +52 -0
- package/components/base/pin-input/pin-input.demo.tsx +126 -0
- package/components/base/pin-input/pin-input.story.tsx +22 -0
- package/components/base/progress-indicators/progress-indicators.demo.tsx +54 -0
- package/components/base/progress-indicators/progress-indicators.story.tsx +57 -0
- package/components/base/radio-buttons/radio-buttons.demo.tsx +77 -0
- package/components/base/radio-buttons/radio-buttons.story.tsx +19 -0
- package/components/base/select/select.demo.tsx +936 -0
- package/components/base/select/select.story.tsx +43 -0
- package/components/base/slider/slider.demo.tsx +19 -0
- package/components/base/slider/slider.story.tsx +26 -0
- package/components/base/tags/tags.demo.tsx +341 -0
- package/components/base/tags/tags.story.tsx +28 -0
- package/components/base/textarea/textarea.demo.tsx +25 -0
- package/components/base/textarea/textarea.story.tsx +15 -0
- package/components/base/toggle/toggle.demo.tsx +76 -0
- package/components/base/toggle/toggle.story.tsx +23 -0
- package/components/base/tooltip/tooltip.demo.tsx +125 -0
- package/components/base/tooltip/tooltip.story.tsx +21 -0
- package/components/foundations/featured-icon/featured-icon.demo.tsx +160 -0
- package/components/foundations/featured-icon/featured-icon.story.tsx +25 -0
- package/components/shared-assets/credit-card/credit-card.demo.tsx +106 -0
- package/components/shared-assets/credit-card/credit-card.story.tsx +41 -0
- package/dist/index.d.mts +1417 -0
- package/dist/index.d.ts +1417 -0
- package/dist/index.js +10435 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +10345 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +126 -0
- package/styles/globals.css +65 -0
- package/styles/theme.css +430 -0
- package/styles/tokens-mapped.css +233 -0
- package/styles/tokens.css +807 -0
- package/styles/typography.css +430 -0
- package/tokens/design-tokens.dtcg.json +3515 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import { endOfMonth, endOfWeek, getLocalTimeZone, startOfMonth, startOfWeek, today } from "@internationalized/date";
|
|
5
|
+
import type { DateValue } from "react-aria-components";
|
|
6
|
+
import { DatePicker as AriaDatePicker, DateRangePicker as AriaDateRangePicker, Dialog as AriaDialog, useLocale } from "react-aria-components";
|
|
7
|
+
import { Button } from "@/components/base/buttons/button";
|
|
8
|
+
import { Calendar } from "./calendar";
|
|
9
|
+
import { DateInput } from "./date-input";
|
|
10
|
+
import { DatePicker } from "./date-picker";
|
|
11
|
+
import { DateRangePicker } from "./date-range-picker";
|
|
12
|
+
import { RangeCalendar } from "./range-calendar";
|
|
13
|
+
import { RangePresetButton } from "./range-preset";
|
|
14
|
+
|
|
15
|
+
const now = today(getLocalTimeZone());
|
|
16
|
+
|
|
17
|
+
export const CalendarDemo = () => <Calendar aria-label="Calendar" />;
|
|
18
|
+
|
|
19
|
+
export const CalendarCardDemo = () => (
|
|
20
|
+
<AriaDatePicker aria-label="Calendar card" defaultValue={now}>
|
|
21
|
+
<AriaDialog className="rounded-2xl bg-primary shadow-xl ring ring-secondary_alt">
|
|
22
|
+
<div className="flex px-6 py-5">
|
|
23
|
+
<Calendar />
|
|
24
|
+
</div>
|
|
25
|
+
<div className="grid grid-cols-2 gap-3 border-t border-secondary p-4">
|
|
26
|
+
<Button size="md" color="secondary">
|
|
27
|
+
Cancel
|
|
28
|
+
</Button>
|
|
29
|
+
<Button size="md" color="primary">
|
|
30
|
+
Apply
|
|
31
|
+
</Button>
|
|
32
|
+
</div>
|
|
33
|
+
</AriaDialog>
|
|
34
|
+
</AriaDatePicker>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export const DatePickerDemo = () => <DatePicker aria-label="Date picker" defaultValue={now} />;
|
|
38
|
+
|
|
39
|
+
export const DatePickerControlledDemo = () => {
|
|
40
|
+
const [value, setValue] = useState<DateValue | null>(now);
|
|
41
|
+
|
|
42
|
+
return <DatePicker aria-label="Date picker" value={value} onChange={setValue} />;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const RangeCalendarDemo = () => <RangeCalendar aria-label="Range calendar" />;
|
|
46
|
+
|
|
47
|
+
export const RangeCalendarCardDemo = () => {
|
|
48
|
+
const { locale } = useLocale();
|
|
49
|
+
const [focusedValue, setFocusedValue] = useState<DateValue | null>(null);
|
|
50
|
+
const [value, setValue] = useState<{ start: DateValue; end: DateValue } | null>({
|
|
51
|
+
start: now.subtract({ days: 7 }),
|
|
52
|
+
end: now,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const presets = useMemo(
|
|
56
|
+
() => ({
|
|
57
|
+
today: { label: "Today", value: { start: now, end: now } },
|
|
58
|
+
yesterday: { label: "Yesterday", value: { start: now.subtract({ days: 1 }), end: now.subtract({ days: 1 }) } },
|
|
59
|
+
thisWeek: { label: "This week", value: { start: startOfWeek(now, locale), end: endOfWeek(now, locale) } },
|
|
60
|
+
lastWeek: {
|
|
61
|
+
label: "Last week",
|
|
62
|
+
value: {
|
|
63
|
+
start: startOfWeek(now, locale).subtract({ weeks: 1 }),
|
|
64
|
+
end: endOfWeek(now, locale).subtract({ weeks: 1 }),
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
thisMonth: { label: "This month", value: { start: startOfMonth(now), end: endOfMonth(now) } },
|
|
68
|
+
lastMonth: {
|
|
69
|
+
label: "Last month",
|
|
70
|
+
value: {
|
|
71
|
+
start: startOfMonth(now).subtract({ months: 1 }),
|
|
72
|
+
end: endOfMonth(now).subtract({ months: 1 }),
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
thisYear: { label: "This year", value: { start: startOfMonth(now.set({ month: 1 })), end: endOfMonth(now.set({ month: 12 })) } },
|
|
76
|
+
lastYear: {
|
|
77
|
+
label: "Last year",
|
|
78
|
+
value: {
|
|
79
|
+
start: startOfMonth(now.set({ month: 1 }).subtract({ years: 1 })),
|
|
80
|
+
end: endOfMonth(now.set({ month: 12 }).subtract({ years: 1 })),
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
allTime: {
|
|
84
|
+
label: "All time",
|
|
85
|
+
value: {
|
|
86
|
+
start: now.set({ year: 2000, month: 1, day: 1 }),
|
|
87
|
+
end: now,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
}),
|
|
91
|
+
[locale],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<AriaDateRangePicker aria-label="Range calendar" value={value} onChange={setValue}>
|
|
96
|
+
<AriaDialog className="flex rounded-2xl bg-primary shadow-xl ring ring-secondary_alt focus:outline-hidden">
|
|
97
|
+
<div className="hidden w-38 flex-col gap-0.5 border-r border-solid border-secondary p-3 lg:flex">
|
|
98
|
+
{Object.values(presets).map((preset) => (
|
|
99
|
+
<RangePresetButton
|
|
100
|
+
key={preset.label}
|
|
101
|
+
value={preset.value}
|
|
102
|
+
onClick={() => {
|
|
103
|
+
setFocusedValue(preset.value.start);
|
|
104
|
+
setValue(preset.value);
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
{preset.label}
|
|
108
|
+
</RangePresetButton>
|
|
109
|
+
))}
|
|
110
|
+
</div>
|
|
111
|
+
<div className="flex flex-col">
|
|
112
|
+
<RangeCalendar
|
|
113
|
+
focusedValue={focusedValue}
|
|
114
|
+
onFocusChange={setFocusedValue}
|
|
115
|
+
presets={{
|
|
116
|
+
lastWeek: presets.lastWeek,
|
|
117
|
+
lastMonth: presets.lastMonth,
|
|
118
|
+
lastYear: presets.lastYear,
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
121
|
+
<div className="flex justify-between gap-3 border-t border-secondary p-4">
|
|
122
|
+
<div className="hidden items-center gap-3 md:flex">
|
|
123
|
+
<DateInput slot="start" className="w-36" />
|
|
124
|
+
<div className="text-md text-quaternary">–</div>
|
|
125
|
+
<DateInput slot="end" className="w-36" />
|
|
126
|
+
</div>
|
|
127
|
+
<div className="grid w-full grid-cols-2 gap-3 md:flex md:w-auto">
|
|
128
|
+
<Button size="md" color="secondary">
|
|
129
|
+
Cancel
|
|
130
|
+
</Button>
|
|
131
|
+
<Button size="md" color="primary">
|
|
132
|
+
Apply
|
|
133
|
+
</Button>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</AriaDialog>
|
|
138
|
+
</AriaDateRangePicker>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const DateRangePickerDemo = () => {
|
|
143
|
+
return (
|
|
144
|
+
<DateRangePicker
|
|
145
|
+
aria-label="Date range picker"
|
|
146
|
+
defaultValue={{
|
|
147
|
+
start: now.subtract({ days: 7 }),
|
|
148
|
+
end: now,
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export const DateRangePickerControlledDemo = () => {
|
|
155
|
+
const [value, setValue] = useState<{ start: DateValue; end: DateValue } | null>({
|
|
156
|
+
start: now.subtract({ days: 7 }),
|
|
157
|
+
end: now,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return <DateRangePicker aria-label="Date range picker" shouldCloseOnSelect={false} value={value} onChange={setValue} />;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const DarkModeDemo = () => {
|
|
164
|
+
const [value, setValue] = useState<DateValue | null>(now);
|
|
165
|
+
const [focusedValue, setFocusedValue] = useState<DateValue | null>(now);
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<div className="relative h-180 w-full max-w-180 [--clip-boundary:50%]">
|
|
169
|
+
<div
|
|
170
|
+
style={{
|
|
171
|
+
clipPath: "polygon(0 0, var(--clip-boundary) 0, var(--clip-boundary) 100%, 0 100%)",
|
|
172
|
+
transitionTimingFunction: "cubic-bezier(0.25, 0.1, 0.25, 1)",
|
|
173
|
+
}}
|
|
174
|
+
className="peer/light absolute inset-0 flex items-center justify-center overflow-hidden rounded-2xl bg-tertiary outline-1 -outline-offset-1 outline-secondary_alt transition-all duration-200 peer-hover/dark:[--clip-boundary:10%] hover:z-10 hover:[--clip-boundary:90%]"
|
|
175
|
+
>
|
|
176
|
+
<AriaDatePicker aria-label="Calendar card" value={value} onChange={setValue}>
|
|
177
|
+
<AriaDialog className="rounded-2xl bg-primary shadow-xl ring ring-secondary_alt">
|
|
178
|
+
<div className="flex px-6 py-5">
|
|
179
|
+
<Calendar focusedValue={focusedValue} onFocusChange={setFocusedValue} />
|
|
180
|
+
</div>
|
|
181
|
+
<div className="grid grid-cols-2 gap-3 border-t border-secondary p-4">
|
|
182
|
+
<Button size="md" color="secondary">
|
|
183
|
+
Cancel
|
|
184
|
+
</Button>
|
|
185
|
+
<Button size="md" color="primary">
|
|
186
|
+
Apply
|
|
187
|
+
</Button>
|
|
188
|
+
</div>
|
|
189
|
+
</AriaDialog>
|
|
190
|
+
</AriaDatePicker>
|
|
191
|
+
</div>
|
|
192
|
+
<div
|
|
193
|
+
style={{
|
|
194
|
+
clipPath: "polygon(var(--clip-boundary) 0, 100% 0, 100% 100%, var(--clip-boundary) 100%)",
|
|
195
|
+
transitionTimingFunction: "cubic-bezier(0.25, 0.1, 0.25, 1)",
|
|
196
|
+
}}
|
|
197
|
+
className="peer/dark dark-mode absolute inset-0 flex items-center justify-center overflow-hidden rounded-2xl bg-tertiary outline-1 -outline-offset-1 outline-secondary_alt transition-all duration-200 peer-hover/light:[--clip-boundary:90%] hover:z-10 hover:[--clip-boundary:10%]"
|
|
198
|
+
>
|
|
199
|
+
<AriaDatePicker aria-label="Calendar card" value={value} onChange={setValue}>
|
|
200
|
+
<AriaDialog className="rounded-2xl bg-primary shadow-xl ring ring-secondary_alt">
|
|
201
|
+
<div className="flex px-6 py-5">
|
|
202
|
+
<Calendar focusedValue={focusedValue} onFocusChange={setFocusedValue} />
|
|
203
|
+
</div>
|
|
204
|
+
<div className="grid grid-cols-2 gap-3 border-t border-secondary p-4">
|
|
205
|
+
<Button size="md" color="secondary">
|
|
206
|
+
Cancel
|
|
207
|
+
</Button>
|
|
208
|
+
<Button size="md" color="primary">
|
|
209
|
+
Apply
|
|
210
|
+
</Button>
|
|
211
|
+
</div>
|
|
212
|
+
</AriaDialog>
|
|
213
|
+
</AriaDatePicker>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { FC } from "react";
|
|
4
|
+
import * as Demos from "./date-picker.demo";
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
title: "Application/Date pickers",
|
|
8
|
+
decorators: [
|
|
9
|
+
(Story: FC) => (
|
|
10
|
+
<div className="flex min-h-screen items-start justify-center bg-tertiary p-8">
|
|
11
|
+
<div className="flex w-75 justify-end">
|
|
12
|
+
<Story />
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
),
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const CalendarDemo = () => <Demos.CalendarDemo />;
|
|
20
|
+
CalendarDemo.storyName = "Calendar";
|
|
21
|
+
|
|
22
|
+
export const CalendarCardDemo = () => <Demos.CalendarCardDemo />;
|
|
23
|
+
CalendarCardDemo.storyName = "Calendar card";
|
|
24
|
+
|
|
25
|
+
export const DatePickerDemo = () => <Demos.DatePickerDemo />;
|
|
26
|
+
DatePickerDemo.storyName = "Date picker";
|
|
27
|
+
|
|
28
|
+
export const DatePickerControlledDemo = () => <Demos.DatePickerControlledDemo />;
|
|
29
|
+
DatePickerControlledDemo.storyName = "Date picker controlled";
|
|
30
|
+
|
|
31
|
+
export const RangeCalendarDemo = () => <Demos.RangeCalendarDemo />;
|
|
32
|
+
RangeCalendarDemo.storyName = "Range calendar";
|
|
33
|
+
|
|
34
|
+
export const RangeCalendarCardDemo = () => <Demos.RangeCalendarCardDemo />;
|
|
35
|
+
RangeCalendarCardDemo.storyName = "Range calendar card";
|
|
36
|
+
|
|
37
|
+
export const DateRangePickerDemo = () => <Demos.DateRangePickerDemo />;
|
|
38
|
+
DateRangePickerDemo.storyName = "Date range picker";
|
|
39
|
+
|
|
40
|
+
export const DateRangePickerControlledDemo = () => <Demos.DateRangePickerControlledDemo />;
|
|
41
|
+
DateRangePickerControlledDemo.storyName = "Date range picker controlled";
|
|
42
|
+
|
|
43
|
+
export const DarkModeDemo = () => <Demos.DarkModeDemo />;
|
|
44
|
+
DarkModeDemo.storyName = "Dark mode demo";
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { FileUpload, getReadableFileSize } from "@/components/application/file-upload/file-upload-base";
|
|
5
|
+
|
|
6
|
+
const uploadFile = (file: File, onProgress: (progress: number) => void) => {
|
|
7
|
+
// Add your upload logic here...
|
|
8
|
+
|
|
9
|
+
// This is dummy upload logic
|
|
10
|
+
let progress = 0;
|
|
11
|
+
const interval = setInterval(() => {
|
|
12
|
+
onProgress(++progress);
|
|
13
|
+
if (progress === 100) {
|
|
14
|
+
clearInterval(interval);
|
|
15
|
+
}
|
|
16
|
+
}, 100);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type UploadedFile = { id: string; name: string; size: number; progress: number; type?: string; failed?: boolean };
|
|
20
|
+
|
|
21
|
+
export const ImagesOnlyDemo = () => {
|
|
22
|
+
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([]);
|
|
23
|
+
|
|
24
|
+
const handleDropFiles = (files: FileList) => {
|
|
25
|
+
const newFiles = Array.from(files);
|
|
26
|
+
const newFilesWithIds = newFiles.map((file) => ({
|
|
27
|
+
id: Math.random().toString(),
|
|
28
|
+
name: file.name,
|
|
29
|
+
size: file.size,
|
|
30
|
+
type: file.type,
|
|
31
|
+
progress: 0,
|
|
32
|
+
fileObject: file,
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
setUploadedFiles([...newFilesWithIds.map(({ fileObject: _, ...file }) => file), ...uploadedFiles]);
|
|
36
|
+
|
|
37
|
+
newFilesWithIds.forEach(({ id, fileObject }) => {
|
|
38
|
+
uploadFile(fileObject, (progress) => {
|
|
39
|
+
setUploadedFiles((prev) => prev.map((uploadedFile) => (uploadedFile.id === id ? { ...uploadedFile, progress } : uploadedFile)));
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handleDropUnacceptedFiles = (files: FileList) => {
|
|
45
|
+
console.log("Unaccepted files", files);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleDeleteFile = (id: string) => {
|
|
49
|
+
setUploadedFiles((prev) => prev.filter((file) => file.id !== id));
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const handleRetryFile = (id: string) => {
|
|
53
|
+
const file = uploadedFiles.find((file) => file.id === id);
|
|
54
|
+
if (!file) return;
|
|
55
|
+
|
|
56
|
+
uploadFile(new File([], file.name, { type: file.type }), (progress) => {
|
|
57
|
+
setUploadedFiles((prev) => prev.map((uploadedFile) => (uploadedFile.id === id ? { ...uploadedFile, progress, failed: false } : uploadedFile)));
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<FileUpload.Root>
|
|
63
|
+
<FileUpload.DropZone
|
|
64
|
+
accept="image/*"
|
|
65
|
+
hint="Please upload PNG or JPEG images only."
|
|
66
|
+
onDropFiles={handleDropFiles}
|
|
67
|
+
onDropUnacceptedFiles={handleDropUnacceptedFiles}
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
<FileUpload.List>
|
|
71
|
+
{uploadedFiles.map((file) => (
|
|
72
|
+
<FileUpload.ListItemProgressBar
|
|
73
|
+
key={file.id}
|
|
74
|
+
{...file}
|
|
75
|
+
size={file.size}
|
|
76
|
+
onDelete={() => handleDeleteFile(file.id)}
|
|
77
|
+
onRetry={() => handleRetryFile(file.id)}
|
|
78
|
+
/>
|
|
79
|
+
))}
|
|
80
|
+
</FileUpload.List>
|
|
81
|
+
</FileUpload.Root>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const MaxSizeLimitDemo = () => {
|
|
86
|
+
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([]);
|
|
87
|
+
|
|
88
|
+
const handleDropFiles = (files: FileList) => {
|
|
89
|
+
const newFiles = Array.from(files);
|
|
90
|
+
const newFilesWithIds = newFiles.map((file) => ({
|
|
91
|
+
id: Math.random().toString(),
|
|
92
|
+
name: file.name,
|
|
93
|
+
size: file.size,
|
|
94
|
+
type: file.type,
|
|
95
|
+
progress: 0,
|
|
96
|
+
fileObject: file,
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
setUploadedFiles([...newFilesWithIds.map(({ fileObject: _, ...file }) => file), ...uploadedFiles]);
|
|
100
|
+
|
|
101
|
+
newFilesWithIds.forEach(({ id, fileObject }) => {
|
|
102
|
+
uploadFile(fileObject, (progress) => {
|
|
103
|
+
setUploadedFiles((prev) => prev.map((uploadedFile) => (uploadedFile.id === id ? { ...uploadedFile, progress } : uploadedFile)));
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const handleMaxSizeExceed = (files: FileList) => {
|
|
109
|
+
console.log("Max size exceeded", files);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handleDeleteFile = (id: string) => {
|
|
113
|
+
setUploadedFiles((prev) => prev.filter((file) => file.id !== id));
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const handleRetryFile = (id: string) => {
|
|
117
|
+
const file = uploadedFiles.find((file) => file.id === id);
|
|
118
|
+
if (!file) return;
|
|
119
|
+
|
|
120
|
+
uploadFile(new File([], file.name, { type: file.type }), (progress) => {
|
|
121
|
+
setUploadedFiles((prev) => prev.map((uploadedFile) => (uploadedFile.id === id ? { ...uploadedFile, progress, failed: false } : uploadedFile)));
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const MAX_SIZE = 1024 * 1024 * 1;
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<FileUpload.Root>
|
|
129
|
+
<FileUpload.DropZone
|
|
130
|
+
maxSize={MAX_SIZE}
|
|
131
|
+
hint={`Upload files to add to this project (max. ${getReadableFileSize(MAX_SIZE)}).`}
|
|
132
|
+
onDropFiles={handleDropFiles}
|
|
133
|
+
onSizeLimitExceed={handleMaxSizeExceed}
|
|
134
|
+
/>
|
|
135
|
+
|
|
136
|
+
<FileUpload.List>
|
|
137
|
+
{uploadedFiles.map((file) => (
|
|
138
|
+
<FileUpload.ListItemProgressBar
|
|
139
|
+
key={file.id}
|
|
140
|
+
{...file}
|
|
141
|
+
size={file.size}
|
|
142
|
+
onDelete={() => handleDeleteFile(file.id)}
|
|
143
|
+
onRetry={() => handleRetryFile(file.id)}
|
|
144
|
+
/>
|
|
145
|
+
))}
|
|
146
|
+
</FileUpload.List>
|
|
147
|
+
</FileUpload.Root>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const DisabledDemo = () => {
|
|
152
|
+
return (
|
|
153
|
+
<FileUpload.Root>
|
|
154
|
+
<FileUpload.DropZone isDisabled />
|
|
155
|
+
</FileUpload.Root>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const placeholderFiles = [
|
|
160
|
+
{
|
|
161
|
+
id: "file-01",
|
|
162
|
+
name: "Example dashboard screenshot.jpg",
|
|
163
|
+
type: "jpg",
|
|
164
|
+
size: 720 * 1024,
|
|
165
|
+
progress: 50,
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: "file-02",
|
|
169
|
+
name: "Tech design requirements_2.pdf",
|
|
170
|
+
type: "pdf",
|
|
171
|
+
size: 720 * 1024,
|
|
172
|
+
progress: 100,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "file-03",
|
|
176
|
+
name: "Tech design requirements.pdf",
|
|
177
|
+
type: "pdf",
|
|
178
|
+
failed: true,
|
|
179
|
+
size: 1024 * 1024 * 1,
|
|
180
|
+
progress: 0,
|
|
181
|
+
},
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
export const FileUploadProgressBar = (props: { isDisabled?: boolean }) => {
|
|
185
|
+
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>(placeholderFiles);
|
|
186
|
+
|
|
187
|
+
const handleDropFiles = (files: FileList) => {
|
|
188
|
+
const newFiles = Array.from(files);
|
|
189
|
+
const newFilesWithIds = newFiles.map((file) => ({
|
|
190
|
+
id: Math.random().toString(),
|
|
191
|
+
name: file.name,
|
|
192
|
+
size: file.size,
|
|
193
|
+
type: file.type,
|
|
194
|
+
progress: 0,
|
|
195
|
+
fileObject: file,
|
|
196
|
+
}));
|
|
197
|
+
|
|
198
|
+
setUploadedFiles([...newFilesWithIds.map(({ fileObject: _, ...file }) => file), ...uploadedFiles]);
|
|
199
|
+
|
|
200
|
+
newFilesWithIds.forEach(({ id, fileObject }) => {
|
|
201
|
+
uploadFile(fileObject, (progress) => {
|
|
202
|
+
setUploadedFiles((prev) => prev.map((uploadedFile) => (uploadedFile.id === id ? { ...uploadedFile, progress } : uploadedFile)));
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const handleDeleteFile = (id: string) => {
|
|
208
|
+
setUploadedFiles((prev) => prev.filter((file) => file.id !== id));
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const handleRetryFile = (id: string) => {
|
|
212
|
+
const file = uploadedFiles.find((file) => file.id === id);
|
|
213
|
+
if (!file) return;
|
|
214
|
+
|
|
215
|
+
uploadFile(new File([], file.name, { type: file.type }), (progress) => {
|
|
216
|
+
setUploadedFiles((prev) => prev.map((uploadedFile) => (uploadedFile.id === id ? { ...uploadedFile, progress, failed: false } : uploadedFile)));
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<FileUpload.Root>
|
|
222
|
+
<FileUpload.DropZone isDisabled={props.isDisabled} onDropFiles={handleDropFiles} />
|
|
223
|
+
|
|
224
|
+
<FileUpload.List>
|
|
225
|
+
{uploadedFiles.map((file) => (
|
|
226
|
+
<FileUpload.ListItemProgressBar
|
|
227
|
+
key={file.id}
|
|
228
|
+
{...file}
|
|
229
|
+
size={file.size}
|
|
230
|
+
onDelete={() => handleDeleteFile(file.id)}
|
|
231
|
+
onRetry={() => handleRetryFile(file.id)}
|
|
232
|
+
/>
|
|
233
|
+
))}
|
|
234
|
+
</FileUpload.List>
|
|
235
|
+
</FileUpload.Root>
|
|
236
|
+
);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export const FileUploadProgressFill = (props: { isDisabled?: boolean }) => {
|
|
240
|
+
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>(placeholderFiles);
|
|
241
|
+
|
|
242
|
+
const handleDropFiles = (files: FileList) => {
|
|
243
|
+
const newFiles = Array.from(files);
|
|
244
|
+
const newFilesWithIds = newFiles.map((file) => ({
|
|
245
|
+
id: Math.random().toString(),
|
|
246
|
+
name: file.name,
|
|
247
|
+
size: file.size,
|
|
248
|
+
type: file.type,
|
|
249
|
+
progress: 0,
|
|
250
|
+
fileObject: file,
|
|
251
|
+
}));
|
|
252
|
+
|
|
253
|
+
setUploadedFiles([...newFilesWithIds.map(({ fileObject: _, ...file }) => file), ...uploadedFiles]);
|
|
254
|
+
|
|
255
|
+
newFilesWithIds.forEach(({ id, fileObject }) => {
|
|
256
|
+
uploadFile(fileObject, (progress) => {
|
|
257
|
+
setUploadedFiles((prev) => prev.map((uploadedFile) => (uploadedFile.id === id ? { ...uploadedFile, progress } : uploadedFile)));
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const handleDeleteFile = (id: string) => {
|
|
263
|
+
setUploadedFiles((prev) => prev.filter((file) => file.id !== id));
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const handleRetryFile = (id: string) => {
|
|
267
|
+
const file = uploadedFiles.find((file) => file.id === id);
|
|
268
|
+
if (!file) return;
|
|
269
|
+
|
|
270
|
+
uploadFile(new File([], file.name, { type: file.type }), (progress) => {
|
|
271
|
+
setUploadedFiles((prev) => prev.map((uploadedFile) => (uploadedFile.id === id ? { ...uploadedFile, progress, failed: false } : uploadedFile)));
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
return (
|
|
276
|
+
<FileUpload.Root>
|
|
277
|
+
<FileUpload.DropZone isDisabled={props.isDisabled} onDropFiles={handleDropFiles} />
|
|
278
|
+
|
|
279
|
+
<FileUpload.List>
|
|
280
|
+
{uploadedFiles.map((file) => (
|
|
281
|
+
<FileUpload.ListItemProgressFill
|
|
282
|
+
key={file.id}
|
|
283
|
+
{...file}
|
|
284
|
+
size={file.size}
|
|
285
|
+
onDelete={() => handleDeleteFile(file.id)}
|
|
286
|
+
onRetry={() => handleRetryFile(file.id)}
|
|
287
|
+
/>
|
|
288
|
+
))}
|
|
289
|
+
</FileUpload.List>
|
|
290
|
+
</FileUpload.Root>
|
|
291
|
+
);
|
|
292
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { FC } from "react";
|
|
2
|
+
import { Draggable } from "./draggable";
|
|
3
|
+
import * as FileUploads from "./file-upload.demo";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: "Application/File upload",
|
|
7
|
+
decorators: [
|
|
8
|
+
(Story: FC) => (
|
|
9
|
+
<div data-drag-constraint className="flex min-h-screen w-full flex-col items-center gap-12 bg-primary p-8">
|
|
10
|
+
<Story />
|
|
11
|
+
</div>
|
|
12
|
+
),
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const FileUploadProgressBar = () => (
|
|
17
|
+
<>
|
|
18
|
+
<div className="mb-12 flex">
|
|
19
|
+
<Draggable name="chatgpt-clone.ts" type="application/typescript" size={1024 * 1024 * 0.5} />
|
|
20
|
+
<Draggable name="Side project.mp4" type="video/mp4" size={1024 * 1024 * 2.2} />
|
|
21
|
+
<Draggable name="Invoice #876.pdf" type="application/pdf" size={1024 * 1024 * 1.2} />
|
|
22
|
+
</div>
|
|
23
|
+
<div className="flex w-full max-w-xl flex-col gap-12">
|
|
24
|
+
<FileUploads.FileUploadProgressBar />
|
|
25
|
+
</div>
|
|
26
|
+
</>
|
|
27
|
+
);
|
|
28
|
+
FileUploadProgressBar.storyName = "File upload progress bar";
|
|
29
|
+
|
|
30
|
+
export const FileUploadProgressBarDisabled = () => (
|
|
31
|
+
<>
|
|
32
|
+
<div className="mb-12 flex">
|
|
33
|
+
<Draggable name="chatgpt-clone.ts" type="application/typescript" size={1024 * 1024 * 0.5} />
|
|
34
|
+
<Draggable name="Side project.mp4" type="video/mp4" size={1024 * 1024 * 2.2} />
|
|
35
|
+
<Draggable name="Invoice #876.pdf" type="application/pdf" size={1024 * 1024 * 1.2} />
|
|
36
|
+
</div>
|
|
37
|
+
<div className="flex w-full max-w-xl flex-col gap-12">
|
|
38
|
+
<FileUploads.FileUploadProgressBar isDisabled />
|
|
39
|
+
</div>
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
42
|
+
FileUploadProgressBarDisabled.storyName = "File upload progress bar disabled";
|
|
43
|
+
|
|
44
|
+
export const FileUploadProgressFill = () => (
|
|
45
|
+
<>
|
|
46
|
+
<div className="mb-12 flex">
|
|
47
|
+
<Draggable name="chatgpt-clone.ts" type="application/typescript" size={1024 * 1024 * 0.5} />
|
|
48
|
+
<Draggable name="Side project.mp4" type="video/mp4" size={1024 * 1024 * 2.2} />
|
|
49
|
+
<Draggable name="Invoice #876.pdf" type="application/pdf" size={1024 * 1024 * 1.2} />
|
|
50
|
+
</div>
|
|
51
|
+
<div className="flex w-full max-w-xl flex-col gap-12">
|
|
52
|
+
<FileUploads.FileUploadProgressFill />
|
|
53
|
+
</div>
|
|
54
|
+
</>
|
|
55
|
+
);
|
|
56
|
+
FileUploadProgressFill.storyName = "File upload progress fill";
|
|
57
|
+
|
|
58
|
+
export const FileUploadProgressFillDisabled = () => (
|
|
59
|
+
<>
|
|
60
|
+
<div className="mb-12 flex">
|
|
61
|
+
<Draggable name="chatgpt-clone.ts" type="application/typescript" size={1024 * 1024 * 0.5} />
|
|
62
|
+
<Draggable name="Side project.mp4" type="video/mp4" size={1024 * 1024 * 2.2} />
|
|
63
|
+
<Draggable name="Invoice #876.pdf" type="application/pdf" size={1024 * 1024 * 1.2} />
|
|
64
|
+
</div>
|
|
65
|
+
<div className="flex w-full max-w-xl flex-col gap-12">
|
|
66
|
+
<FileUploads.FileUploadProgressFill isDisabled />
|
|
67
|
+
</div>
|
|
68
|
+
</>
|
|
69
|
+
);
|
|
70
|
+
FileUploadProgressFillDisabled.storyName = "File upload progress fill disabled";
|