tharaday 0.7.1 → 0.7.3
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/.storybook/main.ts +1 -1
- package/.storybook/preview.ts +0 -2
- package/.storybook/vitest.setup.ts +2 -0
- package/dist/ds.css +1 -1
- package/dist/ds.js +889 -814
- package/dist/ds.umd.cjs +1 -1
- package/dist/src/components/Tree/Tree.d.ts +1 -1
- package/dist/src/components/Tree/Tree.stories.d.ts +1 -1
- package/dist/src/components/Tree/TreeItem.d.ts +1 -1
- package/dist/src/components/Tree/TreeItem.types.d.ts +6 -0
- package/dist/src/layouts/AppLayout/AppLayout.d.ts +1 -7
- package/dist/src/layouts/AppLayout/AppLayout.stories.d.ts +1 -9
- package/dist/src/layouts/AppLayout/AppLayout.types.d.ts +7 -52
- package/dist/src/layouts/DashboardLayout/DashboardLayout.d.ts +1 -1
- package/dist/src/layouts/DashboardLayout/DashboardLayout.stories.d.ts +1 -7
- package/dist/src/layouts/DashboardLayout/DashboardLayout.types.d.ts +2 -9
- package/dist/src/layouts/SettingsLayout/SettingsLayout.d.ts +1 -1
- package/dist/src/layouts/SettingsLayout/SettingsLayout.stories.d.ts +1 -7
- package/dist/src/layouts/SettingsLayout/SettingsLayout.types.d.ts +2 -9
- package/package.json +8 -1
- package/src/components/Accordion/Accordion.module.css +1 -1
- package/src/components/Accordion/Accordion.test.tsx +82 -0
- package/src/components/Accordion/Accordion.tsx +14 -1
- package/src/components/Avatar/Avatar.test.tsx +36 -0
- package/src/components/Badge/Badge.test.tsx +15 -0
- package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +96 -0
- package/src/components/Checkbox/Checkbox.module.css +7 -7
- package/src/components/Checkbox/Checkbox.test.tsx +68 -0
- package/src/components/Dropdown/Dropdown.test.tsx +104 -0
- package/src/components/Input/Input.test.tsx +61 -0
- package/src/components/List/List.module.css +12 -12
- package/src/components/List/List.test.tsx +46 -0
- package/src/components/Modal/Modal.module.css +12 -13
- package/src/components/Modal/Modal.test.tsx +86 -0
- package/src/components/Modal/Modal.tsx +10 -4
- package/src/components/NavBar/NavBar.module.css +3 -3
- package/src/components/Notification/Notification.module.css +6 -6
- package/src/components/Notification/Notification.test.tsx +38 -0
- package/src/components/Notification/Notification.tsx +8 -1
- package/src/components/Pagination/Pagination.test.tsx +70 -0
- package/src/components/ProgressBar/ProgressBar.test.tsx +58 -0
- package/src/components/RadioButton/RadioButton.test.tsx +51 -0
- package/src/components/Select/Select.test.tsx +64 -0
- package/src/components/Slider/Slider.test.tsx +49 -0
- package/src/components/Stepper/Step.module.css +2 -2
- package/src/components/Stepper/Stepper.test.tsx +51 -0
- package/src/components/Switch/Switch.test.tsx +53 -0
- package/src/components/Table/Table.test.tsx +78 -0
- package/src/components/Tabs/Tabs.test.tsx +83 -0
- package/src/components/Textarea/Textarea.test.tsx +56 -0
- package/src/components/Tree/Tree.test.tsx +116 -0
- package/src/components/Tree/Tree.tsx +65 -1
- package/src/components/Tree/TreeItem.module.css +20 -26
- package/src/components/Tree/TreeItem.tsx +144 -79
- package/src/components/Tree/TreeItem.types.ts +6 -0
- package/src/layouts/AppLayout/AppLayout.stories.tsx +48 -36
- package/src/layouts/AppLayout/AppLayout.tsx +4 -34
- package/src/layouts/AppLayout/AppLayout.types.ts +7 -51
- package/src/layouts/DashboardLayout/DashboardLayout.stories.tsx +4 -8
- package/src/layouts/DashboardLayout/DashboardLayout.tsx +2 -17
- package/src/layouts/DashboardLayout/DashboardLayout.types.tsx +2 -7
- package/src/layouts/SettingsLayout/SettingsLayout.stories.tsx +16 -7
- package/src/layouts/SettingsLayout/SettingsLayout.tsx +2 -17
- package/src/layouts/SettingsLayout/SettingsLayout.types.tsx +2 -7
- package/src/styles/semantic.css +3 -0
- package/src/styles/tokens.css +15 -0
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
|
-
import { useState } from 'react';
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import type { FocusEvent, KeyboardEvent, MouseEvent } from 'react';
|
|
3
4
|
import type { TreeItemProps } from './TreeItem.types';
|
|
4
5
|
import styles from './TreeItem.module.css';
|
|
5
6
|
|
|
7
|
+
function focusTreeItem(item: HTMLElement) {
|
|
8
|
+
const tree = item.closest('[role="tree"]');
|
|
9
|
+
if (!tree) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
tree.querySelectorAll<HTMLElement>('[role="treeitem"]').forEach((el) => {
|
|
13
|
+
el.tabIndex = -1;
|
|
14
|
+
});
|
|
15
|
+
item.tabIndex = 0;
|
|
16
|
+
item.focus();
|
|
17
|
+
}
|
|
18
|
+
|
|
6
19
|
export const TreeItem = ({
|
|
7
20
|
data,
|
|
8
21
|
label,
|
|
@@ -10,14 +23,91 @@ export const TreeItem = ({
|
|
|
10
23
|
expandIcon,
|
|
11
24
|
collapseIcon,
|
|
12
25
|
isRoot,
|
|
26
|
+
level,
|
|
27
|
+
setSize,
|
|
28
|
+
posInSet,
|
|
13
29
|
}: TreeItemProps) => {
|
|
14
30
|
const [isExpanded, setIsExpanded] = useState(isRoot ? true : (defaultExpanded ?? false));
|
|
31
|
+
const itemRef = useRef<HTMLDivElement>(null);
|
|
15
32
|
|
|
16
33
|
const isObject = data !== null && typeof data === 'object';
|
|
17
34
|
const hasChildren =
|
|
18
|
-
isObject && (Array.isArray(data) ? data.length > 0 : Object.keys(data).length > 0);
|
|
35
|
+
isObject && (Array.isArray(data) ? data.length > 0 : Object.keys(data as object).length > 0);
|
|
36
|
+
|
|
37
|
+
const childLevel = isRoot ? level : level + 1;
|
|
38
|
+
|
|
39
|
+
const getChildren = (): { key: string; value: unknown }[] => {
|
|
40
|
+
if (!isObject) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(data)) {
|
|
44
|
+
return (data as unknown[]).map((v, i) => ({ key: String(i), value: v }));
|
|
45
|
+
}
|
|
46
|
+
return Object.entries(data as Record<string, unknown>).map(([k, v]) => ({ key: k, value: v }));
|
|
47
|
+
};
|
|
19
48
|
|
|
20
|
-
const
|
|
49
|
+
const children = getChildren();
|
|
50
|
+
|
|
51
|
+
const handleClick = (e: MouseEvent) => {
|
|
52
|
+
e.stopPropagation();
|
|
53
|
+
if (hasChildren) {
|
|
54
|
+
setIsExpanded((prev) => !prev);
|
|
55
|
+
}
|
|
56
|
+
if (itemRef.current) {
|
|
57
|
+
focusTreeItem(itemRef.current);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const handleFocus = (e: FocusEvent) => {
|
|
62
|
+
e.stopPropagation();
|
|
63
|
+
if (!itemRef.current) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const tree = itemRef.current.closest('[role="tree"]');
|
|
67
|
+
if (!tree) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
tree.querySelectorAll<HTMLElement>('[role="treeitem"]').forEach((el) => {
|
|
71
|
+
el.tabIndex = -1;
|
|
72
|
+
});
|
|
73
|
+
itemRef.current.tabIndex = 0;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
|
|
77
|
+
switch (e.key) {
|
|
78
|
+
case 'Enter':
|
|
79
|
+
case ' ':
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
if (hasChildren) {
|
|
82
|
+
setIsExpanded((prev) => !prev);
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
case 'ArrowRight':
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
e.stopPropagation();
|
|
88
|
+
if (hasChildren && !isExpanded) {
|
|
89
|
+
setIsExpanded(true);
|
|
90
|
+
} else if (hasChildren && isExpanded) {
|
|
91
|
+
const firstChild = itemRef.current?.querySelector<HTMLElement>('[role="treeitem"]');
|
|
92
|
+
if (firstChild) {
|
|
93
|
+
focusTreeItem(firstChild);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
case 'ArrowLeft':
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
e.stopPropagation();
|
|
100
|
+
if (hasChildren && isExpanded) {
|
|
101
|
+
setIsExpanded(false);
|
|
102
|
+
} else {
|
|
103
|
+
const parent = itemRef.current?.parentElement?.closest<HTMLElement>('[role="treeitem"]');
|
|
104
|
+
if (parent) {
|
|
105
|
+
focusTreeItem(parent);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
21
111
|
|
|
22
112
|
const defaultExpandIcon = (
|
|
23
113
|
<svg
|
|
@@ -49,98 +139,73 @@ export const TreeItem = ({
|
|
|
49
139
|
</svg>
|
|
50
140
|
);
|
|
51
141
|
|
|
52
|
-
const
|
|
142
|
+
const renderLeafContent = () => {
|
|
53
143
|
if (data === undefined) {
|
|
54
144
|
return <span className={styles.empty}>undefined</span>;
|
|
55
145
|
}
|
|
56
|
-
|
|
57
146
|
if (data === null) {
|
|
58
147
|
return <span className={styles.empty}>null</span>;
|
|
59
148
|
}
|
|
60
|
-
|
|
61
149
|
if (typeof data !== 'object') {
|
|
62
150
|
return <span className={styles.value}>{String(data)}</span>;
|
|
63
151
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (data.length === 0) {
|
|
67
|
-
return <span className={styles.empty}>[]</span>;
|
|
68
|
-
}
|
|
69
|
-
return (
|
|
70
|
-
<ul
|
|
71
|
-
className={clsx(
|
|
72
|
-
styles.list,
|
|
73
|
-
styles.collapsibleContent,
|
|
74
|
-
isExpanded && styles.expanded,
|
|
75
|
-
isRoot && styles.rootList
|
|
76
|
-
)}
|
|
77
|
-
>
|
|
78
|
-
{data.map((item, index) => (
|
|
79
|
-
<li key={index} className={styles.item}>
|
|
80
|
-
<TreeItem
|
|
81
|
-
data={item}
|
|
82
|
-
defaultExpanded={defaultExpanded}
|
|
83
|
-
expandIcon={expandIcon}
|
|
84
|
-
collapseIcon={collapseIcon}
|
|
85
|
-
/>
|
|
86
|
-
</li>
|
|
87
|
-
))}
|
|
88
|
-
</ul>
|
|
89
|
-
);
|
|
152
|
+
if (Array.isArray(data) && (data as unknown[]).length === 0) {
|
|
153
|
+
return <span className={styles.empty}>[]</span>;
|
|
90
154
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return <span className={styles.empty}>{}</span>;
|
|
155
|
+
if (!Array.isArray(data) && Object.keys(data as object).length === 0) {
|
|
156
|
+
return <span className={styles.empty}>{'{}'}</span>;
|
|
94
157
|
}
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<ul
|
|
98
|
-
className={clsx(
|
|
99
|
-
styles.list,
|
|
100
|
-
styles.collapsibleContent,
|
|
101
|
-
isExpanded && styles.expanded,
|
|
102
|
-
isRoot && styles.rootList
|
|
103
|
-
)}
|
|
104
|
-
>
|
|
105
|
-
{Object.entries(data).map(([key, value]) => (
|
|
106
|
-
<li key={key} className={styles.item}>
|
|
107
|
-
<TreeItem
|
|
108
|
-
label={key}
|
|
109
|
-
data={value}
|
|
110
|
-
defaultExpanded={defaultExpanded}
|
|
111
|
-
expandIcon={expandIcon}
|
|
112
|
-
collapseIcon={collapseIcon}
|
|
113
|
-
/>
|
|
114
|
-
</li>
|
|
115
|
-
))}
|
|
116
|
-
</ul>
|
|
117
|
-
);
|
|
158
|
+
return null;
|
|
118
159
|
};
|
|
119
160
|
|
|
161
|
+
const childrenNodes = children.map((child, index) => (
|
|
162
|
+
<TreeItem
|
|
163
|
+
key={child.key}
|
|
164
|
+
label={Array.isArray(data) ? undefined : child.key}
|
|
165
|
+
data={child.value}
|
|
166
|
+
defaultExpanded={defaultExpanded}
|
|
167
|
+
expandIcon={expandIcon}
|
|
168
|
+
collapseIcon={collapseIcon}
|
|
169
|
+
level={childLevel}
|
|
170
|
+
setSize={children.length}
|
|
171
|
+
posInSet={index + 1}
|
|
172
|
+
/>
|
|
173
|
+
));
|
|
174
|
+
|
|
175
|
+
if (isRoot) {
|
|
176
|
+
return hasChildren ? <>{childrenNodes}</> : <>{renderLeafContent()}</>;
|
|
177
|
+
}
|
|
178
|
+
|
|
120
179
|
return (
|
|
121
|
-
<div
|
|
122
|
-
{
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
180
|
+
<div
|
|
181
|
+
ref={itemRef}
|
|
182
|
+
role="treeitem"
|
|
183
|
+
aria-expanded={hasChildren ? isExpanded : undefined}
|
|
184
|
+
aria-level={level}
|
|
185
|
+
aria-setsize={setSize}
|
|
186
|
+
aria-posinset={posInSet}
|
|
187
|
+
tabIndex={-1}
|
|
188
|
+
className={clsx(styles.item, hasChildren && styles.branch)}
|
|
189
|
+
onClick={handleClick}
|
|
190
|
+
onFocus={handleFocus}
|
|
191
|
+
onKeyDown={handleKeyDown}
|
|
192
|
+
>
|
|
193
|
+
<div className={styles.itemHeader}>
|
|
194
|
+
<span className={styles.toggleIcon} aria-hidden="true">
|
|
195
|
+
{hasChildren
|
|
196
|
+
? isExpanded
|
|
197
|
+
? (collapseIcon ?? defaultCollapseIcon)
|
|
198
|
+
: (expandIcon ?? defaultExpandIcon)
|
|
199
|
+
: null}
|
|
200
|
+
</span>
|
|
201
|
+
{label !== undefined && <span className={styles.key}>{label}:</span>}
|
|
202
|
+
{!hasChildren && renderLeafContent()}
|
|
203
|
+
</div>
|
|
204
|
+
{hasChildren && isExpanded && (
|
|
205
|
+
<div role="group" className={styles.childGroup}>
|
|
206
|
+
{childrenNodes}
|
|
140
207
|
</div>
|
|
141
208
|
)}
|
|
142
|
-
{hasChildren && renderContent()}
|
|
143
|
-
{isRoot && !hasChildren && <div className={styles.itemHeader}>{renderContent()}</div>}
|
|
144
209
|
</div>
|
|
145
210
|
);
|
|
146
211
|
};
|
|
@@ -13,4 +13,10 @@ export interface TreeItemProps {
|
|
|
13
13
|
collapseIcon?: ReactNode;
|
|
14
14
|
/** Whether the tree item is a root element */
|
|
15
15
|
isRoot?: boolean;
|
|
16
|
+
/** ARIA level (depth in the tree, 1-based) */
|
|
17
|
+
level: number;
|
|
18
|
+
/** Total number of siblings at this level */
|
|
19
|
+
setSize: number;
|
|
20
|
+
/** 1-based position among siblings */
|
|
21
|
+
posInSet: number;
|
|
16
22
|
}
|
|
@@ -5,6 +5,8 @@ import { AppLayout } from './AppLayout.tsx';
|
|
|
5
5
|
import { Box } from '../../components/Box/Box.tsx';
|
|
6
6
|
import { Button } from '../../components/Button/Button.tsx';
|
|
7
7
|
import { Card, CardHeader, CardContent } from '../../components/Card/Card.tsx';
|
|
8
|
+
import { Header } from '../../components/Header/Header.tsx';
|
|
9
|
+
import { NavBar } from '../../components/NavBar/NavBar.tsx';
|
|
8
10
|
import { Text } from '../../components/Text/Text.tsx';
|
|
9
11
|
|
|
10
12
|
const meta = {
|
|
@@ -13,13 +15,6 @@ const meta = {
|
|
|
13
15
|
parameters: {
|
|
14
16
|
layout: 'fullscreen',
|
|
15
17
|
},
|
|
16
|
-
args: {
|
|
17
|
-
headerTitle: 'Tharaday',
|
|
18
|
-
onLogin: fn(),
|
|
19
|
-
onLogout: fn(),
|
|
20
|
-
onCreateAccount: fn(),
|
|
21
|
-
onNavItemClick: fn(),
|
|
22
|
-
},
|
|
23
18
|
} satisfies Meta<typeof AppLayout>;
|
|
24
19
|
|
|
25
20
|
export default meta;
|
|
@@ -46,14 +41,26 @@ const navItems = [
|
|
|
46
41
|
|
|
47
42
|
export const Default: Story = {
|
|
48
43
|
args: {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
44
|
+
header: (
|
|
45
|
+
<Header
|
|
46
|
+
logo={logo}
|
|
47
|
+
title="Tharaday"
|
|
48
|
+
user={{ name: 'John Doe' }}
|
|
49
|
+
onLogout={fn()}
|
|
50
|
+
onCreateAccount={fn()}
|
|
51
|
+
/>
|
|
52
|
+
),
|
|
53
|
+
navbar: (
|
|
54
|
+
<NavBar
|
|
55
|
+
items={navItems}
|
|
56
|
+
activeId="dashboard"
|
|
57
|
+
actions={
|
|
58
|
+
<Button size="sm" variant="outline">
|
|
59
|
+
Feedback
|
|
60
|
+
</Button>
|
|
61
|
+
}
|
|
62
|
+
onItemClick={fn()}
|
|
63
|
+
/>
|
|
57
64
|
),
|
|
58
65
|
children: (
|
|
59
66
|
<Box display="flex" flexDirection="column" gap={6}>
|
|
@@ -72,7 +79,7 @@ export const Default: Story = {
|
|
|
72
79
|
The top bar is the <strong>Header</strong>. It handles:
|
|
73
80
|
</Text>
|
|
74
81
|
<ul>
|
|
75
|
-
<li>Global Branding (Logo & Title)</li>
|
|
82
|
+
<li>Global Branding (Logo & Title)</li>
|
|
76
83
|
<li>User Identity (Welcome message)</li>
|
|
77
84
|
<li>Session State (Log out button)</li>
|
|
78
85
|
</ul>
|
|
@@ -111,31 +118,36 @@ export const Default: Story = {
|
|
|
111
118
|
|
|
112
119
|
export const LoggedOut: Story = {
|
|
113
120
|
args: {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
...LoggedOut.args,
|
|
129
|
-
onLogin: undefined,
|
|
130
|
-
onCreateAccount: undefined,
|
|
121
|
+
header: <Header logo={logo} title="Tharaday" onLogin={fn()} onCreateAccount={fn()} />,
|
|
122
|
+
navbar: (
|
|
123
|
+
<NavBar
|
|
124
|
+
items={[
|
|
125
|
+
{ id: 'home', label: 'Home' },
|
|
126
|
+
{ id: 'features', label: 'Features' },
|
|
127
|
+
{ id: 'pricing', label: 'Pricing' },
|
|
128
|
+
{ id: 'docs', label: 'Documentation' },
|
|
129
|
+
]}
|
|
130
|
+
activeId="home"
|
|
131
|
+
onItemClick={fn()}
|
|
132
|
+
/>
|
|
133
|
+
),
|
|
134
|
+
children: Default.args?.children,
|
|
131
135
|
},
|
|
132
136
|
};
|
|
133
137
|
|
|
134
138
|
export const LongHeaderContent: Story = {
|
|
135
139
|
args: {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
header: (
|
|
141
|
+
<Header
|
|
142
|
+
logo={logo}
|
|
143
|
+
title="Tharaday Design System Platform"
|
|
144
|
+
user={{ name: 'Alexandria Catherine Montgomery' }}
|
|
145
|
+
onLogout={fn()}
|
|
146
|
+
maxWidth="48rem"
|
|
147
|
+
/>
|
|
148
|
+
),
|
|
149
|
+
navbar: <NavBar items={navItems} activeId="dashboard" onItemClick={fn()} maxWidth="48rem" />,
|
|
139
150
|
maxWidth: '48rem',
|
|
151
|
+
children: Default.args?.children,
|
|
140
152
|
},
|
|
141
153
|
};
|
|
@@ -3,48 +3,18 @@ import clsx from 'clsx';
|
|
|
3
3
|
import styles from './AppLayout.module.css';
|
|
4
4
|
import type { AppLayoutProps } from './AppLayout.types.ts';
|
|
5
5
|
import { Box } from '../../components/Box/Box.tsx';
|
|
6
|
-
import { Header } from '../../components/Header/Header.tsx';
|
|
7
|
-
import { NavBar } from '../../components/NavBar/NavBar.tsx';
|
|
8
6
|
|
|
9
|
-
/**
|
|
10
|
-
* AppLayout provides a standard layout structure that includes both
|
|
11
|
-
* a Header (for branding and user state) and a NavBar (for navigation).
|
|
12
|
-
*
|
|
13
|
-
* This component demonstrates the distinct roles of Header and NavBar.
|
|
14
|
-
*/
|
|
15
7
|
export const AppLayout = ({
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
user,
|
|
19
|
-
navItems,
|
|
20
|
-
activeNavId,
|
|
21
|
-
navActions,
|
|
8
|
+
header,
|
|
9
|
+
navbar,
|
|
22
10
|
children,
|
|
23
|
-
onLogin,
|
|
24
|
-
onLogout,
|
|
25
|
-
onCreateAccount,
|
|
26
|
-
onNavItemClick,
|
|
27
11
|
className,
|
|
28
12
|
maxWidth = '75rem',
|
|
29
13
|
}: AppLayoutProps) => {
|
|
30
14
|
return (
|
|
31
15
|
<div className={clsx(styles.root, className)}>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
title={headerTitle}
|
|
35
|
-
user={user}
|
|
36
|
-
onLogin={onLogin}
|
|
37
|
-
onLogout={onLogout}
|
|
38
|
-
onCreateAccount={onCreateAccount}
|
|
39
|
-
maxWidth={maxWidth}
|
|
40
|
-
/>
|
|
41
|
-
<NavBar
|
|
42
|
-
items={navItems}
|
|
43
|
-
activeId={activeNavId}
|
|
44
|
-
actions={navActions}
|
|
45
|
-
onItemClick={onNavItemClick}
|
|
46
|
-
maxWidth={maxWidth}
|
|
47
|
-
/>
|
|
16
|
+
{header}
|
|
17
|
+
{navbar}
|
|
48
18
|
<main className={styles.main}>
|
|
49
19
|
<Box className={styles.container} maxWidth={maxWidth}>
|
|
50
20
|
{children}
|
|
@@ -1,58 +1,14 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
-
import type { NavBarItem } from '../../components/NavBar/NavBar.types.ts';
|
|
4
|
-
|
|
5
3
|
export interface AppLayoutProps {
|
|
6
|
-
/**
|
|
7
|
-
* Additional class name for the layout root
|
|
8
|
-
*/
|
|
4
|
+
/** Additional class name for the layout root */
|
|
9
5
|
className?: string;
|
|
10
|
-
/**
|
|
11
|
-
* Max width for header and navbar containers
|
|
12
|
-
*/
|
|
6
|
+
/** Max width for the main content container */
|
|
13
7
|
maxWidth?: string | number;
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Header title
|
|
20
|
-
*/
|
|
21
|
-
headerTitle?: string;
|
|
22
|
-
/**
|
|
23
|
-
* Current user object for the Header
|
|
24
|
-
*/
|
|
25
|
-
user?: { name: string };
|
|
26
|
-
/**
|
|
27
|
-
* NavBar navigation items
|
|
28
|
-
*/
|
|
29
|
-
navItems: NavBarItem[];
|
|
30
|
-
/**
|
|
31
|
-
* Active NavBar item ID
|
|
32
|
-
*/
|
|
33
|
-
activeNavId?: string;
|
|
34
|
-
/**
|
|
35
|
-
* NavBar actions (right side)
|
|
36
|
-
*/
|
|
37
|
-
navActions?: ReactNode;
|
|
38
|
-
/**
|
|
39
|
-
* Content to display in the main area
|
|
40
|
-
*/
|
|
8
|
+
/** Header slot — render a pre-configured Header component */
|
|
9
|
+
header?: ReactNode;
|
|
10
|
+
/** NavBar slot — render a pre-configured NavBar component */
|
|
11
|
+
navbar?: ReactNode;
|
|
12
|
+
/** Main content */
|
|
41
13
|
children: ReactNode;
|
|
42
|
-
/**
|
|
43
|
-
* Callback for header login
|
|
44
|
-
*/
|
|
45
|
-
onLogin?: () => void;
|
|
46
|
-
/**
|
|
47
|
-
* Callback for header logout
|
|
48
|
-
*/
|
|
49
|
-
onLogout?: () => void;
|
|
50
|
-
/**
|
|
51
|
-
* Callback for header create account
|
|
52
|
-
*/
|
|
53
|
-
onCreateAccount?: () => void;
|
|
54
|
-
/**
|
|
55
|
-
* Callback for nav item click
|
|
56
|
-
*/
|
|
57
|
-
onNavItemClick?: (id: string) => void;
|
|
58
14
|
}
|
|
@@ -8,6 +8,7 @@ import { Box } from '../../components/Box/Box.tsx';
|
|
|
8
8
|
import { Breadcrumbs, BreadcrumbItem } from '../../components/Breadcrumbs/Breadcrumbs.tsx';
|
|
9
9
|
import { Button } from '../../components/Button/Button.tsx';
|
|
10
10
|
import { Card, CardHeader, CardContent } from '../../components/Card/Card.tsx';
|
|
11
|
+
import { Header } from '../../components/Header/Header.tsx';
|
|
11
12
|
import { Text } from '../../components/Text/Text.tsx';
|
|
12
13
|
|
|
13
14
|
const meta = {
|
|
@@ -16,12 +17,6 @@ const meta = {
|
|
|
16
17
|
parameters: {
|
|
17
18
|
layout: 'fullscreen',
|
|
18
19
|
},
|
|
19
|
-
args: {
|
|
20
|
-
headerTitle: 'DS Creator',
|
|
21
|
-
onLogin: fn(),
|
|
22
|
-
onLogout: fn(),
|
|
23
|
-
onCreateAccount: fn(),
|
|
24
|
-
},
|
|
25
20
|
} satisfies Meta<typeof DashboardLayout>;
|
|
26
21
|
|
|
27
22
|
export default meta;
|
|
@@ -41,8 +36,9 @@ const logo = (
|
|
|
41
36
|
|
|
42
37
|
export const Overview: Story = {
|
|
43
38
|
args: {
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
header: (
|
|
40
|
+
<Header logo={logo} title="DS Creator" user={{ name: 'Design Creator' }} onLogout={fn()} />
|
|
41
|
+
),
|
|
46
42
|
breadcrumbs: (
|
|
47
43
|
<Breadcrumbs>
|
|
48
44
|
<BreadcrumbItem href="#">Home</BreadcrumbItem>
|
|
@@ -2,33 +2,18 @@ import clsx from 'clsx';
|
|
|
2
2
|
|
|
3
3
|
import styles from './DashboardLayout.module.css';
|
|
4
4
|
import type { DashboardLayoutProps } from './DashboardLayout.types.tsx';
|
|
5
|
-
import { Header } from '../../components/Header/Header.tsx';
|
|
6
5
|
|
|
7
6
|
export const DashboardLayout = ({
|
|
8
|
-
|
|
9
|
-
headerTitle,
|
|
7
|
+
header,
|
|
10
8
|
breadcrumbs,
|
|
11
9
|
actions,
|
|
12
10
|
stats,
|
|
13
11
|
children,
|
|
14
|
-
user,
|
|
15
|
-
onLogin,
|
|
16
|
-
onLogout,
|
|
17
|
-
onCreateAccount,
|
|
18
12
|
className,
|
|
19
|
-
maxWidth,
|
|
20
13
|
}: DashboardLayoutProps) => {
|
|
21
14
|
return (
|
|
22
15
|
<div className={clsx(styles.root, className)}>
|
|
23
|
-
|
|
24
|
-
logo={headerLogo}
|
|
25
|
-
title={headerTitle}
|
|
26
|
-
user={user}
|
|
27
|
-
onLogin={onLogin}
|
|
28
|
-
onLogout={onLogout}
|
|
29
|
-
onCreateAccount={onCreateAccount}
|
|
30
|
-
maxWidth={maxWidth}
|
|
31
|
-
/>
|
|
16
|
+
{header}
|
|
32
17
|
<main className={styles.main}>
|
|
33
18
|
<div className={styles.container}>
|
|
34
19
|
{(breadcrumbs || actions) && (
|
|
@@ -2,15 +2,10 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
|
|
3
3
|
export interface DashboardLayoutProps {
|
|
4
4
|
className?: string;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
headerTitle?: string;
|
|
5
|
+
/** Header slot — render a pre-configured Header component */
|
|
6
|
+
header?: ReactNode;
|
|
8
7
|
breadcrumbs?: ReactNode;
|
|
9
8
|
actions?: ReactNode;
|
|
10
9
|
stats?: ReactNode;
|
|
11
10
|
children: ReactNode;
|
|
12
|
-
user?: { name: string };
|
|
13
|
-
onLogin?: () => void;
|
|
14
|
-
onLogout?: () => void;
|
|
15
|
-
onCreateAccount?: () => void;
|
|
16
11
|
}
|
|
@@ -6,6 +6,7 @@ import { Box } from '../../components/Box/Box.tsx';
|
|
|
6
6
|
import { Breadcrumbs, BreadcrumbItem } from '../../components/Breadcrumbs/Breadcrumbs.tsx';
|
|
7
7
|
import { Button } from '../../components/Button/Button.tsx';
|
|
8
8
|
import { Card, CardHeader, CardContent, CardFooter } from '../../components/Card/Card.tsx';
|
|
9
|
+
import { Header } from '../../components/Header/Header.tsx';
|
|
9
10
|
import { Input } from '../../components/Input/Input.tsx';
|
|
10
11
|
import { Notification } from '../../components/Notification/Notification.tsx';
|
|
11
12
|
import { Text } from '../../components/Text/Text.tsx';
|
|
@@ -17,20 +18,28 @@ const meta = {
|
|
|
17
18
|
parameters: {
|
|
18
19
|
layout: 'fullscreen',
|
|
19
20
|
},
|
|
20
|
-
args: {
|
|
21
|
-
headerTitle: 'DS Creator',
|
|
22
|
-
onLogin: fn(),
|
|
23
|
-
onLogout: fn(),
|
|
24
|
-
onCreateAccount: fn(),
|
|
25
|
-
},
|
|
26
21
|
} satisfies Meta<typeof SettingsLayout>;
|
|
27
22
|
|
|
28
23
|
export default meta;
|
|
29
24
|
type Story = StoryObj<typeof meta>;
|
|
30
25
|
|
|
26
|
+
const logo = (
|
|
27
|
+
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
|
28
|
+
<g fill="none" fillRule="evenodd">
|
|
29
|
+
<path
|
|
30
|
+
d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
|
|
31
|
+
fill="#3b82f6"
|
|
32
|
+
/>
|
|
33
|
+
<path d="M16 8l8 12H8l8-12z" fill="#FFF" />
|
|
34
|
+
</g>
|
|
35
|
+
</svg>
|
|
36
|
+
);
|
|
37
|
+
|
|
31
38
|
export const ProfileSettings: Story = {
|
|
32
39
|
args: {
|
|
33
|
-
|
|
40
|
+
header: (
|
|
41
|
+
<Header logo={logo} title="DS Creator" user={{ name: 'Design Creator' }} onLogout={fn()} />
|
|
42
|
+
),
|
|
34
43
|
breadcrumbs: (
|
|
35
44
|
<Breadcrumbs>
|
|
36
45
|
<BreadcrumbItem href="#">Home</BreadcrumbItem>
|
|
@@ -2,32 +2,17 @@ import clsx from 'clsx';
|
|
|
2
2
|
|
|
3
3
|
import styles from './SettingsLayout.module.css';
|
|
4
4
|
import type { SettingsLayoutProps } from './SettingsLayout.types.tsx';
|
|
5
|
-
import { Header } from '../../components/Header/Header.tsx';
|
|
6
5
|
|
|
7
6
|
export const SettingsLayout = ({
|
|
8
|
-
|
|
9
|
-
headerTitle,
|
|
7
|
+
header,
|
|
10
8
|
breadcrumbs,
|
|
11
9
|
sidebar,
|
|
12
10
|
children,
|
|
13
|
-
user,
|
|
14
|
-
onLogin,
|
|
15
|
-
onLogout,
|
|
16
|
-
onCreateAccount,
|
|
17
11
|
className,
|
|
18
|
-
maxWidth,
|
|
19
12
|
}: SettingsLayoutProps) => {
|
|
20
13
|
return (
|
|
21
14
|
<div className={clsx(styles.root, className)}>
|
|
22
|
-
|
|
23
|
-
logo={headerLogo}
|
|
24
|
-
title={headerTitle}
|
|
25
|
-
user={user}
|
|
26
|
-
onLogin={onLogin}
|
|
27
|
-
onLogout={onLogout}
|
|
28
|
-
onCreateAccount={onCreateAccount}
|
|
29
|
-
maxWidth={maxWidth}
|
|
30
|
-
/>
|
|
15
|
+
{header}
|
|
31
16
|
<main className={styles.main}>
|
|
32
17
|
<div className={styles.container}>
|
|
33
18
|
{breadcrumbs && <div className={styles.breadcrumbs}>{breadcrumbs}</div>}
|