Skip to content

Commit 5a95667

Browse files
emogoogcopybara-github
authored andcommitted
feat(labs): add navigation rail web component suite
PiperOrigin-RevId: 999452943
1 parent e3471c0 commit 5a95667

9 files changed

Lines changed: 1586 additions & 0 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//
2+
// Copyright 2026 Google LLC
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
6+
@mixin root {
7+
// LINT.IfChange
8+
--container-color: var(--md-sys-color-surface);
9+
--container-elevation: var(--md-sys-elevation-shadow-0);
10+
--container-shape: var(--md-sys-shape-corner-none);
11+
--container-vertical-padding: var(--md-sys-space-500);
12+
--container-width: 96px;
13+
--expanded-container-color: var(--md-sys-color-surface);
14+
--expanded-container-width-maximum: 360px;
15+
--expanded-container-width-minimum: 220px;
16+
--expanded-modal-container-color: var(--md-sys-color-surface-container);
17+
--header-space-minimum: 40px;
18+
--item-active-focused-icon-color: var(--md-sys-color-on-secondary-container);
19+
--item-active-focused-label-text-color: var(--md-sys-color-secondary);
20+
--item-active-hovered-icon-color: var(--md-sys-color-on-secondary-container);
21+
--item-active-hovered-label-text-color: var(--md-sys-color-secondary);
22+
--item-active-icon-color: var(--md-sys-color-on-secondary-container);
23+
--item-active-indicator-color: var(--md-sys-color-secondary-container);
24+
--item-active-indicator-height: 32px;
25+
--item-active-indicator-shape: var(--md-sys-shape-corner-full);
26+
--item-active-indicator-width: 56px;
27+
--item-active-label-text-color: var(--md-sys-color-secondary);
28+
--item-active-pressed-icon-color: var(--md-sys-color-on-secondary-container);
29+
--item-active-pressed-label-text-color: var(--md-sys-color-secondary);
30+
--item-active-state-layer-color: var(--md-sys-color-on-secondary-container);
31+
--item-container-height: 64px;
32+
--item-container-vertical-space: 4px;
33+
--item-full-width-leading-space: 20px;
34+
--item-full-width-trailing-space: 20px;
35+
--item-horizontal-active-indicator-height: 56px;
36+
--item-horizontal-icon-label-space: 8px;
37+
--item-icon-size: 24px;
38+
--item-inactive-focused-icon-color: var(--md-sys-color-on-surface);
39+
--item-inactive-focused-label-text-color: var(--md-sys-color-on-surface);
40+
--item-inactive-hovered-icon-color: var(--md-sys-color-on-surface);
41+
--item-inactive-hovered-label-text-color: var(--md-sys-color-on-surface);
42+
--item-inactive-icon-color: var(--md-sys-color-on-surface-variant);
43+
--item-inactive-label-text-color: var(--md-sys-color-on-surface-variant);
44+
--item-inactive-pressed-icon-color: var(--md-sys-color-on-surface);
45+
--item-inactive-pressed-label-text-color: var(--md-sys-color-on-surface);
46+
--item-inactive-state-layer-color: var(--md-sys-color-on-secondary-container);
47+
--item-label-text: var(--md-sys-typescale-label-md);
48+
--item-label-text-axes: var(--md-sys-typescale-label-md-axes);
49+
--item-label-text-tracking: var(--md-sys-typescale-label-md-tracking);
50+
--item-vertical-icon-label-space: 4px;
51+
--item-vertical-leading-space: 16px;
52+
--item-vertical-trailing-space: 16px;
53+
--modal-container-elevation: var(--md-sys-elevation-shadow-2);
54+
--modal-container-shape: var(--md-sys-shape-corner-lg);
55+
--motion-duration: var(--md-sys-motion-duration-short4);
56+
--motion-easing: var(--md-sys-motion-easing-emphasized);
57+
// LINT.ThenChange(nav-rail-element.ts) keep in sync with `@cssprop` jsdoc tags
58+
}
59+
60+
@mixin expanded {
61+
--container-color: var(--expanded-container-color);
62+
--item-active-focused-label-text-color: var(
63+
--md-sys-color-on-secondary-container
64+
);
65+
--item-active-hovered-label-text-color: var(
66+
--md-sys-color-on-secondary-container
67+
);
68+
--item-active-indicator-height: var(
69+
--item-horizontal-active-indicator-height
70+
);
71+
--item-active-indicator-width: auto;
72+
--item-active-label-text-color: var(--md-sys-color-on-secondary-container);
73+
--item-active-pressed-label-text-color: var(
74+
--md-sys-color-on-secondary-container
75+
);
76+
--item-container-height: var(--item-horizontal-active-indicator-height);
77+
--item-container-vertical-space: 0px;
78+
--item-label-text: var(--md-sys-typescale-label-lg);
79+
--item-label-text-axes: var(--md-sys-typescale-label-lg-axes);
80+
--item-label-text-tracking: var(--md-sys-typescale-label-lg-tracking);
81+
}
82+
83+
@mixin modal {
84+
--container-color: var(--expanded-modal-container-color);
85+
--container-elevation: var(--modal-container-elevation);
86+
--container-shape: var(--modal-container-shape);
87+
}
88+
89+
@mixin vertical-item {
90+
--item-active-focused-label-text-color: var(--md-sys-color-secondary);
91+
--item-active-hovered-label-text-color: var(--md-sys-color-secondary);
92+
--item-active-indicator-height: 32px;
93+
--item-active-indicator-width: 56px;
94+
--item-active-label-text-color: var(--md-sys-color-secondary);
95+
--item-active-pressed-label-text-color: var(--md-sys-color-secondary);
96+
--item-container-height: 64px;
97+
--item-container-vertical-space: 4px;
98+
--item-label-text: var(--md-sys-typescale-label-md);
99+
--item-label-text-axes: var(--md-sys-typescale-label-md-axes);
100+
--item-label-text-tracking: var(--md-sys-typescale-label-md-tracking);
101+
}
102+
103+
@mixin horizontal-item {
104+
--item-active-focused-label-text-color: var(
105+
--md-sys-color-on-secondary-container
106+
);
107+
--item-active-hovered-label-text-color: var(
108+
--md-sys-color-on-secondary-container
109+
);
110+
--item-active-indicator-height: var(
111+
--item-horizontal-active-indicator-height
112+
);
113+
--item-active-indicator-width: auto;
114+
--item-active-label-text-color: var(--md-sys-color-on-secondary-container);
115+
--item-active-pressed-label-text-color: var(
116+
--md-sys-color-on-secondary-container
117+
);
118+
--item-container-height: var(--item-horizontal-active-indicator-height);
119+
--item-container-vertical-space: 0px;
120+
--item-label-text: var(--md-sys-typescale-label-lg);
121+
--item-label-text-axes: var(--md-sys-typescale-label-lg-axes);
122+
--item-label-text-tracking: var(--md-sys-typescale-label-lg-tracking);
123+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import './material-collection.js';
8+
import './index.js';
9+
10+
import {
11+
type KnobTypesToKnobs,
12+
MaterialCollection,
13+
materialInitsToStoryInits,
14+
setUpDemo,
15+
} from './material-collection.js';
16+
import {type NavRailAlignment} from '@material/web/labs/gb/components/navrail/nav-rail.js';
17+
import {boolInput, Knob, selectDropdown} from './index.js';
18+
19+
import {stories, type StoryKnobs} from './stories.js';
20+
21+
const collection = new MaterialCollection<KnobTypesToKnobs<StoryKnobs>>(
22+
'Navigation Rail',
23+
[
24+
new Knob('expanded', {ui: boolInput()}),
25+
new Knob('modal', {ui: boolInput()}),
26+
new Knob('alignment', {
27+
ui: selectDropdown<NavRailAlignment>({
28+
options: [
29+
{value: 'top', label: 'Top'},
30+
{value: 'center', label: 'Center'},
31+
{value: 'bottom', label: 'Bottom'},
32+
],
33+
}),
34+
}),
35+
new Knob('showHeader', {ui: boolInput(), defaultValue: true}),
36+
new Knob('showBadges', {ui: boolInput(), defaultValue: true}),
37+
],
38+
);
39+
40+
collection.addStories(...materialInitsToStoryInits(stories));
41+
42+
setUpDemo(collection, {fonts: 'roboto', icons: 'material-symbols'});
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import '@material/web/labs/gb/components/fab/md-gb-fab.js';
8+
import '@material/web/labs/gb/components/iconbutton/md-gb-icon-button.js';
9+
import '@material/web/labs/gb/components/navrail/md-gb-nav-rail.js';
10+
import '@material/web/labs/gb/components/navrail/md-gb-nav-rail-item.js';
11+
import '@material/web/labs/gb/styles/icon/md-gb-icon.js';
12+
13+
import {type MaterialStoryInit} from './material-collection.js';
14+
import {type NavRailAlignment} from '@material/web/labs/gb/components/navrail/nav-rail.js';
15+
import {adoptStyles} from '@material/web/labs/gb/styles/adopt-styles.js';
16+
import {css, html, nothing} from 'lit';
17+
18+
import {styles as m3Styles} from '@material/web/labs/gb/styles/m3.css' with {type: 'css'}; // github-only
19+
// import {styles as m3Styles} from '@material/web/labs/gb/styles/m3.cssresult.js'; // google3-only
20+
21+
/** Knob types for navigation rail stories. */
22+
export interface StoryKnobs {
23+
alignment?: NavRailAlignment;
24+
expanded?: boolean;
25+
modal?: boolean;
26+
showBadges?: boolean;
27+
showHeader?: boolean;
28+
}
29+
30+
adoptStyles(document, [
31+
m3Styles,
32+
css`
33+
:root {
34+
--md-icon-font: 'Material Symbols Outlined';
35+
}
36+
`,
37+
]);
38+
39+
const styles = css`
40+
.demo-shell {
41+
background-color: var(--md-sys-color-surface-container-lowest);
42+
border: 1px solid var(--md-sys-color-outline-variant);
43+
border-radius: 16px;
44+
box-shadow: var(--md-sys-elevation-shadow-1);
45+
display: flex;
46+
height: 480px;
47+
max-width: 720px;
48+
overflow: hidden;
49+
width: 100%;
50+
}
51+
52+
.demo-content {
53+
align-items: center;
54+
color: var(--md-sys-color-on-surface-variant);
55+
display: flex;
56+
flex: 1;
57+
font: var(--md-sys-typescale-body-lg);
58+
justify-content: center;
59+
padding: 24px;
60+
}
61+
62+
.demo-row {
63+
display: flex;
64+
flex-wrap: wrap;
65+
gap: 24px;
66+
}
67+
`;
68+
69+
const playground: MaterialStoryInit<StoryKnobs> = {
70+
name: 'Playground',
71+
styles,
72+
render(knobs) {
73+
const alignment = knobs.alignment || 'top';
74+
const expanded = Boolean(knobs.expanded);
75+
const modal = Boolean(knobs.modal);
76+
const showBadges = Boolean(knobs.showBadges);
77+
const showHeader = Boolean(knobs.showHeader);
78+
79+
return html`
80+
<div class="demo-shell">
81+
<md-gb-nav-rail
82+
alignment=${alignment}
83+
?collapsible=${showHeader}
84+
?expanded=${expanded}
85+
?modal=${modal}
86+
aria-label="Primary navigation rail">
87+
${showHeader
88+
? html`
89+
<md-gb-icon-button
90+
slot="header"
91+
aria-label="Toggle navigation rail">
92+
<md-gb-icon>${expanded ? 'menu_open' : 'menu'}</md-gb-icon>
93+
</md-gb-icon-button>
94+
<md-gb-fab
95+
slot="header"
96+
color="tertiary-container"
97+
aria-label="Compose">
98+
<md-gb-icon>edit</md-gb-icon>
99+
<span>Compose</span>
100+
</md-gb-fab>
101+
`
102+
: nothing}
103+
<md-gb-nav-rail-item selected badge=${showBadges ? 'dot' : ''}>
104+
<md-gb-icon slot="icon">mail</md-gb-icon>
105+
Inbox
106+
</md-gb-nav-rail-item>
107+
<md-gb-nav-rail-item badge=${showBadges ? '4' : ''}>
108+
<md-gb-icon slot="icon">chat_bubble</md-gb-icon>
109+
Chat
110+
</md-gb-nav-rail-item>
111+
<md-gb-nav-rail-item badge=${showBadges ? '99+' : ''}>
112+
<md-gb-icon slot="icon">groups</md-gb-icon>
113+
Spaces
114+
</md-gb-nav-rail-item>
115+
<md-gb-nav-rail-item>
116+
<md-gb-icon slot="icon">videocam</md-gb-icon>
117+
Meet
118+
</md-gb-nav-rail-item>
119+
</md-gb-nav-rail>
120+
<div class="demo-content">Main application workspace</div>
121+
</div>
122+
`;
123+
},
124+
};
125+
126+
const allVariants: MaterialStoryInit<StoryKnobs> = {
127+
name: 'Collapsed, Expanded & Aligned',
128+
styles,
129+
render() {
130+
return html`
131+
<div class="demo-row">
132+
<div class="demo-shell">
133+
<md-gb-nav-rail aria-label="Collapsed navigation rail">
134+
<md-gb-icon-button
135+
slot="header"
136+
aria-label="Toggle navigation rail">
137+
<md-gb-icon>menu</md-gb-icon>
138+
</md-gb-icon-button>
139+
<md-gb-fab
140+
slot="header"
141+
color="tertiary-container"
142+
aria-label="Create">
143+
<md-gb-icon>add</md-gb-icon>
144+
<span>Create</span>
145+
</md-gb-fab>
146+
<md-gb-nav-rail-item selected>
147+
<md-gb-icon slot="icon">home</md-gb-icon>
148+
Home
149+
</md-gb-nav-rail-item>
150+
<md-gb-nav-rail-item badge="3">
151+
<md-gb-icon slot="icon">explore</md-gb-icon>
152+
Explore
153+
</md-gb-nav-rail-item>
154+
<md-gb-nav-rail-item badge="dot">
155+
<md-gb-icon slot="icon">library_music</md-gb-icon>
156+
Library
157+
</md-gb-nav-rail-item>
158+
<md-gb-nav-rail-item>
159+
<md-gb-icon slot="icon">settings</md-gb-icon>
160+
Settings
161+
</md-gb-nav-rail-item>
162+
</md-gb-nav-rail>
163+
<div class="demo-content">Collapsed Rail (Top Aligned)</div>
164+
</div>
165+
166+
<div class="demo-shell">
167+
<md-gb-nav-rail expanded modal aria-label="Expanded navigation rail">
168+
<md-gb-icon-button
169+
slot="header"
170+
aria-label="Toggle navigation rail">
171+
<md-gb-icon>menu_open</md-gb-icon>
172+
</md-gb-icon-button>
173+
<md-gb-fab
174+
slot="header"
175+
color="tertiary-container"
176+
aria-label="Compose">
177+
<md-gb-icon>edit</md-gb-icon>
178+
<span>Compose</span>
179+
</md-gb-fab>
180+
<md-gb-nav-rail-item selected>
181+
<md-gb-icon slot="icon">inbox</md-gb-icon>
182+
Inbox
183+
</md-gb-nav-rail-item>
184+
<md-gb-nav-rail-item badge="12">
185+
<md-gb-icon slot="icon">star</md-gb-icon>
186+
Starred
187+
</md-gb-nav-rail-item>
188+
<md-gb-nav-rail-item>
189+
<md-gb-icon slot="icon">send</md-gb-icon>
190+
Sent
191+
</md-gb-nav-rail-item>
192+
<md-gb-nav-rail-item>
193+
<md-gb-icon slot="icon">delete</md-gb-icon>
194+
Trash
195+
</md-gb-nav-rail-item>
196+
</md-gb-nav-rail>
197+
<div class="demo-content">Expanded Modal Rail</div>
198+
</div>
199+
200+
<div class="demo-shell">
201+
<md-gb-nav-rail
202+
collapsible
203+
alignment="center"
204+
aria-label="Center aligned navigation rail">
205+
<md-gb-nav-rail-item selected>
206+
<md-gb-icon slot="icon">dashboard</md-gb-icon>
207+
Overview
208+
</md-gb-nav-rail-item>
209+
<md-gb-nav-rail-item>
210+
<md-gb-icon slot="icon">analytics</md-gb-icon>
211+
Analytics
212+
</md-gb-nav-rail-item>
213+
<md-gb-nav-rail-item>
214+
<md-gb-icon slot="icon">person</md-gb-icon>
215+
Account
216+
</md-gb-nav-rail-item>
217+
</md-gb-nav-rail>
218+
<div class="demo-content">Center Aligned Rail</div>
219+
</div>
220+
</div>
221+
`;
222+
},
223+
};
224+
225+
/** Navigation Rail stories. */
226+
export const stories = [playground, allVariants];

0 commit comments

Comments
 (0)