@wallarm-org/design-system 1.24.0 → 1.26.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/README.md +2 -0
- package/dist/components/AppShell/story-content/_storyAccountDropdown.d.ts +5 -2
- package/dist/components/AppShell/story-content/_storyAccountDropdown.js +110 -55
- package/dist/components/AppShell/story-content/_storyConfigRenderer.d.ts +4 -1
- package/dist/components/AppShell/story-content/_storyConfigRenderer.js +8 -5
- package/dist/components/AppShell/story-content/index.d.ts +1 -1
- package/dist/components/AppShell/story-content/index.js +1 -1
- package/dist/components/DropdownMenu/DropdownMenuRadioItem.js +1 -1
- package/dist/components/NavPanel/NavPanelBack.js +1 -1
- package/dist/components/NavPanel/NavPanelGroupItem.js +8 -1
- package/dist/components/NavPanel/NavPanelGroupLabel.js +1 -1
- package/dist/components/NavPanel/NavPanelSkeleton.js +1 -1
- package/dist/components/NavPanel/classes.js +6 -6
- package/dist/components/NavRail/NavRail.d.ts +7 -0
- package/dist/components/NavRail/NavRail.js +5 -3
- package/dist/components/NavRail/NavRailBody.js +6 -1
- package/dist/components/NavRail/NavRailContext.d.ts +2 -1
- package/dist/components/NavRail/NavRailContext.js +1 -1
- package/dist/components/NavRail/NavRailFooter.js +6 -1
- package/dist/components/NavRail/NavRailItem.d.ts +4 -0
- package/dist/components/NavRail/NavRailItem.js +33 -7
- package/dist/components/NavRail/NavRailSkeleton.js +24 -4
- package/dist/components/NavRail/classes.d.ts +13 -1
- package/dist/components/NavRail/classes.js +55 -9
- package/dist/components/NavRail/index.d.ts +1 -1
- package/dist/components/SearchModal/SearchModal.js +1 -0
- package/dist/components/Skeleton/Skeleton.d.ts +1 -1
- package/dist/components/Skeleton/Skeleton.js +1 -0
- package/dist/components/ThemeProvider/ThemeProvider.d.ts +4 -1
- package/dist/components/ThemeProvider/ThemeProvider.js +18 -2
- package/dist/components/ThemeProvider/ThemeProviderContext.d.ts +3 -1
- package/dist/components/ThemeProvider/ThemeProviderContext.js +3 -1
- package/dist/components/ThemeProvider/index.d.ts +1 -1
- package/dist/components/ThemeProvider/types.d.ts +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/metadata/components.json +56 -9
- package/dist/theme/components/app-shell.css +11 -0
- package/dist/theme/index.css +4 -0
- package/dist/theme/radius.css +1 -0
- package/dist/theme/semantic.css +79 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,8 @@ export const App = () => {
|
|
|
37
37
|
};
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
`ThemeProvider` also controls the Frame style (`useTheme().setFrameStyle`, `'neutral' | 'branded'`) - see [Installation Guide](docs/installation.md) for details.
|
|
41
|
+
|
|
40
42
|
```css
|
|
41
43
|
/* App.css */
|
|
42
44
|
@import 'tailwindcss';
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
1
|
+
import { type NavRailMode } from '../../NavRail';
|
|
2
|
+
import { type Theme } from '../../ThemeProvider';
|
|
3
|
+
export type SidebarMode = 'adaptive' | 'expanded' | 'collapsed' | 'compact';
|
|
4
|
+
/** Adaptive collapses the rail inside a product and expands it on Home; the other modes pin it. */
|
|
5
|
+
export declare const railModeFor: (mode: SidebarMode, onHome: boolean) => NavRailMode;
|
|
3
6
|
export declare const AccountDropdown: ({ sidebarMode, onSidebarModeChange, theme, onThemeChange, }: {
|
|
4
7
|
sidebarMode: SidebarMode;
|
|
5
8
|
onSidebarModeChange: (mode: SidebarMode) => void;
|
|
@@ -1,20 +1,60 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { Fragment } from "react";
|
|
3
|
+
import { LogOut, PanelLeftDashed, PencilRuler, Settings, User, UserRound } from "../../../icons/index.js";
|
|
4
|
+
import { cn } from "../../../utils/cn.js";
|
|
5
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuItemContent, DropdownMenuItemIcon, DropdownMenuItemText, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, DropdownMenuTriggerItem } from "../../DropdownMenu/index.js";
|
|
4
6
|
import { NavRailItem } from "../../NavRail/index.js";
|
|
5
7
|
import { HStack, VStack } from "../../Stack/index.js";
|
|
6
8
|
import { Text } from "../../Text/index.js";
|
|
9
|
+
import { useTheme } from "../../ThemeProvider/index.js";
|
|
10
|
+
const railModeFor = (mode, onHome)=>{
|
|
11
|
+
if ('adaptive' === mode) return onHome ? 'expanded' : 'collapsed';
|
|
12
|
+
return mode;
|
|
13
|
+
};
|
|
7
14
|
const USER_NAME = 'Meow Meow';
|
|
8
15
|
const USER_EMAIL = 'meow@meow.com';
|
|
16
|
+
const THEMES = [
|
|
17
|
+
'light',
|
|
18
|
+
'dark'
|
|
19
|
+
];
|
|
20
|
+
const SIDEBAR_MODE_GROUPS = [
|
|
21
|
+
[
|
|
22
|
+
'adaptive'
|
|
23
|
+
],
|
|
24
|
+
[
|
|
25
|
+
'expanded',
|
|
26
|
+
'collapsed'
|
|
27
|
+
],
|
|
28
|
+
[
|
|
29
|
+
'compact'
|
|
30
|
+
]
|
|
31
|
+
];
|
|
32
|
+
const SIDEBAR_MODES = SIDEBAR_MODE_GROUPS.flat();
|
|
9
33
|
const THEME_LABELS = {
|
|
10
34
|
light: 'Light',
|
|
11
35
|
dark: 'Dark'
|
|
12
36
|
};
|
|
37
|
+
const FRAME_STYLES = [
|
|
38
|
+
'neutral',
|
|
39
|
+
'branded'
|
|
40
|
+
];
|
|
41
|
+
const FRAME_STYLE_LABELS = {
|
|
42
|
+
neutral: 'Neutral',
|
|
43
|
+
branded: 'Branded'
|
|
44
|
+
};
|
|
45
|
+
const FRAME_STYLE_SWATCH = {
|
|
46
|
+
neutral: 'bg-icon-secondary',
|
|
47
|
+
branded: 'bg-icon-brand'
|
|
48
|
+
};
|
|
13
49
|
const SIDEBAR_MODE_LABELS = {
|
|
14
50
|
adaptive: 'Adaptive',
|
|
15
|
-
expanded: 'Always expanded'
|
|
51
|
+
expanded: 'Always expanded',
|
|
52
|
+
collapsed: 'Always collapsed',
|
|
53
|
+
compact: 'Compact'
|
|
16
54
|
};
|
|
17
|
-
const AccountDropdown = ({ sidebarMode, onSidebarModeChange, theme, onThemeChange })
|
|
55
|
+
const AccountDropdown = ({ sidebarMode, onSidebarModeChange, theme, onThemeChange })=>{
|
|
56
|
+
const { frameStyle, setFrameStyle } = useTheme();
|
|
57
|
+
return /*#__PURE__*/ jsxs(DropdownMenu, {
|
|
18
58
|
positioning: {
|
|
19
59
|
placement: 'right-end',
|
|
20
60
|
gutter: 6,
|
|
@@ -25,8 +65,9 @@ const AccountDropdown = ({ sidebarMode, onSidebarModeChange, theme, onThemeChang
|
|
|
25
65
|
children: [
|
|
26
66
|
/*#__PURE__*/ jsx(DropdownMenuTrigger, {
|
|
27
67
|
children: /*#__PURE__*/ jsx(NavRailItem, {
|
|
28
|
-
icon:
|
|
29
|
-
label: USER_NAME
|
|
68
|
+
icon: UserRound,
|
|
69
|
+
label: USER_NAME,
|
|
70
|
+
avatar: true
|
|
30
71
|
})
|
|
31
72
|
}),
|
|
32
73
|
/*#__PURE__*/ jsxs(DropdownMenuContent, {
|
|
@@ -79,12 +120,16 @@ const AccountDropdown = ({ sidebarMode, onSidebarModeChange, theme, onThemeChang
|
|
|
79
120
|
/*#__PURE__*/ jsxs(DropdownMenuItemContent, {
|
|
80
121
|
children: [
|
|
81
122
|
/*#__PURE__*/ jsx(DropdownMenuItemText, {
|
|
82
|
-
children: "
|
|
123
|
+
children: "Appearance"
|
|
83
124
|
}),
|
|
84
|
-
/*#__PURE__*/
|
|
125
|
+
/*#__PURE__*/ jsxs(Text, {
|
|
85
126
|
size: "xs",
|
|
86
127
|
color: "secondary",
|
|
87
|
-
children:
|
|
128
|
+
children: [
|
|
129
|
+
THEME_LABELS[theme],
|
|
130
|
+
" and ",
|
|
131
|
+
FRAME_STYLE_LABELS[frameStyle].toLowerCase()
|
|
132
|
+
]
|
|
88
133
|
})
|
|
89
134
|
]
|
|
90
135
|
})
|
|
@@ -93,27 +138,42 @@ const AccountDropdown = ({ sidebarMode, onSidebarModeChange, theme, onThemeChang
|
|
|
93
138
|
/*#__PURE__*/ jsxs(DropdownMenuContent, {
|
|
94
139
|
className: "w-132",
|
|
95
140
|
children: [
|
|
96
|
-
/*#__PURE__*/
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
141
|
+
/*#__PURE__*/ jsx(DropdownMenuRadioGroup, {
|
|
142
|
+
value: theme,
|
|
143
|
+
onValueChange: ({ value })=>{
|
|
144
|
+
const next = THEMES.find((t)=>t === value);
|
|
145
|
+
if (next) onThemeChange(next);
|
|
146
|
+
},
|
|
147
|
+
children: THEMES.map((t)=>/*#__PURE__*/ jsx(DropdownMenuRadioItem, {
|
|
148
|
+
value: t,
|
|
149
|
+
children: /*#__PURE__*/ jsx(DropdownMenuItemText, {
|
|
150
|
+
children: THEME_LABELS[t]
|
|
151
|
+
})
|
|
152
|
+
}, t))
|
|
106
153
|
}),
|
|
107
|
-
/*#__PURE__*/
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
154
|
+
/*#__PURE__*/ jsx(DropdownMenuSeparator, {}),
|
|
155
|
+
/*#__PURE__*/ jsx(DropdownMenuRadioGroup, {
|
|
156
|
+
value: frameStyle,
|
|
157
|
+
onValueChange: ({ value })=>{
|
|
158
|
+
const next = FRAME_STYLES.find((s)=>s === value);
|
|
159
|
+
if (next) setFrameStyle(next);
|
|
160
|
+
},
|
|
161
|
+
children: FRAME_STYLES.map((s)=>/*#__PURE__*/ jsxs(DropdownMenuRadioItem, {
|
|
162
|
+
value: s,
|
|
163
|
+
children: [
|
|
164
|
+
/*#__PURE__*/ jsx(DropdownMenuItemIcon, {
|
|
165
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
166
|
+
className: "flex size-16 items-center justify-center",
|
|
167
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
168
|
+
className: cn('size-8 rounded-full', FRAME_STYLE_SWATCH[s])
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
}),
|
|
172
|
+
/*#__PURE__*/ jsx(DropdownMenuItemText, {
|
|
173
|
+
children: FRAME_STYLE_LABELS[s]
|
|
174
|
+
})
|
|
175
|
+
]
|
|
176
|
+
}, s))
|
|
117
177
|
})
|
|
118
178
|
]
|
|
119
179
|
})
|
|
@@ -140,32 +200,26 @@ const AccountDropdown = ({ sidebarMode, onSidebarModeChange, theme, onThemeChang
|
|
|
140
200
|
})
|
|
141
201
|
]
|
|
142
202
|
}),
|
|
143
|
-
/*#__PURE__*/
|
|
203
|
+
/*#__PURE__*/ jsx(DropdownMenuContent, {
|
|
144
204
|
className: "w-200",
|
|
145
|
-
children:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
'expanded' === sidebarMode && /*#__PURE__*/ jsx(DropdownMenuShortcut, {
|
|
164
|
-
children: /*#__PURE__*/ jsx(Check, {})
|
|
165
|
-
})
|
|
166
|
-
]
|
|
167
|
-
})
|
|
168
|
-
]
|
|
205
|
+
children: /*#__PURE__*/ jsx(DropdownMenuRadioGroup, {
|
|
206
|
+
value: sidebarMode,
|
|
207
|
+
onValueChange: ({ value })=>{
|
|
208
|
+
const next = SIDEBAR_MODES.find((m)=>m === value);
|
|
209
|
+
if (next) onSidebarModeChange(next);
|
|
210
|
+
},
|
|
211
|
+
children: SIDEBAR_MODE_GROUPS.map((group, index)=>/*#__PURE__*/ jsxs(Fragment, {
|
|
212
|
+
children: [
|
|
213
|
+
index > 0 && /*#__PURE__*/ jsx(DropdownMenuSeparator, {}),
|
|
214
|
+
group.map((m)=>/*#__PURE__*/ jsx(DropdownMenuRadioItem, {
|
|
215
|
+
value: m,
|
|
216
|
+
children: /*#__PURE__*/ jsx(DropdownMenuItemText, {
|
|
217
|
+
children: SIDEBAR_MODE_LABELS[m]
|
|
218
|
+
})
|
|
219
|
+
}, m))
|
|
220
|
+
]
|
|
221
|
+
}, group.join('-')))
|
|
222
|
+
})
|
|
169
223
|
})
|
|
170
224
|
]
|
|
171
225
|
}),
|
|
@@ -184,4 +238,5 @@ const AccountDropdown = ({ sidebarMode, onSidebarModeChange, theme, onThemeChang
|
|
|
184
238
|
})
|
|
185
239
|
]
|
|
186
240
|
});
|
|
187
|
-
|
|
241
|
+
};
|
|
242
|
+
export { AccountDropdown, railModeFor };
|
|
@@ -3,7 +3,10 @@ import { type Product } from './_storyLib';
|
|
|
3
3
|
export interface ConfigRemoteProps {
|
|
4
4
|
config: NavConfig;
|
|
5
5
|
basePath?: string;
|
|
6
|
+
/** Fake a 2s load whenever the product changes, to show the panel and content skeletons. */
|
|
7
|
+
simulateLoading?: boolean;
|
|
6
8
|
}
|
|
7
|
-
export declare const RemoteForProduct: ({ product }: {
|
|
9
|
+
export declare const RemoteForProduct: ({ product, simulateLoading, }: {
|
|
8
10
|
product: Product;
|
|
11
|
+
simulateLoading?: boolean;
|
|
9
12
|
}) => import("react").JSX.Element;
|
|
@@ -31,16 +31,18 @@ const RemotePageContent = ()=>{
|
|
|
31
31
|
]
|
|
32
32
|
});
|
|
33
33
|
};
|
|
34
|
-
const ConfigRemote = ({ config, basePath })=>{
|
|
35
|
-
const [loading, setLoading] = useState(
|
|
34
|
+
const ConfigRemote = ({ config, basePath, simulateLoading = true })=>{
|
|
35
|
+
const [loading, setLoading] = useState(simulateLoading);
|
|
36
36
|
useEffect(()=>{
|
|
37
|
+
if (!simulateLoading) return;
|
|
37
38
|
setLoading(true);
|
|
38
39
|
const timer = setTimeout(()=>{
|
|
39
40
|
setLoading(false);
|
|
40
41
|
}, 2000);
|
|
41
42
|
return ()=>clearTimeout(timer);
|
|
42
43
|
}, [
|
|
43
|
-
config.productLabel
|
|
44
|
+
config.productLabel,
|
|
45
|
+
simulateLoading
|
|
44
46
|
]);
|
|
45
47
|
return /*#__PURE__*/ jsx(RemoteShell, {
|
|
46
48
|
config: config,
|
|
@@ -67,12 +69,13 @@ const ConfigRemote = ({ config, basePath })=>{
|
|
|
67
69
|
})
|
|
68
70
|
});
|
|
69
71
|
};
|
|
70
|
-
const RemoteForProduct = ({ product })=>{
|
|
72
|
+
const RemoteForProduct = ({ product, simulateLoading = true })=>{
|
|
71
73
|
if ('home' === product) return /*#__PURE__*/ jsx(HomeContent, {});
|
|
72
74
|
const { config } = PRODUCT_CONFIGS[product];
|
|
73
75
|
return /*#__PURE__*/ jsx(ConfigRemote, {
|
|
74
76
|
config: config,
|
|
75
|
-
basePath: `/${product}
|
|
77
|
+
basePath: `/${product}`,
|
|
78
|
+
simulateLoading: simulateLoading
|
|
76
79
|
});
|
|
77
80
|
};
|
|
78
81
|
export { RemoteForProduct };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { AccountDropdown, type SidebarMode } from './_storyAccountDropdown';
|
|
1
|
+
export { AccountDropdown, railModeFor, type SidebarMode } from './_storyAccountDropdown';
|
|
2
2
|
export { RemoteForProduct } from './_storyConfigRenderer';
|
|
3
3
|
export { HeaderActions } from './_storyHeaderActions';
|
|
4
4
|
export { HomeContent } from './_storyHomeContent';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { AccountDropdown } from "./_storyAccountDropdown.js";
|
|
1
|
+
export { AccountDropdown, railModeFor } from "./_storyAccountDropdown.js";
|
|
2
2
|
export { RemoteForProduct } from "./_storyConfigRenderer.js";
|
|
3
3
|
export { HeaderActions } from "./_storyHeaderActions.js";
|
|
4
4
|
export { HomeContent } from "./_storyHomeContent.js";
|
|
@@ -15,7 +15,7 @@ const DropdownMenuRadioItem = ({ children, value, disabled, closeOnSelect, varia
|
|
|
15
15
|
"data-testid": testId,
|
|
16
16
|
className: cn(dropdownMenuItemVariants({
|
|
17
17
|
variant
|
|
18
|
-
}), className),
|
|
18
|
+
}), 'data-[state=checked]:bg-states-primary-active', className),
|
|
19
19
|
children: [
|
|
20
20
|
children,
|
|
21
21
|
/*#__PURE__*/ jsx(DropdownMenuItemIndicator, {
|
|
@@ -11,7 +11,7 @@ const NavPanelBack = ({ ref, className, children, ...props })=>{
|
|
|
11
11
|
type: "button",
|
|
12
12
|
"data-slot": "nav-panel-back",
|
|
13
13
|
"data-testid": testId,
|
|
14
|
-
className: cn('overlay flex h-32 shrink-0 w-full cursor-pointer items-center gap-8 rounded-
|
|
14
|
+
className: cn('overlay flex h-32 shrink-0 w-full cursor-pointer items-center gap-8 rounded-10 p-8 text-sm text-text-primary transition-colors outline-none hover:overlay-states-primary-hover focus-visible:overlay-states-primary-hover active:overlay-states-primary-pressed', className),
|
|
15
15
|
children: [
|
|
16
16
|
/*#__PURE__*/ jsx(CornerUpLeft, {
|
|
17
17
|
size: "md"
|
|
@@ -4,7 +4,7 @@ import { cn } from "../../utils/cn.js";
|
|
|
4
4
|
import { useTestId } from "../../utils/testId.js";
|
|
5
5
|
import { Text } from "../Text/index.js";
|
|
6
6
|
import { navPanelGroupItemVariants } from "./classes.js";
|
|
7
|
-
import { useNavPanelDepth, useNavPanelIndent } from "./NavPanelContext.js";
|
|
7
|
+
import { NAV_PANEL_BASE_PADDING, useNavPanelDepth, useNavPanelIndent } from "./NavPanelContext.js";
|
|
8
8
|
const NavPanelGroupItem = ({ ref, asChild = false, icon: Icon, active = false, className, children, ...props })=>{
|
|
9
9
|
const testId = useTestId('group-item');
|
|
10
10
|
const depth = useNavPanelDepth();
|
|
@@ -33,6 +33,13 @@ const NavPanelGroupItem = ({ ref, asChild = false, icon: Icon, active = false, c
|
|
|
33
33
|
size: "sm",
|
|
34
34
|
truncate: true,
|
|
35
35
|
children: children
|
|
36
|
+
}),
|
|
37
|
+
active && depth > 0 && /*#__PURE__*/ jsx("span", {
|
|
38
|
+
"aria-hidden": true,
|
|
39
|
+
className: "absolute top-0 bottom-0 hidden w-px bg-border-brand branded:block",
|
|
40
|
+
style: {
|
|
41
|
+
left: depth * indent - NAV_PANEL_BASE_PADDING
|
|
42
|
+
}
|
|
36
43
|
})
|
|
37
44
|
]
|
|
38
45
|
});
|
|
@@ -22,7 +22,7 @@ const NavPanelGroupLabel = ({ ref, icon: Icon, className, children, ...props })=
|
|
|
22
22
|
style: {
|
|
23
23
|
paddingLeft: 8 + depth * indent
|
|
24
24
|
},
|
|
25
|
-
className: cn('overlay flex h-32 shrink-0 w-full cursor-pointer items-center gap-8 rounded-
|
|
25
|
+
className: cn('overlay flex h-32 shrink-0 w-full cursor-pointer items-center gap-8 rounded-10 py-8 pr-8 text-sm text-text-primary transition-colors outline-none hover:overlay-states-primary-hover focus-visible:overlay-states-primary-hover active:overlay-states-primary-pressed', className),
|
|
26
26
|
children: [
|
|
27
27
|
Icon && /*#__PURE__*/ jsx("span", {
|
|
28
28
|
className: "flex shrink-0 items-center justify-center",
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { cva } from "class-variance-authority";
|
|
2
|
-
const navPanelItemVariants = cva('overlay flex h-32 shrink-0 w-full cursor-pointer items-center gap-8 rounded-
|
|
2
|
+
const navPanelItemVariants = cva('overlay flex h-32 shrink-0 w-full cursor-pointer items-center gap-8 rounded-10 p-8 text-sm text-text-primary transition-colors outline-none', {
|
|
3
3
|
variants: {
|
|
4
4
|
active: {
|
|
5
|
-
true: 'overlay-states-primary-active font-semibold',
|
|
6
|
-
false: '
|
|
5
|
+
true: 'overlay-states-primary-active font-semibold branded:overlay-states-brand-active branded:text-text-brand',
|
|
6
|
+
false: 'hover:overlay-states-primary-hover focus-visible:overlay-states-primary-hover active:overlay-states-primary-pressed'
|
|
7
7
|
}
|
|
8
8
|
},
|
|
9
9
|
defaultVariants: {
|
|
10
10
|
active: false
|
|
11
11
|
}
|
|
12
12
|
});
|
|
13
|
-
const navPanelGroupItemVariants = cva('overlay flex h-32 shrink-0 w-full cursor-pointer items-center gap-8 rounded-
|
|
13
|
+
const navPanelGroupItemVariants = cva('overlay flex h-32 shrink-0 w-full cursor-pointer items-center gap-8 rounded-10 py-8 pr-8 text-sm text-text-primary transition-colors outline-none', {
|
|
14
14
|
variants: {
|
|
15
15
|
active: {
|
|
16
|
-
true: 'overlay-states-primary-active font-semibold',
|
|
17
|
-
false: '
|
|
16
|
+
true: 'overlay-states-primary-active font-semibold branded:overlay-states-brand-active branded:text-text-brand',
|
|
17
|
+
false: 'hover:overlay-states-primary-hover focus-visible:overlay-states-primary-hover active:overlay-states-primary-pressed'
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
defaultVariants: {
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type { FC, HTMLAttributes, ReactNode, Ref } from 'react';
|
|
2
2
|
import { type TestableProps } from '../../utils/testId';
|
|
3
|
+
import { type NavRailMode } from './NavRailContext';
|
|
3
4
|
export interface NavRailProps extends HTMLAttributes<HTMLElement>, TestableProps {
|
|
4
5
|
ref?: Ref<HTMLElement>;
|
|
5
6
|
children?: ReactNode;
|
|
7
|
+
/**
|
|
8
|
+
* How the rail lays out its items: `expanded` (icon beside label), `collapsed` (icon only) or
|
|
9
|
+
* `compact` (icon over a short label). Wins over `collapsed` when both are set.
|
|
10
|
+
*/
|
|
11
|
+
mode?: NavRailMode;
|
|
12
|
+
/** Shorthand for `mode='collapsed'`, kept so existing hosts keep working. Prefer `mode`. */
|
|
6
13
|
collapsed?: boolean;
|
|
7
14
|
}
|
|
8
15
|
export declare const NavRail: FC<NavRailProps>;
|
|
@@ -6,8 +6,9 @@ import { cn } from "../../utils/cn.js";
|
|
|
6
6
|
import { TestIdProvider } from "../../utils/testId.js";
|
|
7
7
|
import { navRailVariants } from "./classes.js";
|
|
8
8
|
import { NavRailContextProvider } from "./NavRailContext.js";
|
|
9
|
-
const NavRail = ({ ref, collapsed = false, className, children, 'data-testid': testId, ...props })=>{
|
|
9
|
+
const NavRail = ({ ref, mode: modeProp, collapsed = false, className, children, 'data-testid': testId, ...props })=>{
|
|
10
10
|
const internalRef = useRef(null);
|
|
11
|
+
const mode = modeProp ?? (collapsed ? 'collapsed' : 'expanded');
|
|
11
12
|
const focusPanel = useCallback(()=>{
|
|
12
13
|
const panel = document.querySelector('[data-slot="nav-panel"]');
|
|
13
14
|
const target = panel?.querySelector('[tabindex="0"]');
|
|
@@ -37,16 +38,17 @@ const NavRail = ({ ref, collapsed = false, className, children, 'data-testid': t
|
|
|
37
38
|
value: testId,
|
|
38
39
|
children: /*#__PURE__*/ jsx(NavRailContextProvider, {
|
|
39
40
|
value: {
|
|
40
|
-
|
|
41
|
+
mode
|
|
41
42
|
},
|
|
42
43
|
children: /*#__PURE__*/ jsx("nav", {
|
|
43
44
|
...props,
|
|
44
45
|
ref: composeRefs(internalRef, ref),
|
|
45
46
|
"aria-label": "Global navigation",
|
|
46
47
|
"data-slot": "nav-rail",
|
|
48
|
+
"data-mode": mode,
|
|
47
49
|
"data-testid": testId,
|
|
48
50
|
className: cn(navRailVariants({
|
|
49
|
-
|
|
51
|
+
mode
|
|
50
52
|
}), className),
|
|
51
53
|
children: children
|
|
52
54
|
})
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../../utils/cn.js";
|
|
3
3
|
import { useTestId } from "../../utils/testId.js";
|
|
4
|
+
import { navRailBodyVariants } from "./classes.js";
|
|
5
|
+
import { useNavRailContext } from "./NavRailContext.js";
|
|
4
6
|
const NavRailBody = ({ ref, className, children, ...props })=>{
|
|
7
|
+
const { mode } = useNavRailContext();
|
|
5
8
|
const testId = useTestId('body');
|
|
6
9
|
return /*#__PURE__*/ jsx("div", {
|
|
7
10
|
...props,
|
|
8
11
|
ref: ref,
|
|
9
12
|
"data-slot": "nav-rail-body",
|
|
10
13
|
"data-testid": testId,
|
|
11
|
-
className: cn(
|
|
14
|
+
className: cn(navRailBodyVariants({
|
|
15
|
+
mode
|
|
16
|
+
}), className),
|
|
12
17
|
children: children
|
|
13
18
|
});
|
|
14
19
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
export type NavRailMode = 'expanded' | 'collapsed' | 'compact';
|
|
1
2
|
interface NavRailContextValue {
|
|
2
|
-
|
|
3
|
+
mode: NavRailMode;
|
|
3
4
|
}
|
|
4
5
|
export declare const NavRailContextProvider: import("react").Provider<NavRailContextValue>;
|
|
5
6
|
export declare function useNavRailContext(): NavRailContextValue;
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../../utils/cn.js";
|
|
3
3
|
import { useTestId } from "../../utils/testId.js";
|
|
4
|
+
import { navRailFooterVariants } from "./classes.js";
|
|
5
|
+
import { useNavRailContext } from "./NavRailContext.js";
|
|
4
6
|
const NavRailFooter = ({ ref, className, children, ...props })=>{
|
|
7
|
+
const { mode } = useNavRailContext();
|
|
5
8
|
const testId = useTestId('footer');
|
|
6
9
|
return /*#__PURE__*/ jsx("div", {
|
|
7
10
|
...props,
|
|
8
11
|
ref: ref,
|
|
9
12
|
"data-slot": "nav-rail-footer",
|
|
10
13
|
"data-testid": testId,
|
|
11
|
-
className: cn(
|
|
14
|
+
className: cn(navRailFooterVariants({
|
|
15
|
+
mode
|
|
16
|
+
}), className),
|
|
12
17
|
children: children
|
|
13
18
|
});
|
|
14
19
|
};
|
|
@@ -8,5 +8,9 @@ export interface NavRailItemProps extends AnchorHTMLAttributes<HTMLAnchorElement
|
|
|
8
8
|
label: ReactNode;
|
|
9
9
|
shortcut?: string[];
|
|
10
10
|
active?: boolean;
|
|
11
|
+
/** Seats the icon on a soft plate, for the signed-in user's item at the foot of the rail. */
|
|
12
|
+
avatar?: boolean;
|
|
13
|
+
/** User photo for the plate (implies `avatar`). The icon stays underneath and returns if the photo fails to load. */
|
|
14
|
+
avatarSrc?: string;
|
|
11
15
|
}
|
|
12
16
|
export declare const NavRailItem: FC<NavRailItemProps>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Fragment, useRef } from "react";
|
|
2
|
+
import { Fragment, useRef, useState } from "react";
|
|
3
3
|
import { composeRefs } from "@radix-ui/react-compose-refs";
|
|
4
4
|
import { Slot, Slottable } from "@radix-ui/react-slot";
|
|
5
5
|
import { cn } from "../../utils/cn.js";
|
|
@@ -8,35 +8,61 @@ import { Kbd } from "../Kbd/index.js";
|
|
|
8
8
|
import { Tooltip } from "../Tooltip/index.js";
|
|
9
9
|
import { TooltipContent } from "../Tooltip/TooltipContent.js";
|
|
10
10
|
import { TooltipTrigger } from "../Tooltip/TooltipTrigger.js";
|
|
11
|
-
import { navRailItemVariants } from "./classes.js";
|
|
11
|
+
import { navRailItemCompactLabelClassName, navRailItemVariants } from "./classes.js";
|
|
12
12
|
import { useNavRailContext } from "./NavRailContext.js";
|
|
13
13
|
import { useShortcut } from "./useShortcut.js";
|
|
14
|
-
const NavRailItem = ({ ref, asChild = false, icon: Icon, label, shortcut, active = false, className, children, 'data-testid': testIdProp, ...props })=>{
|
|
15
|
-
const {
|
|
14
|
+
const NavRailItem = ({ ref, asChild = false, icon: Icon, label, shortcut, active = false, avatar = false, avatarSrc, className, children, 'data-testid': testIdProp, ...props })=>{
|
|
15
|
+
const { mode } = useNavRailContext();
|
|
16
16
|
const testId = useTestId('item', testIdProp);
|
|
17
17
|
const Comp = asChild ? Slot : 'a';
|
|
18
18
|
const internalRef = useRef(null);
|
|
19
|
+
const [failedSrc, setFailedSrc] = useState();
|
|
20
|
+
const photo = avatarSrc && avatarSrc !== failedSrc ? avatarSrc : void 0;
|
|
21
|
+
const hasPlate = avatar || void 0 !== avatarSrc;
|
|
22
|
+
const showsLabel = 'expanded' === mode || 'compact' === mode && !hasPlate;
|
|
23
|
+
const fallbackName = showsLabel || 'string' != typeof label ? void 0 : label;
|
|
19
24
|
useShortcut(shortcut, internalRef);
|
|
20
25
|
const element = /*#__PURE__*/ jsxs(Comp, {
|
|
21
26
|
...props,
|
|
22
27
|
ref: composeRefs(internalRef, ref),
|
|
28
|
+
"aria-label": props['aria-label'] ?? fallbackName,
|
|
23
29
|
"aria-current": active ? 'page' : void 0,
|
|
24
30
|
"data-slot": "nav-rail-item",
|
|
25
31
|
"data-testid": testId,
|
|
26
32
|
className: cn(navRailItemVariants({
|
|
27
|
-
|
|
33
|
+
mode,
|
|
34
|
+
active,
|
|
35
|
+
avatar: hasPlate
|
|
28
36
|
}), className),
|
|
29
37
|
children: [
|
|
30
38
|
/*#__PURE__*/ jsx("span", {
|
|
31
39
|
className: "flex shrink-0 items-center justify-center",
|
|
32
|
-
children: /*#__PURE__*/
|
|
40
|
+
children: hasPlate ? /*#__PURE__*/ jsxs("span", {
|
|
41
|
+
"data-slot": "nav-rail-item-avatar",
|
|
42
|
+
className: cn('-m-4 grid size-24 place-items-center overflow-hidden rounded-8 bg-states-primary-hover branded:bg-states-brand-hover branded:text-icon-brand *:col-start-1 *:row-start-1', !photo && 'border border-border-primary branded:border-border-brand'),
|
|
43
|
+
children: [
|
|
44
|
+
/*#__PURE__*/ jsx(Icon, {
|
|
45
|
+
size: "md"
|
|
46
|
+
}),
|
|
47
|
+
photo && /*#__PURE__*/ jsx("img", {
|
|
48
|
+
src: photo,
|
|
49
|
+
alt: "",
|
|
50
|
+
className: "size-full object-cover",
|
|
51
|
+
onError: ()=>setFailedSrc(photo)
|
|
52
|
+
})
|
|
53
|
+
]
|
|
54
|
+
}) : /*#__PURE__*/ jsx(Icon, {
|
|
33
55
|
size: "md"
|
|
34
56
|
})
|
|
35
57
|
}),
|
|
36
|
-
|
|
58
|
+
'expanded' === mode && /*#__PURE__*/ jsx("span", {
|
|
37
59
|
className: cn('truncate whitespace-nowrap transition-[opacity,width] duration-200 pl-8'),
|
|
38
60
|
children: label
|
|
39
61
|
}),
|
|
62
|
+
showsLabel && 'compact' === mode && /*#__PURE__*/ jsx("span", {
|
|
63
|
+
className: navRailItemCompactLabelClassName,
|
|
64
|
+
children: label
|
|
65
|
+
}),
|
|
40
66
|
/*#__PURE__*/ jsx(Slottable, {
|
|
41
67
|
children: children
|
|
42
68
|
})
|