@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,218 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { FileUpload } from "../../components/ui/file-upload";
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "UI/FileUpload",
|
|
6
|
+
component: FileUpload,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: "padded",
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component: [
|
|
13
|
+
"A drag-and-drop file upload zone with file list, size validation, and error states.",
|
|
14
|
+
"",
|
|
15
|
+
"**Import:**",
|
|
16
|
+
"```tsx",
|
|
17
|
+
`import { FileUpload } from "@syscore/ui-library"`,
|
|
18
|
+
"```",
|
|
19
|
+
"",
|
|
20
|
+
"**Key props:**",
|
|
21
|
+
"",
|
|
22
|
+
"| Prop | Type | Default | Effect |",
|
|
23
|
+
"|------|------|---------|--------|",
|
|
24
|
+
"| `accept` | string | — | Allowed file types e.g. `\".pdf,.png\"` |",
|
|
25
|
+
"| `multiple` | boolean | `false` | Allow selecting multiple files |",
|
|
26
|
+
"| `maxSize` | number (bytes) | — | Reject files larger than this |",
|
|
27
|
+
"| `maxFiles` | number | — | Cap the number of accepted files |",
|
|
28
|
+
"| `variant` | `\"default\"` \\| `\"compact\"` | `\"default\"` | Full zone vs inline row |",
|
|
29
|
+
"| `disabled` | boolean | `false` | Disable all interaction |",
|
|
30
|
+
"| `onFilesChange` | `(files: File[]) => void` | — | Called whenever the file list changes |",
|
|
31
|
+
].join("\n"),
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
} satisfies Meta<typeof FileUpload>;
|
|
36
|
+
|
|
37
|
+
export default meta;
|
|
38
|
+
type Story = StoryObj<typeof meta>;
|
|
39
|
+
|
|
40
|
+
export const Default: Story = {
|
|
41
|
+
parameters: {
|
|
42
|
+
docs: {
|
|
43
|
+
source: {
|
|
44
|
+
code: `import { FileUpload } from "@syscore/ui-library"
|
|
45
|
+
|
|
46
|
+
<FileUpload
|
|
47
|
+
accept=".pdf,.png,.jpg"
|
|
48
|
+
maxSize={5 * 1024 * 1024}
|
|
49
|
+
onFilesChange={(files) => console.log(files)}
|
|
50
|
+
/>`,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
render: () => (
|
|
55
|
+
<div className="max-w-lg">
|
|
56
|
+
<FileUpload
|
|
57
|
+
accept=".pdf,.png,.jpg"
|
|
58
|
+
maxSize={5 * 1024 * 1024}
|
|
59
|
+
onFilesChange={(files) => console.log("Files:", files)}
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const Multiple: Story = {
|
|
66
|
+
parameters: {
|
|
67
|
+
docs: {
|
|
68
|
+
source: {
|
|
69
|
+
code: `import { FileUpload } from "@syscore/ui-library"
|
|
70
|
+
|
|
71
|
+
<FileUpload
|
|
72
|
+
multiple
|
|
73
|
+
maxFiles={5}
|
|
74
|
+
accept=".pdf,.docx,.xlsx"
|
|
75
|
+
maxSize={10 * 1024 * 1024}
|
|
76
|
+
onFilesChange={(files) => console.log(files)}
|
|
77
|
+
/>`,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
render: () => (
|
|
82
|
+
<div className="max-w-lg">
|
|
83
|
+
<FileUpload
|
|
84
|
+
multiple
|
|
85
|
+
maxFiles={5}
|
|
86
|
+
accept=".pdf,.docx,.xlsx"
|
|
87
|
+
maxSize={10 * 1024 * 1024}
|
|
88
|
+
onFilesChange={(files) => console.log("Files:", files)}
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const Compact: Story = {
|
|
95
|
+
parameters: {
|
|
96
|
+
docs: {
|
|
97
|
+
source: {
|
|
98
|
+
code: `import { FileUpload } from "@syscore/ui-library"
|
|
99
|
+
|
|
100
|
+
{/* Inline row layout — fits inside a form field row */}
|
|
101
|
+
<FileUpload
|
|
102
|
+
variant="compact"
|
|
103
|
+
accept=".pdf"
|
|
104
|
+
maxSize={2 * 1024 * 1024}
|
|
105
|
+
onFilesChange={(files) => console.log(files)}
|
|
106
|
+
/>`,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
render: () => (
|
|
111
|
+
<div className="max-w-lg">
|
|
112
|
+
<FileUpload
|
|
113
|
+
variant="compact"
|
|
114
|
+
accept=".pdf"
|
|
115
|
+
maxSize={2 * 1024 * 1024}
|
|
116
|
+
onFilesChange={(files) => console.log("Files:", files)}
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const ImagesOnly: Story = {
|
|
123
|
+
name: "Images only",
|
|
124
|
+
parameters: {
|
|
125
|
+
docs: {
|
|
126
|
+
source: {
|
|
127
|
+
code: `import { FileUpload } from "@syscore/ui-library"
|
|
128
|
+
|
|
129
|
+
<FileUpload
|
|
130
|
+
accept="image/*"
|
|
131
|
+
multiple
|
|
132
|
+
maxSize={3 * 1024 * 1024}
|
|
133
|
+
onFilesChange={(files) => console.log(files)}
|
|
134
|
+
/>`,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
render: () => (
|
|
139
|
+
<div className="max-w-lg">
|
|
140
|
+
<FileUpload
|
|
141
|
+
accept="image/*"
|
|
142
|
+
multiple
|
|
143
|
+
maxSize={3 * 1024 * 1024}
|
|
144
|
+
onFilesChange={(files) => console.log("Files:", files)}
|
|
145
|
+
/>
|
|
146
|
+
</div>
|
|
147
|
+
),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export const Disabled: Story = {
|
|
151
|
+
parameters: {
|
|
152
|
+
docs: {
|
|
153
|
+
source: {
|
|
154
|
+
code: `import { FileUpload } from "@syscore/ui-library"
|
|
155
|
+
|
|
156
|
+
<FileUpload disabled />`,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
render: () => (
|
|
161
|
+
<div className="max-w-lg">
|
|
162
|
+
<FileUpload disabled />
|
|
163
|
+
</div>
|
|
164
|
+
),
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const InForm: Story = {
|
|
168
|
+
name: "Inside a form",
|
|
169
|
+
parameters: {
|
|
170
|
+
docs: {
|
|
171
|
+
source: {
|
|
172
|
+
code: `import { FileUpload } from "@syscore/ui-library"
|
|
173
|
+
import { Stack, Text, Button } from "@syscore/ui-library"
|
|
174
|
+
|
|
175
|
+
<Stack gap={6}>
|
|
176
|
+
<Stack gap={2}>
|
|
177
|
+
<Text variant="overline-small">Project name</Text>
|
|
178
|
+
<Input placeholder="My WELL project" />
|
|
179
|
+
</Stack>
|
|
180
|
+
|
|
181
|
+
<Stack gap={2}>
|
|
182
|
+
<Text variant="overline-small">Supporting documents</Text>
|
|
183
|
+
<FileUpload
|
|
184
|
+
multiple
|
|
185
|
+
maxFiles={3}
|
|
186
|
+
accept=".pdf,.docx"
|
|
187
|
+
maxSize={10 * 1024 * 1024}
|
|
188
|
+
onFilesChange={(files) => setDocuments(files)}
|
|
189
|
+
/>
|
|
190
|
+
</Stack>
|
|
191
|
+
|
|
192
|
+
<Button variant="primary-dark" size="large">Submit</Button>
|
|
193
|
+
</Stack>`,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
render: () => (
|
|
198
|
+
<div className="max-w-md border border-gray-100 rounded-xl p-6">
|
|
199
|
+
<div className="flex flex-col gap-6">
|
|
200
|
+
<div className="flex flex-col gap-2">
|
|
201
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-gray-500">Project name</span>
|
|
202
|
+
<input className="border border-gray-200 rounded-md px-3 py-2 text-sm w-full" placeholder="My WELL project" />
|
|
203
|
+
</div>
|
|
204
|
+
<div className="flex flex-col gap-2">
|
|
205
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-gray-500">Supporting documents</span>
|
|
206
|
+
<FileUpload
|
|
207
|
+
multiple
|
|
208
|
+
maxFiles={3}
|
|
209
|
+
accept=".pdf,.docx"
|
|
210
|
+
maxSize={10 * 1024 * 1024}
|
|
211
|
+
onFilesChange={(files) => console.log("Files:", files)}
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
<button className="w-full bg-gray-900 text-white rounded-lg py-2.5 text-sm font-medium">Submit</button>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
),
|
|
218
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import {
|
|
3
|
+
Footer,
|
|
4
|
+
FooterRow,
|
|
5
|
+
FooterLinks,
|
|
6
|
+
FooterLink,
|
|
7
|
+
FooterIconButton,
|
|
8
|
+
FooterDivider,
|
|
9
|
+
FooterCopyright,
|
|
10
|
+
FooterBand,
|
|
11
|
+
} from "../../components/ui/footer";
|
|
12
|
+
|
|
13
|
+
// ─── Social icons (inline SVGs — no extra dep needed) ─────────────────────
|
|
14
|
+
|
|
15
|
+
function IconFacebook() {
|
|
16
|
+
return (
|
|
17
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
18
|
+
<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
function IconTwitter() {
|
|
23
|
+
return (
|
|
24
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
25
|
+
<path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z" />
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
function IconLinkedIn() {
|
|
30
|
+
return (
|
|
31
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
32
|
+
<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z" />
|
|
33
|
+
<rect x="2" y="9" width="4" height="12" />
|
|
34
|
+
<circle cx="4" cy="4" r="2" />
|
|
35
|
+
</svg>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
function IconInstagram() {
|
|
39
|
+
return (
|
|
40
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
41
|
+
<rect x="2" y="2" width="20" height="20" rx="5" ry="5" />
|
|
42
|
+
<path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z" />
|
|
43
|
+
<line x1="17.5" y1="6.5" x2="17.51" y2="6.5" />
|
|
44
|
+
</svg>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const SOCIAL_ICONS = [
|
|
49
|
+
{ icon: <IconFacebook />, label: "Facebook" },
|
|
50
|
+
{ icon: <IconTwitter />, label: "Twitter" },
|
|
51
|
+
{ icon: <IconLinkedIn />, label: "LinkedIn" },
|
|
52
|
+
{ icon: <IconInstagram />, label: "Instagram" },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const NAV_LINKS = ["Home", "About Us", "Team", "Services", "Blog", "Contact Us"];
|
|
56
|
+
|
|
57
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
const meta = {
|
|
60
|
+
title: "UI/Footer",
|
|
61
|
+
component: Footer,
|
|
62
|
+
tags: ["autodocs"],
|
|
63
|
+
parameters: {
|
|
64
|
+
layout: "fullscreen",
|
|
65
|
+
},
|
|
66
|
+
argTypes: {
|
|
67
|
+
bordered: { control: "boolean" },
|
|
68
|
+
rounded: { control: "boolean" },
|
|
69
|
+
elevated: { control: "boolean" },
|
|
70
|
+
color: { control: "color" },
|
|
71
|
+
},
|
|
72
|
+
} satisfies Meta<typeof Footer>;
|
|
73
|
+
|
|
74
|
+
export default meta;
|
|
75
|
+
|
|
76
|
+
type Story = StoryObj<typeof meta>;
|
|
77
|
+
|
|
78
|
+
// ─── Default ─────────────────────────────────────────────────────────────────
|
|
79
|
+
// Simplest form — just a container with a copyright line.
|
|
80
|
+
|
|
81
|
+
export const Default: Story = {
|
|
82
|
+
render: () => (
|
|
83
|
+
<Footer>
|
|
84
|
+
<FooterCopyright />
|
|
85
|
+
</Footer>
|
|
86
|
+
),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// ─── Bordered ────────────────────────────────────────────────────────────────
|
|
90
|
+
// Adds a visible top border.
|
|
91
|
+
|
|
92
|
+
export const Bordered: Story = {
|
|
93
|
+
render: () => (
|
|
94
|
+
<Footer bordered>
|
|
95
|
+
<FooterCopyright />
|
|
96
|
+
</Footer>
|
|
97
|
+
),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// ─── Company Footer ───────────────────────────────────────────────────────────
|
|
101
|
+
// Nav links + copyright. Matches the Vuetify "company footer" example.
|
|
102
|
+
|
|
103
|
+
export const CompanyFooter: Story = {
|
|
104
|
+
render: () => (
|
|
105
|
+
<Footer>
|
|
106
|
+
<FooterLinks>
|
|
107
|
+
{NAV_LINKS.map((link) => (
|
|
108
|
+
<FooterLink key={link} href="#">
|
|
109
|
+
{link}
|
|
110
|
+
</FooterLink>
|
|
111
|
+
))}
|
|
112
|
+
</FooterLinks>
|
|
113
|
+
<FooterCopyright />
|
|
114
|
+
</Footer>
|
|
115
|
+
),
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// ─── Indigo Footer ────────────────────────────────────────────────────────────
|
|
119
|
+
// Social icons + paragraph + copyright on an indigo background.
|
|
120
|
+
// Uses --color-indigo-300 from the global token system.
|
|
121
|
+
|
|
122
|
+
export const IndigoFooter: Story = {
|
|
123
|
+
render: () => (
|
|
124
|
+
<Footer
|
|
125
|
+
color="var(--color-indigo-300, #a5b4fc)"
|
|
126
|
+
className="footer--indigo"
|
|
127
|
+
>
|
|
128
|
+
{/* Social icon row */}
|
|
129
|
+
<FooterRow className="footer-row--center">
|
|
130
|
+
{SOCIAL_ICONS.map(({ icon, label }) => (
|
|
131
|
+
<FooterIconButton key={label} aria-label={label}>
|
|
132
|
+
{icon}
|
|
133
|
+
</FooterIconButton>
|
|
134
|
+
))}
|
|
135
|
+
</FooterRow>
|
|
136
|
+
|
|
137
|
+
{/* Divider */}
|
|
138
|
+
<FooterDivider width="50px" className="footer-divider--thick" />
|
|
139
|
+
|
|
140
|
+
{/* Body copy */}
|
|
141
|
+
<p className="footer-body-text">
|
|
142
|
+
Phasellus feugiat arcu sapien, et iaculis ipsum elementum sit amet.
|
|
143
|
+
Mauris cursus commodo interdum. Praesent ut risus eget metus luctus
|
|
144
|
+
accumsan id ultrices nunc. Sed at orci sed massa consectetur dignissim a
|
|
145
|
+
sit amet dui. Duis commodo vitae velit et faucibus. Morbi vehicula
|
|
146
|
+
lacinia malesuada. Nulla placerat augue vel ipsum ultrices, cursus
|
|
147
|
+
iaculis dui sollicitudin.
|
|
148
|
+
</p>
|
|
149
|
+
|
|
150
|
+
{/* Full-width divider */}
|
|
151
|
+
<FooterDivider />
|
|
152
|
+
|
|
153
|
+
{/* Copyright */}
|
|
154
|
+
<FooterCopyright />
|
|
155
|
+
</Footer>
|
|
156
|
+
),
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// ─── Teal Footer ─────────────────────────────────────────────────────────────
|
|
160
|
+
// Two-section footer: top row with tagline + social icons,
|
|
161
|
+
// bottom band with copyright. Rounded corners.
|
|
162
|
+
|
|
163
|
+
export const TealFooter: Story = {
|
|
164
|
+
render: () => (
|
|
165
|
+
<Footer
|
|
166
|
+
color="var(--color-cyan-800, #0f748a)"
|
|
167
|
+
rounded
|
|
168
|
+
className="footer--teal"
|
|
169
|
+
>
|
|
170
|
+
{/* Top row */}
|
|
171
|
+
<FooterRow className="footer-row--space-between footer-row--padded">
|
|
172
|
+
<strong className="footer-tagline">
|
|
173
|
+
Get connected with us on social networks!
|
|
174
|
+
</strong>
|
|
175
|
+
<div className="footer-row footer-row--gap">
|
|
176
|
+
{SOCIAL_ICONS.map(({ icon, label }) => (
|
|
177
|
+
<FooterIconButton
|
|
178
|
+
key={label}
|
|
179
|
+
aria-label={label}
|
|
180
|
+
className="footer-icon-btn--small footer-icon-btn--plain"
|
|
181
|
+
>
|
|
182
|
+
{icon}
|
|
183
|
+
</FooterIconButton>
|
|
184
|
+
))}
|
|
185
|
+
</div>
|
|
186
|
+
</FooterRow>
|
|
187
|
+
|
|
188
|
+
{/* Bottom band */}
|
|
189
|
+
<FooterBand>
|
|
190
|
+
<FooterCopyright />
|
|
191
|
+
</FooterBand>
|
|
192
|
+
</Footer>
|
|
193
|
+
),
|
|
194
|
+
};
|