@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.
@@ -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
+ };