@syscore/ui-library 1.27.3 → 2.0.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/client/components/ui/app-bar.tsx +299 -0
- package/client/components/ui/banner.tsx +108 -0
- package/client/components/ui/bottom-navigation.tsx +223 -0
- package/client/components/ui/card.tsx +108 -26
- package/client/components/ui/date-picker.tsx +188 -0
- package/client/components/ui/empty-state.tsx +197 -0
- package/client/components/ui/file-upload.tsx +214 -0
- package/client/components/ui/footer.tsx +179 -0
- package/client/components/ui/layout.tsx +381 -0
- package/client/components/ui/stepper.tsx +148 -0
- package/client/components/ui/system-bar.tsx +119 -0
- package/client/components/ui/timeline.tsx +181 -0
- package/client/global.css +2304 -41
- package/client/ui/AppBar/app-bar.stories.tsx +280 -0
- package/client/ui/Banner/banner.stories.tsx +238 -0
- package/client/ui/BottomNavigation/bottom-navigation.stories.tsx +285 -0
- package/client/ui/Card.stories.tsx +285 -168
- package/client/ui/DatePicker/DatePicker.stories.tsx +293 -0
- package/client/ui/EmptyState/empty-state.stories.tsx +251 -0
- package/client/ui/FileUpload/FileUpload.stories.tsx +218 -0
- package/client/ui/Footer/footer.stories.tsx +194 -0
- package/client/ui/Layout.stories.tsx +1481 -0
- package/client/ui/Stepper/Stepper.stories.tsx +344 -0
- package/client/ui/SystemBar/system-bar.stories.tsx +179 -0
- package/client/ui/Timeline/timeline.stories.tsx +404 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +219 -0
- package/dist/index.es.js +1430 -24
- package/package.json +1 -1
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { DatePicker, DateRangePicker, type DateRange } from "../../components/ui/date-picker";
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "UI/DatePicker",
|
|
7
|
+
component: DatePicker,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: "padded",
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component: [
|
|
14
|
+
"A text input that opens a Calendar popover when clicked. Supports single date and date range selection.",
|
|
15
|
+
"",
|
|
16
|
+
"**The difference from Calendar:**",
|
|
17
|
+
"- `Calendar` — always visible grid, sits on the page",
|
|
18
|
+
"- `DatePicker` — input field that pops open a calendar, closes after selection",
|
|
19
|
+
"",
|
|
20
|
+
"**Import:**",
|
|
21
|
+
"```tsx",
|
|
22
|
+
`import { DatePicker, DateRangePicker } from "@syscore/ui-library"`,
|
|
23
|
+
"```",
|
|
24
|
+
"",
|
|
25
|
+
"**Key props:**",
|
|
26
|
+
"",
|
|
27
|
+
"| Prop | Type | Default | Effect |",
|
|
28
|
+
"|------|------|---------|--------|",
|
|
29
|
+
"| `value` | `Date` | — | Controlled selected date |",
|
|
30
|
+
"| `onChange` | `(date?: Date) => void` | — | Called when date is picked |",
|
|
31
|
+
"| `placeholder` | string | `\"Select date\"` | Input placeholder text |",
|
|
32
|
+
"| `dateFormat` | string | `\"MMM d, yyyy\"` | Display format (date-fns) |",
|
|
33
|
+
"| `fromDate` | `Date` | — | Disable dates before this |",
|
|
34
|
+
"| `toDate` | `Date` | — | Disable dates after this |",
|
|
35
|
+
"| `disabled` | boolean | `false` | Disable the input |",
|
|
36
|
+
].join("\n"),
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
} satisfies Meta<typeof DatePicker>;
|
|
41
|
+
|
|
42
|
+
export default meta;
|
|
43
|
+
type Story = StoryObj<typeof meta>;
|
|
44
|
+
|
|
45
|
+
export const Default: Story = {
|
|
46
|
+
parameters: {
|
|
47
|
+
docs: {
|
|
48
|
+
source: {
|
|
49
|
+
code: `import { DatePicker } from "@syscore/ui-library"
|
|
50
|
+
import { useState } from "react"
|
|
51
|
+
|
|
52
|
+
const [date, setDate] = useState<Date>()
|
|
53
|
+
|
|
54
|
+
<DatePicker
|
|
55
|
+
value={date}
|
|
56
|
+
onChange={setDate}
|
|
57
|
+
placeholder="Select date"
|
|
58
|
+
/>`,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
render: () => {
|
|
63
|
+
const [date, setDate] = useState<Date>();
|
|
64
|
+
return (
|
|
65
|
+
<div className="max-w-xs">
|
|
66
|
+
<DatePicker
|
|
67
|
+
value={date}
|
|
68
|
+
onChange={setDate}
|
|
69
|
+
placeholder="Select date"
|
|
70
|
+
/>
|
|
71
|
+
{date && (
|
|
72
|
+
<p className="mt-2 text-sm text-gray-500">Selected: {date.toDateString()}</p>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const WithDefaultValue: Story = {
|
|
80
|
+
name: "With default value",
|
|
81
|
+
parameters: {
|
|
82
|
+
docs: {
|
|
83
|
+
source: {
|
|
84
|
+
code: `import { DatePicker } from "@syscore/ui-library"
|
|
85
|
+
import { useState } from "react"
|
|
86
|
+
|
|
87
|
+
const [date, setDate] = useState<Date>(new Date())
|
|
88
|
+
|
|
89
|
+
<DatePicker value={date} onChange={setDate} />`,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
render: () => {
|
|
94
|
+
const [date, setDate] = useState<Date>(new Date());
|
|
95
|
+
return (
|
|
96
|
+
<div className="max-w-xs">
|
|
97
|
+
<DatePicker value={date} onChange={setDate} />
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const WithDateLimits: Story = {
|
|
104
|
+
name: "With date limits",
|
|
105
|
+
parameters: {
|
|
106
|
+
docs: {
|
|
107
|
+
source: {
|
|
108
|
+
code: `import { DatePicker } from "@syscore/ui-library"
|
|
109
|
+
import { useState } from "react"
|
|
110
|
+
|
|
111
|
+
// Only allow dates from today onwards
|
|
112
|
+
const [date, setDate] = useState<Date>()
|
|
113
|
+
const today = new Date()
|
|
114
|
+
|
|
115
|
+
<DatePicker
|
|
116
|
+
value={date}
|
|
117
|
+
onChange={setDate}
|
|
118
|
+
fromDate={today}
|
|
119
|
+
placeholder="Select future date"
|
|
120
|
+
/>`,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
render: () => {
|
|
125
|
+
const [date, setDate] = useState<Date>();
|
|
126
|
+
const today = new Date();
|
|
127
|
+
return (
|
|
128
|
+
<div className="max-w-xs">
|
|
129
|
+
<DatePicker
|
|
130
|
+
value={date}
|
|
131
|
+
onChange={setDate}
|
|
132
|
+
fromDate={today}
|
|
133
|
+
placeholder="Select future date"
|
|
134
|
+
/>
|
|
135
|
+
<p className="mt-2 text-xs text-gray-400">Dates before today are disabled</p>
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const CustomFormat: Story = {
|
|
142
|
+
name: "Custom date format",
|
|
143
|
+
parameters: {
|
|
144
|
+
docs: {
|
|
145
|
+
source: {
|
|
146
|
+
code: `import { DatePicker } from "@syscore/ui-library"
|
|
147
|
+
|
|
148
|
+
// Uses date-fns format tokens
|
|
149
|
+
<DatePicker
|
|
150
|
+
value={date}
|
|
151
|
+
onChange={setDate}
|
|
152
|
+
dateFormat="dd/MM/yyyy"
|
|
153
|
+
/>`,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
render: () => {
|
|
158
|
+
const [date, setDate] = useState<Date>(new Date());
|
|
159
|
+
return (
|
|
160
|
+
<div className="flex flex-col gap-4 max-w-xs">
|
|
161
|
+
<div className="flex flex-col gap-1">
|
|
162
|
+
<span className="text-xs text-gray-500">MMM d, yyyy (default)</span>
|
|
163
|
+
<DatePicker value={date} onChange={setDate} dateFormat="MMM d, yyyy" />
|
|
164
|
+
</div>
|
|
165
|
+
<div className="flex flex-col gap-1">
|
|
166
|
+
<span className="text-xs text-gray-500">dd/MM/yyyy</span>
|
|
167
|
+
<DatePicker value={date} onChange={setDate} dateFormat="dd/MM/yyyy" />
|
|
168
|
+
</div>
|
|
169
|
+
<div className="flex flex-col gap-1">
|
|
170
|
+
<span className="text-xs text-gray-500">MMMM do, yyyy</span>
|
|
171
|
+
<DatePicker value={date} onChange={setDate} dateFormat="MMMM do, yyyy" />
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const Disabled: Story = {
|
|
179
|
+
parameters: {
|
|
180
|
+
docs: {
|
|
181
|
+
source: {
|
|
182
|
+
code: `import { DatePicker } from "@syscore/ui-library"
|
|
183
|
+
|
|
184
|
+
<DatePicker disabled placeholder="Not available" />`,
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
render: () => (
|
|
189
|
+
<div className="max-w-xs">
|
|
190
|
+
<DatePicker disabled placeholder="Not available" />
|
|
191
|
+
</div>
|
|
192
|
+
),
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export const RangePicker: Story = {
|
|
196
|
+
name: "Date range picker",
|
|
197
|
+
parameters: {
|
|
198
|
+
docs: {
|
|
199
|
+
source: {
|
|
200
|
+
code: `import { DateRangePicker, type DateRange } from "@syscore/ui-library"
|
|
201
|
+
import { useState } from "react"
|
|
202
|
+
|
|
203
|
+
const [range, setRange] = useState<DateRange>()
|
|
204
|
+
|
|
205
|
+
<DateRangePicker
|
|
206
|
+
value={range}
|
|
207
|
+
onChange={setRange}
|
|
208
|
+
placeholder="Select date range"
|
|
209
|
+
/>`,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
render: () => {
|
|
214
|
+
const [range, setRange] = useState<DateRange>();
|
|
215
|
+
return (
|
|
216
|
+
<div className="max-w-sm">
|
|
217
|
+
<DateRangePicker
|
|
218
|
+
value={range}
|
|
219
|
+
onChange={setRange}
|
|
220
|
+
placeholder="Select date range"
|
|
221
|
+
/>
|
|
222
|
+
{range?.from && (
|
|
223
|
+
<p className="mt-2 text-sm text-gray-500">
|
|
224
|
+
{range.from.toDateString()} {range.to ? `→ ${range.to.toDateString()}` : "→ pick end date"}
|
|
225
|
+
</p>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
);
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const InForm: Story = {
|
|
233
|
+
name: "Inside a form",
|
|
234
|
+
parameters: {
|
|
235
|
+
docs: {
|
|
236
|
+
source: {
|
|
237
|
+
code: `import { DatePicker, DateRangePicker } from "@syscore/ui-library"
|
|
238
|
+
import { Stack, Text, Button } from "@syscore/ui-library"
|
|
239
|
+
|
|
240
|
+
<Stack gap={6}>
|
|
241
|
+
<Stack gap={2}>
|
|
242
|
+
<Text variant="overline-small">Payment date</Text>
|
|
243
|
+
<DatePicker
|
|
244
|
+
value={paymentDate}
|
|
245
|
+
onChange={setPaymentDate}
|
|
246
|
+
placeholder="Select date"
|
|
247
|
+
/>
|
|
248
|
+
</Stack>
|
|
249
|
+
|
|
250
|
+
<Stack gap={2}>
|
|
251
|
+
<Text variant="overline-small">Project period</Text>
|
|
252
|
+
<DateRangePicker
|
|
253
|
+
value={period}
|
|
254
|
+
onChange={setPeriod}
|
|
255
|
+
placeholder="Start — End"
|
|
256
|
+
/>
|
|
257
|
+
</Stack>
|
|
258
|
+
|
|
259
|
+
<Button variant="primary-dark" size="large">Save</Button>
|
|
260
|
+
</Stack>`,
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
render: () => {
|
|
265
|
+
const [paymentDate, setPaymentDate] = useState<Date>();
|
|
266
|
+
const [period, setPeriod] = useState<DateRange>();
|
|
267
|
+
return (
|
|
268
|
+
<div className="max-w-sm border border-gray-100 rounded-xl p-6">
|
|
269
|
+
<div className="flex flex-col gap-6">
|
|
270
|
+
<div className="flex flex-col gap-2">
|
|
271
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-gray-500">Payment date</span>
|
|
272
|
+
<DatePicker
|
|
273
|
+
value={paymentDate}
|
|
274
|
+
onChange={setPaymentDate}
|
|
275
|
+
placeholder="Select date"
|
|
276
|
+
/>
|
|
277
|
+
</div>
|
|
278
|
+
<div className="flex flex-col gap-2">
|
|
279
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-gray-500">Project period</span>
|
|
280
|
+
<DateRangePicker
|
|
281
|
+
value={period}
|
|
282
|
+
onChange={setPeriod}
|
|
283
|
+
placeholder="Start — End"
|
|
284
|
+
/>
|
|
285
|
+
</div>
|
|
286
|
+
<button className="w-full bg-gray-900 text-white rounded-lg py-2.5 text-sm font-medium">
|
|
287
|
+
Save
|
|
288
|
+
</button>
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
},
|
|
293
|
+
};
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { EmptyState, EmptyStateAction } from "../../components/ui/empty-state";
|
|
3
|
+
|
|
4
|
+
// ─── Inline SVG icons ─────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
function IconSearch() {
|
|
7
|
+
return (
|
|
8
|
+
<svg
|
|
9
|
+
width="64"
|
|
10
|
+
height="64"
|
|
11
|
+
viewBox="0 0 24 24"
|
|
12
|
+
fill="none"
|
|
13
|
+
stroke="currentColor"
|
|
14
|
+
strokeWidth="1.25"
|
|
15
|
+
strokeLinecap="round"
|
|
16
|
+
strokeLinejoin="round"
|
|
17
|
+
aria-hidden="true"
|
|
18
|
+
>
|
|
19
|
+
<circle cx="11" cy="11" r="8" />
|
|
20
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
|
21
|
+
</svg>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function IconCheckCircle() {
|
|
26
|
+
return (
|
|
27
|
+
<svg
|
|
28
|
+
width="64"
|
|
29
|
+
height="64"
|
|
30
|
+
viewBox="0 0 24 24"
|
|
31
|
+
fill="none"
|
|
32
|
+
stroke="currentColor"
|
|
33
|
+
strokeWidth="1.25"
|
|
34
|
+
strokeLinecap="round"
|
|
35
|
+
strokeLinejoin="round"
|
|
36
|
+
aria-hidden="true"
|
|
37
|
+
>
|
|
38
|
+
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
|
|
39
|
+
<polyline points="22 4 12 14.01 9 11.01" />
|
|
40
|
+
</svg>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function IconFileText() {
|
|
45
|
+
return (
|
|
46
|
+
<svg
|
|
47
|
+
width="64"
|
|
48
|
+
height="64"
|
|
49
|
+
viewBox="0 0 24 24"
|
|
50
|
+
fill="none"
|
|
51
|
+
stroke="currentColor"
|
|
52
|
+
strokeWidth="1.25"
|
|
53
|
+
strokeLinecap="round"
|
|
54
|
+
strokeLinejoin="round"
|
|
55
|
+
aria-hidden="true"
|
|
56
|
+
>
|
|
57
|
+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
|
58
|
+
<polyline points="14 2 14 8 20 8" />
|
|
59
|
+
<line x1="16" y1="13" x2="8" y2="13" />
|
|
60
|
+
<line x1="16" y1="17" x2="8" y2="17" />
|
|
61
|
+
<polyline points="10 9 9 9 8 9" />
|
|
62
|
+
</svg>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
const meta = {
|
|
69
|
+
title: "UI/EmptyState",
|
|
70
|
+
component: EmptyState,
|
|
71
|
+
tags: ["autodocs"],
|
|
72
|
+
parameters: {
|
|
73
|
+
layout: "padded",
|
|
74
|
+
},
|
|
75
|
+
argTypes: {
|
|
76
|
+
headline: { control: "text" },
|
|
77
|
+
title: { control: "text" },
|
|
78
|
+
subtitle: { control: "text" },
|
|
79
|
+
text: { control: "text" },
|
|
80
|
+
image: { control: "text" },
|
|
81
|
+
actionText: { control: "text" },
|
|
82
|
+
},
|
|
83
|
+
} satisfies Meta<typeof EmptyState>;
|
|
84
|
+
|
|
85
|
+
export default meta;
|
|
86
|
+
type Story = StoryObj<typeof meta>;
|
|
87
|
+
|
|
88
|
+
// ─── Default ─────────────────────────────────────────────────────────────────
|
|
89
|
+
// Simplest form — headline + title + body text only, no media.
|
|
90
|
+
|
|
91
|
+
export const Default: Story = {
|
|
92
|
+
render: () => (
|
|
93
|
+
<EmptyState
|
|
94
|
+
headline="No Messages Yet"
|
|
95
|
+
title="Check back later."
|
|
96
|
+
text="You haven't received any messages yet. When you do, they'll appear here."
|
|
97
|
+
/>
|
|
98
|
+
),
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// ─── Content ─────────────────────────────────────────────────────────────────
|
|
102
|
+
// Props: headline, title, subtitle, text.
|
|
103
|
+
|
|
104
|
+
export const Content: Story = {
|
|
105
|
+
render: () => (
|
|
106
|
+
<EmptyState
|
|
107
|
+
headline="Whoops, 404"
|
|
108
|
+
title="Page not found"
|
|
109
|
+
subtitle="The requested resource couldn't be located."
|
|
110
|
+
text="The page you were looking for does not exist. Please check the URL or go back to the home page."
|
|
111
|
+
/>
|
|
112
|
+
),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// ─── Media — Icon ─────────────────────────────────────────────────────────────
|
|
116
|
+
// Pass a React node to the `icon` prop to render it in the media area.
|
|
117
|
+
|
|
118
|
+
export const MediaIcon: Story = {
|
|
119
|
+
render: () => (
|
|
120
|
+
<EmptyState
|
|
121
|
+
icon={<IconSearch />}
|
|
122
|
+
title="We couldn't find a match."
|
|
123
|
+
text="Try adjusting your search terms or filters. Sometimes less specific terms or broader queries can help you find what you're looking for."
|
|
124
|
+
/>
|
|
125
|
+
),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// ─── Media — Image ────────────────────────────────────────────────────────────
|
|
129
|
+
// Pass a URL to the `image` prop for an illustration.
|
|
130
|
+
|
|
131
|
+
export const MediaImage: Story = {
|
|
132
|
+
render: () => (
|
|
133
|
+
<EmptyState
|
|
134
|
+
image="https://cdn.vuetifyjs.com/docs/images/components/v-empty-state/teamwork.png"
|
|
135
|
+
imageAlt="Two people working together"
|
|
136
|
+
title="Manage your inventory transfers"
|
|
137
|
+
text="Track and receive your incoming inventory from suppliers"
|
|
138
|
+
/>
|
|
139
|
+
),
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// ─── Actions ─────────────────────────────────────────────────────────────────
|
|
143
|
+
// Use `actionText` + `onClickAction` for a single auto-rendered CTA button.
|
|
144
|
+
|
|
145
|
+
export const Actions: Story = {
|
|
146
|
+
render: () => (
|
|
147
|
+
<EmptyState
|
|
148
|
+
image="https://cdn.vuetifyjs.com/docs/images/components/v-empty-state/connection.svg"
|
|
149
|
+
imageAlt="Connection error illustration"
|
|
150
|
+
title="Something Went Wrong"
|
|
151
|
+
text="There might be a problem with your connection or our servers. Please check your internet connection or try again later. We appreciate your patience."
|
|
152
|
+
actionText="Retry Request"
|
|
153
|
+
onClickAction={() => alert("Retrying…")}
|
|
154
|
+
/>
|
|
155
|
+
),
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// ─── Slot: Default ────────────────────────────────────────────────────────────
|
|
159
|
+
// Anything passed as children renders in the default slot below the text.
|
|
160
|
+
|
|
161
|
+
export const SlotDefault: Story = {
|
|
162
|
+
render: () => (
|
|
163
|
+
<EmptyState
|
|
164
|
+
headline="Welcome,"
|
|
165
|
+
icon={<IconFileText />}
|
|
166
|
+
title="What would you like to do today?"
|
|
167
|
+
>
|
|
168
|
+
<div
|
|
169
|
+
style={{
|
|
170
|
+
display: "grid",
|
|
171
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))",
|
|
172
|
+
gap: "1rem",
|
|
173
|
+
marginTop: "1.5rem",
|
|
174
|
+
width: "100%",
|
|
175
|
+
}}
|
|
176
|
+
>
|
|
177
|
+
{[
|
|
178
|
+
{ title: "Learn More", description: "Start with our dedicated feature guides" },
|
|
179
|
+
{ title: "Try the Playground", description: "Test things out interactively" },
|
|
180
|
+
{ title: "Create a Bin", description: "Create a new bin to store your code" },
|
|
181
|
+
{ title: "Report a Bug", description: "File a bug report for Vuetify" },
|
|
182
|
+
].map((card) => (
|
|
183
|
+
<div key={card.title} className="empty-state-card">
|
|
184
|
+
<div className="empty-state-card-title">{card.title}</div>
|
|
185
|
+
<div className="empty-state-card-desc">{card.description}</div>
|
|
186
|
+
</div>
|
|
187
|
+
))}
|
|
188
|
+
</div>
|
|
189
|
+
</EmptyState>
|
|
190
|
+
),
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// ─── Slot: Media ─────────────────────────────────────────────────────────────
|
|
194
|
+
// `mediaSlot` fully replaces the media area.
|
|
195
|
+
|
|
196
|
+
export const SlotMedia: Story = {
|
|
197
|
+
render: () => (
|
|
198
|
+
<EmptyState
|
|
199
|
+
mediaSlot={
|
|
200
|
+
<span style={{ color: "var(--color-gray-400, #9ca3af)", display: "flex" }}>
|
|
201
|
+
<IconCheckCircle />
|
|
202
|
+
</span>
|
|
203
|
+
}
|
|
204
|
+
headlineSlot={
|
|
205
|
+
<div style={{ fontSize: "2rem", fontWeight: 700, color: "var(--color-gray-700, #3d3f47)" }}>
|
|
206
|
+
All Done For Now!
|
|
207
|
+
</div>
|
|
208
|
+
}
|
|
209
|
+
titleSlot={
|
|
210
|
+
<div style={{ fontSize: "1.25rem", fontWeight: 600 }}>
|
|
211
|
+
You're all caught up.
|
|
212
|
+
</div>
|
|
213
|
+
}
|
|
214
|
+
textSlot={
|
|
215
|
+
<div style={{ color: "var(--color-gray-500, #71747d)", fontSize: "0.875rem" }}>
|
|
216
|
+
Great job on completing all your tasks! This might be a good time to
|
|
217
|
+
relax or consider planning your next set of goals. If you think of
|
|
218
|
+
something new, just hit the button below to add a new task.
|
|
219
|
+
</div>
|
|
220
|
+
}
|
|
221
|
+
/>
|
|
222
|
+
),
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// ─── Slot: Actions ────────────────────────────────────────────────────────────
|
|
226
|
+
// `actionsSlot` lets you compose multiple buttons in the actions area.
|
|
227
|
+
|
|
228
|
+
export const SlotActions: Story = {
|
|
229
|
+
render: () => (
|
|
230
|
+
<EmptyState
|
|
231
|
+
image="https://cdn.vuetifyjs.com/docs/images/components/v-empty-state/teamwork.png"
|
|
232
|
+
imageAlt="Teamwork illustration"
|
|
233
|
+
titleSlot={
|
|
234
|
+
<div style={{ fontSize: "1rem", fontWeight: 600, marginTop: "2rem" }}>
|
|
235
|
+
Manage your inventory transfers
|
|
236
|
+
</div>
|
|
237
|
+
}
|
|
238
|
+
textSlot={
|
|
239
|
+
<div style={{ fontSize: "0.875rem", color: "var(--color-gray-500, #71747d)" }}>
|
|
240
|
+
Track and receive your incoming inventory from suppliers
|
|
241
|
+
</div>
|
|
242
|
+
}
|
|
243
|
+
actionsSlot={
|
|
244
|
+
<>
|
|
245
|
+
<EmptyStateAction variant="outline">Learn more</EmptyStateAction>
|
|
246
|
+
<EmptyStateAction variant="primary">Add transfer</EmptyStateAction>
|
|
247
|
+
</>
|
|
248
|
+
}
|
|
249
|
+
/>
|
|
250
|
+
),
|
|
251
|
+
};
|