@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,280 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import {
3
+ AppBar,
4
+ AppBarNavIcon,
5
+ AppBarTitle,
6
+ AppBarPrepend,
7
+ AppBarAppend,
8
+ AppBarContent,
9
+ } from "../../components/ui/app-bar";
10
+ import {
11
+ Search,
12
+ Bell,
13
+ ArrowLeft,
14
+ MoreVertical,
15
+ Heart,
16
+ } from "lucide-react";
17
+
18
+ const meta = {
19
+ title: "UI/AppBar",
20
+ component: AppBar,
21
+ tags: ["autodocs"],
22
+ parameters: {
23
+ layout: "fullscreen",
24
+ },
25
+ argTypes: {
26
+ collapse: { control: "boolean" },
27
+ rounded: { control: "boolean" },
28
+ elevated: { control: "boolean" },
29
+ flat: { control: "boolean" },
30
+ color: {
31
+ control: "select",
32
+ options: ["default", "primary", "transparent"],
33
+ },
34
+ position: {
35
+ control: "select",
36
+ options: ["fixed", "sticky", "relative", "static"],
37
+ },
38
+ density: {
39
+ control: "select",
40
+ options: ["default", "comfortable", "compact", "prominent"],
41
+ },
42
+ image: { control: "text" },
43
+ imageOverlay: { control: "text" },
44
+ },
45
+ } satisfies Meta<typeof AppBar>;
46
+
47
+ export default meta;
48
+
49
+ type Story = StoryObj<typeof meta>;
50
+
51
+ // ─── Default ─────────────────────────────────────────────────────────────────
52
+
53
+ export const Default: Story = {
54
+ render: () => (
55
+ <AppBar position="relative">
56
+ <AppBarPrepend>
57
+ <AppBarNavIcon />
58
+ </AppBarPrepend>
59
+ <AppBarTitle>Application Bar</AppBarTitle>
60
+ </AppBar>
61
+ ),
62
+ };
63
+
64
+ // ─── Collapse ────────────────────────────────────────────────────────────────
65
+
66
+ export const Collapse: Story = {
67
+ render: () => (
68
+ <AppBar position="relative" collapse>
69
+ <AppBarPrepend>
70
+ <AppBarNavIcon />
71
+ </AppBarPrepend>
72
+ <AppBarTitle>Application Bar</AppBarTitle>
73
+ </AppBar>
74
+ ),
75
+ };
76
+
77
+ // ─── Rounded ─────────────────────────────────────────────────────────────────
78
+
79
+ export const Rounded: Story = {
80
+ render: () => (
81
+ <AppBar position="relative" rounded>
82
+ <AppBarPrepend>
83
+ <AppBarNavIcon />
84
+ </AppBarPrepend>
85
+ <AppBarTitle>Application Bar</AppBarTitle>
86
+ </AppBar>
87
+ ),
88
+ };
89
+
90
+ // ─── With Actions ────────────────────────────────────────────────────────────
91
+
92
+ export const WithActions: Story = {
93
+ render: () => (
94
+ <AppBar position="relative">
95
+ <AppBarPrepend>
96
+ <AppBarNavIcon />
97
+ </AppBarPrepend>
98
+ <AppBarTitle>Application Bar</AppBarTitle>
99
+ <AppBarAppend>
100
+ <button
101
+ type="button"
102
+ className="app-bar-action-btn"
103
+ aria-label="Search"
104
+ >
105
+ <Search size={20} />
106
+ </button>
107
+ <button
108
+ type="button"
109
+ className="app-bar-action-btn"
110
+ aria-label="Notifications"
111
+ >
112
+ <Bell size={20} />
113
+ </button>
114
+ </AppBarAppend>
115
+ </AppBar>
116
+ ),
117
+ };
118
+
119
+ // ─── Back Navigation ─────────────────────────────────────────────────────────
120
+
121
+ export const BackNavigation: Story = {
122
+ render: () => (
123
+ <AppBar position="relative">
124
+ <AppBarPrepend>
125
+ <AppBarNavIcon icon={<ArrowLeft size={20} />} aria-label="Go back" />
126
+ </AppBarPrepend>
127
+ <AppBarTitle>Page Title</AppBarTitle>
128
+ <AppBarAppend>
129
+ <button
130
+ type="button"
131
+ className="app-bar-action-btn"
132
+ aria-label="More options"
133
+ >
134
+ <MoreVertical size={20} />
135
+ </button>
136
+ </AppBarAppend>
137
+ </AppBar>
138
+ ),
139
+ };
140
+
141
+ // ─── With Search Content ──────────────────────────────────────────────────────
142
+
143
+ export const WithSearchContent: Story = {
144
+ render: () => (
145
+ <AppBar position="relative">
146
+ <AppBarPrepend>
147
+ <AppBarNavIcon />
148
+ </AppBarPrepend>
149
+ <AppBarContent>
150
+ <div className="app-bar-search">
151
+ <Search size={16} className="app-bar-search-icon" />
152
+ <input
153
+ type="text"
154
+ placeholder="Search…"
155
+ className="app-bar-search-input"
156
+ />
157
+ </div>
158
+ </AppBarContent>
159
+ <AppBarAppend>
160
+ <button
161
+ type="button"
162
+ className="app-bar-action-btn"
163
+ aria-label="Notifications"
164
+ >
165
+ <Bell size={20} />
166
+ </button>
167
+ </AppBarAppend>
168
+ </AppBar>
169
+ ),
170
+ };
171
+
172
+ // ─── Density ─────────────────────────────────────────────────────────────────
173
+
174
+ export const Density: Story = {
175
+ render: () => (
176
+ <div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
177
+ {(
178
+ [
179
+ { density: "default", label: "default (64px)" },
180
+ { density: "comfortable", label: "comfortable (56px)" },
181
+ { density: "compact", label: "compact (48px)" },
182
+ ] as const
183
+ ).map(({ density, label }) => (
184
+ <div key={density}>
185
+ <p
186
+ style={{
187
+ fontSize: "0.75rem",
188
+ color: "#71747d",
189
+ marginBottom: "0.25rem",
190
+ paddingLeft: "0.5rem",
191
+ }}
192
+ >
193
+ density="{label}"
194
+ </p>
195
+ <AppBar position="relative" color="primary" density={density}>
196
+ <AppBarPrepend>
197
+ <AppBarNavIcon />
198
+ </AppBarPrepend>
199
+ <AppBarTitle>Photos</AppBarTitle>
200
+ <AppBarAppend>
201
+ <button
202
+ type="button"
203
+ className="app-bar-action-btn"
204
+ aria-label="More options"
205
+ >
206
+ <MoreVertical size={20} />
207
+ </button>
208
+ </AppBarAppend>
209
+ </AppBar>
210
+ </div>
211
+ ))}
212
+ </div>
213
+ ),
214
+ };
215
+
216
+ // ─── Prominent ───────────────────────────────────────────────────────────────
217
+ // density="prominent" gives a taller bar (128px) where the title sits at
218
+ // the bottom — ideal for longer titles or stronger visual presence.
219
+
220
+ export const Prominent: Story = {
221
+ render: () => (
222
+ <AppBar position="relative" color="primary" density="prominent">
223
+ <AppBarPrepend>
224
+ <AppBarNavIcon />
225
+ </AppBarPrepend>
226
+ <AppBarTitle>My Recent Trips</AppBarTitle>
227
+ <AppBarAppend>
228
+ <button
229
+ type="button"
230
+ className="app-bar-action-btn"
231
+ aria-label="More options"
232
+ >
233
+ <MoreVertical size={20} />
234
+ </button>
235
+ </AppBarAppend>
236
+ </AppBar>
237
+ ),
238
+ };
239
+
240
+ // ─── Image ───────────────────────────────────────────────────────────────────
241
+ // The `image` prop sets a background image on the bar.
242
+ // `imageOverlay` adds a gradient on top for legibility.
243
+
244
+ export const Image: Story = {
245
+ render: () => (
246
+ <AppBar
247
+ position="relative"
248
+ image="https://picsum.photos/1920/1080?random"
249
+ imageOverlay="to top right, rgba(19,84,122,.8), rgba(128,208,199,.8)"
250
+ >
251
+ <AppBarPrepend>
252
+ <AppBarNavIcon />
253
+ </AppBarPrepend>
254
+ <AppBarTitle>Title</AppBarTitle>
255
+ <AppBarAppend>
256
+ <button
257
+ type="button"
258
+ className="app-bar-action-btn"
259
+ aria-label="Search"
260
+ >
261
+ <Search size={20} />
262
+ </button>
263
+ <button
264
+ type="button"
265
+ className="app-bar-action-btn"
266
+ aria-label="Favourite"
267
+ >
268
+ <Heart size={20} />
269
+ </button>
270
+ <button
271
+ type="button"
272
+ className="app-bar-action-btn"
273
+ aria-label="More options"
274
+ >
275
+ <MoreVertical size={20} />
276
+ </button>
277
+ </AppBarAppend>
278
+ </AppBar>
279
+ ),
280
+ };
@@ -0,0 +1,238 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import {
4
+ Banner,
5
+ BannerIcon,
6
+ BannerText,
7
+ BannerTitle,
8
+ BannerDescription,
9
+ BannerAction,
10
+ } from "../../components/ui/banner";
11
+
12
+ // ─── Inline icons ─────────────────────────────────────────────────────────────
13
+
14
+ function IconInfo() {
15
+ return (
16
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
17
+ <circle cx="12" cy="12" r="10" />
18
+ <line x1="12" y1="8" x2="12" y2="12" />
19
+ <line x1="12" y1="16" x2="12.01" y2="16" />
20
+ </svg>
21
+ );
22
+ }
23
+ function IconCheckCircle() {
24
+ return (
25
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
26
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
27
+ <polyline points="22 4 12 14.01 9 11.01" />
28
+ </svg>
29
+ );
30
+ }
31
+ function IconAlertTriangle() {
32
+ return (
33
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
34
+ <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
35
+ <line x1="12" y1="9" x2="12" y2="13" />
36
+ <line x1="12" y1="17" x2="12.01" y2="17" />
37
+ </svg>
38
+ );
39
+ }
40
+ function IconAlertCircle() {
41
+ return (
42
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
43
+ <circle cx="12" cy="12" r="10" />
44
+ <line x1="12" y1="8" x2="12" y2="12" />
45
+ <line x1="12" y1="16" x2="12.01" y2="16" />
46
+ </svg>
47
+ );
48
+ }
49
+ function IconMegaphone() {
50
+ return (
51
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
52
+ <path d="M3 11l19-9-9 19-2-8-8-2z" />
53
+ </svg>
54
+ );
55
+ }
56
+
57
+ // ─────────────────────────────────────────────────────────────────────────────
58
+
59
+ const meta = {
60
+ title: "UI/Banner",
61
+ component: Banner,
62
+ tags: ["autodocs"],
63
+ parameters: { layout: "fullscreen" },
64
+ argTypes: {
65
+ variant: {
66
+ control: "select",
67
+ options: ["default", "info", "success", "warning", "destructive"],
68
+ },
69
+ sticky: { control: "boolean" },
70
+ },
71
+ } satisfies Meta<typeof Banner>;
72
+
73
+ export default meta;
74
+ type Story = StoryObj<typeof meta>;
75
+
76
+ // ─── Title Only ───────────────────────────────────────────────────────────────
77
+ // Minimal: just a title, no icon or description.
78
+
79
+ export const TitleOnly: Story = {
80
+ render: () => (
81
+ <Banner variant="info">
82
+ <BannerText>
83
+ <BannerTitle>Scheduled maintenance on Sunday at 2 AM UTC.</BannerTitle>
84
+ </BannerText>
85
+ </Banner>
86
+ ),
87
+ };
88
+
89
+ // ─── With Icon ────────────────────────────────────────────────────────────────
90
+ // Icon + title — the most common single-line pattern.
91
+
92
+ export const WithIcon: Story = {
93
+ render: () => (
94
+ <Banner variant="info">
95
+ <BannerIcon><IconInfo /></BannerIcon>
96
+ <BannerText>
97
+ <BannerTitle>Version 2.4.0 is ready to install.</BannerTitle>
98
+ </BannerText>
99
+ </Banner>
100
+ ),
101
+ };
102
+
103
+ // ─── With Description ─────────────────────────────────────────────────────────
104
+ // Icon + title + supporting description.
105
+
106
+ export const WithDescription: Story = {
107
+ render: () => (
108
+ <Banner variant="warning">
109
+ <BannerIcon><IconAlertTriangle /></BannerIcon>
110
+ <BannerText>
111
+ <BannerTitle>Subscription expiring</BannerTitle>
112
+ <BannerDescription>Your plan expires in 3 days. Renew to avoid interruption.</BannerDescription>
113
+ </BannerText>
114
+ </Banner>
115
+ ),
116
+ };
117
+
118
+ // ─── With Action ──────────────────────────────────────────────────────────────
119
+ // Icon + title + description + call-to-action button.
120
+
121
+ export const WithAction: Story = {
122
+ render: () => (
123
+ <Banner variant="info">
124
+ <BannerIcon><IconInfo /></BannerIcon>
125
+ <BannerText>
126
+ <BannerTitle>New terms of service</BannerTitle>
127
+ <BannerDescription>Please review and accept our updated terms before June 30.</BannerDescription>
128
+ </BannerText>
129
+ <BannerAction>Review now</BannerAction>
130
+ </Banner>
131
+ ),
132
+ };
133
+
134
+ // ─── With Close ───────────────────────────────────────────────────────────────
135
+ // Icon + title + dismiss button (no action).
136
+
137
+ function WithCloseBanner() {
138
+ const [visible, setVisible] = useState(true);
139
+ if (!visible) {
140
+ return (
141
+ <div style={{ padding: "1rem", fontSize: "0.875rem" }}>
142
+ Banner dismissed.{" "}
143
+ <button type="button" onClick={() => setVisible(true)} style={{ textDecoration: "underline", background: "none", border: "none", cursor: "pointer" }}>
144
+ Show again
145
+ </button>
146
+ </div>
147
+ );
148
+ }
149
+ return (
150
+ <Banner variant="info" onClose={() => setVisible(false)}>
151
+ <BannerIcon><IconInfo /></BannerIcon>
152
+ <BannerText>
153
+ <BannerTitle>Cookie notice</BannerTitle>
154
+ <BannerDescription>We use cookies to improve your experience.</BannerDescription>
155
+ </BannerText>
156
+ </Banner>
157
+ );
158
+ }
159
+
160
+ export const WithClose: Story = {
161
+ render: () => <WithCloseBanner />,
162
+ };
163
+
164
+ // ─── Full Kit ─────────────────────────────────────────────────────────────────
165
+ // All slots: icon + title + description + action + close button.
166
+
167
+ function FullKitBanner() {
168
+ const [visible, setVisible] = useState(true);
169
+ if (!visible) {
170
+ return (
171
+ <div style={{ padding: "1rem", fontSize: "0.875rem" }}>
172
+ Banner dismissed.{" "}
173
+ <button type="button" onClick={() => setVisible(true)} style={{ textDecoration: "underline", background: "none", border: "none", cursor: "pointer" }}>
174
+ Show again
175
+ </button>
176
+ </div>
177
+ );
178
+ }
179
+ return (
180
+ <Banner variant="warning" onClose={() => setVisible(false)}>
181
+ <BannerIcon><IconAlertTriangle /></BannerIcon>
182
+ <BannerText>
183
+ <BannerTitle>Subscription expiring soon</BannerTitle>
184
+ <BannerDescription>Your plan expires in 3 days. Renew now to avoid losing access.</BannerDescription>
185
+ </BannerText>
186
+ <BannerAction>Renew plan</BannerAction>
187
+ </Banner>
188
+ );
189
+ }
190
+
191
+ export const FullKit: Story = {
192
+ render: () => <FullKitBanner />,
193
+ };
194
+
195
+ // ─── All Variants ─────────────────────────────────────────────────────────────
196
+ // All 5 variants at a glance using the WithDescription slot combination.
197
+
198
+ export const AllVariants: Story = {
199
+ render: () => (
200
+ <div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
201
+ <Banner>
202
+ <BannerIcon><IconMegaphone /></BannerIcon>
203
+ <BannerText>
204
+ <BannerTitle>Default</BannerTitle>
205
+ <BannerDescription>General announcement banner.</BannerDescription>
206
+ </BannerText>
207
+ </Banner>
208
+ <Banner variant="info">
209
+ <BannerIcon><IconInfo /></BannerIcon>
210
+ <BannerText>
211
+ <BannerTitle>Info</BannerTitle>
212
+ <BannerDescription>Informational message for the user.</BannerDescription>
213
+ </BannerText>
214
+ </Banner>
215
+ <Banner variant="success">
216
+ <BannerIcon><IconCheckCircle /></BannerIcon>
217
+ <BannerText>
218
+ <BannerTitle>Success</BannerTitle>
219
+ <BannerDescription>Action completed successfully.</BannerDescription>
220
+ </BannerText>
221
+ </Banner>
222
+ <Banner variant="warning">
223
+ <BannerIcon><IconAlertTriangle /></BannerIcon>
224
+ <BannerText>
225
+ <BannerTitle>Warning</BannerTitle>
226
+ <BannerDescription>Something needs your attention.</BannerDescription>
227
+ </BannerText>
228
+ </Banner>
229
+ <Banner variant="destructive">
230
+ <BannerIcon><IconAlertCircle /></BannerIcon>
231
+ <BannerText>
232
+ <BannerTitle>Destructive</BannerTitle>
233
+ <BannerDescription>A critical error has occurred.</BannerDescription>
234
+ </BannerText>
235
+ </Banner>
236
+ </div>
237
+ ),
238
+ };