Skip to content

Commit 33d8df2

Browse files
asynclizcopybara-github
authored andcommitted
feat(labs): add expressive list utility class component
PiperOrigin-RevId: 901281224
1 parent 053e6d8 commit 33d8df2

File tree

8 files changed

+942
-3
lines changed

8 files changed

+942
-3
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//
2+
// Copyright 2026 Google LLC
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
6+
@mixin root {
7+
--container-shape: var(--md-sys-shape-corner-lg);
8+
--gap: 0;
9+
}
10+
11+
@mixin segmented {
12+
--gap: 2px;
13+
}
14+
15+
@mixin item {
16+
--container-height: 56px;
17+
--container-color: transparent;
18+
--container-shape: var(--md-sys-shape-corner-xs);
19+
--label-text-color: var(--md-sys-color-on-surface);
20+
--label-text: var(--md-sys-typescale-body-lg);
21+
--label-text-tracking: var(--md-sys-typescale-body-lg-tracking);
22+
--leading-space: 16px;
23+
--trailing-space: 16px;
24+
--between-space: 12px;
25+
--top-space: 10px;
26+
--bottom-space: 10px;
27+
--avatar-size: 40px;
28+
--avatar-shape: var(--md-sys-shape-corner-full);
29+
--avatar-color: var(--md-sys-color-primary-container);
30+
--avatar-label: var(--md-sys-typescale-title-md);
31+
--avatar-label-tracking: var(--md-sys-typescale-title-md-tracking);
32+
--avatar-label-color: var(--md-sys-color-on-primary-container);
33+
--leading-icon-color: var(--md-sys-color-on-surface-variant);
34+
--leading-icon-size: 20px;
35+
--trailing-icon-color: var(--md-sys-color-on-surface-variant);
36+
--trailing-icon-size: 20px;
37+
--overline: var(--md-sys-typescale-label-sm);
38+
--overline-tracking: var(--md-sys-typescale-label-sm-tracking);
39+
--overline-color: var(--md-sys-color-on-surface-variant);
40+
--supporting-text: var(--md-sys-typescale-body-md);
41+
--supporting-text-tracking: var(--md-sys-typescale-body-md-tracking);
42+
--supporting-text-color: var(--md-sys-color-on-surface-variant);
43+
--trailing-supporting-text: var(--md-sys-typescale-label-sm);
44+
--trailing-supporting-text-tracking: var(
45+
--md-sys-typescale-label-sm-tracking
46+
);
47+
--trailing-supporting-text-color: var(--md-sys-color-on-surface-variant);
48+
}
49+
50+
@mixin item-segmented {
51+
--container-color: var(--md-sys-color-surface);
52+
}
53+
54+
@mixin item-hovered {
55+
--container-shape: var(--md-sys-shape-corner-md);
56+
--leading-icon-color: var(--md-sys-color-on-surface);
57+
--trailing-icon-color: var(--md-sys-color-on-surface);
58+
}
59+
60+
@mixin item-focused {
61+
--container-shape: var(--md-sys-shape-corner-lg);
62+
--leading-icon-color: var(--md-sys-color-on-surface);
63+
--trailing-icon-color: var(--md-sys-color-on-surface);
64+
}
65+
66+
@mixin item-pressed {
67+
--container-shape: var(--md-sys-shape-corner-lg);
68+
--leading-icon-color: var(--md-sys-color-on-surface);
69+
--trailing-icon-color: var(--md-sys-color-on-surface);
70+
}
71+
72+
@mixin item-selected {
73+
--container-shape: var(--md-sys-shape-corner-lg);
74+
--container-color: var(--md-sys-color-secondary-container);
75+
--label-text-color: var(--md-sys-color-on-secondary-container);
76+
--leading-icon-color: var(--md-sys-color-on-secondary-container);
77+
--trailing-icon-color: var(--md-sys-color-on-secondary-container);
78+
--overline-color: var(--md-sys-color-on-secondary-container);
79+
--supporting-text-color: var(--md-sys-color-on-secondary-container);
80+
--trailing-supporting-text-color: var(
81+
--md-sys-color-on-secondary-container
82+
);
83+
}
84+
85+
@mixin item-disabled {
86+
--container-shape: var(--md-sys-shape-corner-xs);
87+
--container-color: hsl(from var(--md-sys-color-on-surface) h s l / 10%);
88+
--label-text-color: hsl(from var(--md-sys-color-on-surface) h s l / 38%);
89+
--leading-icon-color: hsl(
90+
from var(--md-sys-color-on-surface) h s l / 38%
91+
);
92+
--trailing-icon-color: hsl(
93+
from var(--md-sys-color-on-surface) h s l / 38%
94+
);
95+
--overline-color: hsl(from var(--md-sys-color-on-surface) h s l / 38%);
96+
--supporting-text-color: hsl(
97+
from var(--md-sys-color-on-surface) h s l / 38%
98+
);
99+
--trailing-supporting-text-color: hsl(
100+
from var(--md-sys-color-on-surface) h s l / 38%
101+
);
102+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
KnobTypesToKnobs,
12+
MaterialCollection,
13+
materialInitsToStoryInits,
14+
setUpDemo,
15+
} from './material-collection.js';
16+
import {boolInput, Knob} from './index.js';
17+
18+
import {stories, StoryKnobs} from './stories.js';
19+
20+
const collection = new MaterialCollection<KnobTypesToKnobs<StoryKnobs>>(
21+
'List',
22+
[
23+
new Knob('segmented', {
24+
ui: boolInput(),
25+
defaultValue: true,
26+
}),
27+
new Knob('nonInteractive', {
28+
ui: boolInput(),
29+
}),
30+
],
31+
);
32+
33+
collection.addStories(...materialInitsToStoryInits(stories));
34+
35+
setUpDemo(collection, {fonts: 'roboto', icons: 'material-symbols'});
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import '@material/web/icon/icon.js';
8+
import '@material/web/labs/gb/components/list/md-list.js';
9+
import '@material/web/labs/gb/components/list/md-list-item.js';
10+
11+
import {MaterialStoryInit} from './material-collection.js';
12+
import {styles as checkboxStyles} from '@material/web/labs/gb/components/checkbox/checkbox.cssresult.js';
13+
import {styles as focusRingStyles} from '@material/web/labs/gb/components/focus/focus-ring.cssresult.js';
14+
import {list, listItem} from '@material/web/labs/gb/components/list/list.js';
15+
import {styles as listStyles} from '@material/web/labs/gb/components/list/list.cssresult.js';
16+
import {styles as radioStyles} from '@material/web/labs/gb/components/radio/radio.cssresult.js';
17+
import {styles as rippleStyles} from '@material/web/labs/gb/components/ripple/ripple.cssresult.js';
18+
import {adoptStyles} from '@material/web/labs/gb/styles/adopt-styles.js';
19+
import {styles as m3Styles} from '@material/web/labs/gb/styles/m3.cssresult.js';
20+
import {css, html} from 'lit';
21+
22+
/** Knob types for list stories. */
23+
export interface StoryKnobs {
24+
segmented: boolean;
25+
nonInteractive: boolean;
26+
}
27+
28+
adoptStyles(document, [
29+
m3Styles,
30+
css`
31+
:root {
32+
--md-icon-font: 'Material Symbols Outlined';
33+
}
34+
`,
35+
]);
36+
37+
const styles = [
38+
focusRingStyles,
39+
rippleStyles,
40+
listStyles,
41+
checkboxStyles,
42+
radioStyles,
43+
css`
44+
.container {
45+
background-color: var(--md-sys-color-surface-container);
46+
border-radius: var(--md-sys-shape-corner-lg);
47+
padding: 2rem;
48+
border: 1px solid var(--md-sys-color-outline-variant);
49+
display: flex;
50+
flex-direction: column;
51+
gap: 1rem;
52+
}
53+
`,
54+
];
55+
56+
const playground: MaterialStoryInit<StoryKnobs> = {
57+
name: 'Playground',
58+
styles,
59+
render(knobs) {
60+
return html`
61+
<div class="container" style="width: 360px;">
62+
<md-list ?segmented=${knobs.segmented}>
63+
<md-list-item ?static=${knobs.nonInteractive}>
64+
Basic Item
65+
</md-list-item>
66+
<md-list-item ?static=${knobs.nonInteractive}>
67+
<md-icon slot="leading">star</md-icon>
68+
With Leading Icon
69+
</md-list-item>
70+
<md-list-item ?static=${knobs.nonInteractive}>
71+
<span slot="avatar">A</span>
72+
With Avatar & Supporting Text
73+
<span slot="supporting-text">Supporting text goes here</span>
74+
</md-list-item>
75+
<md-list-item
76+
?static=${knobs.nonInteractive}
77+
style="align-items: start;">
78+
<md-icon slot="leading">image</md-icon>
79+
<span slot="overline">Overline text</span>
80+
Complex Item
81+
<span slot="supporting-text">
82+
With overline, support text, and two icons
83+
</span>
84+
<span slot="trailing-text">100+</span>
85+
<md-icon slot="trailing">chevron_right</md-icon>
86+
</md-list-item>
87+
<md-list-item ?static=${knobs.nonInteractive} checked>
88+
<md-icon slot="leading">check</md-icon>
89+
Selected Item
90+
</md-list-item>
91+
<md-list-item ?static=${knobs.nonInteractive} disabled>
92+
<md-icon slot="leading">block</md-icon>
93+
Disabled Item
94+
<span slot="supporting-text">This item is disabled</span>
95+
</md-list-item>
96+
</md-list>
97+
</div>
98+
`;
99+
},
100+
};
101+
102+
const staticList: MaterialStoryInit<StoryKnobs> = {
103+
name: 'Non-interactive',
104+
styles,
105+
render(knobs) {
106+
return html`
107+
<div class="container" style="width: 360px;">
108+
<md-list ?segmented=${knobs.segmented}>
109+
<md-list-item static>
110+
<md-icon slot="leading">developer_board</md-icon>
111+
The first computer (ENIAC)
112+
<span slot="trailing-text">Feb 14, 1946</span>
113+
</md-list-item>
114+
<md-list-item static>
115+
<md-icon slot="leading">satellite_alt</md-icon>
116+
Sputnik launched into space
117+
<span slot="trailing-text">Oct 4, 1957</span>
118+
</md-list-item>
119+
<md-list-item static>
120+
<md-icon slot="leading">rocket_launch</md-icon>
121+
The Apollo 11 moon landing
122+
<span slot="trailing-text">Jul 20, 1969</span>
123+
</md-list-item>
124+
<md-list-item static>
125+
<md-icon slot="leading">moon_stars</md-icon>
126+
Artemis 2 crewed lunar flyby
127+
<span slot="trailing-text">Apr 1, 2026</span>
128+
</md-list-item>
129+
</md-list>
130+
</div>
131+
`;
132+
},
133+
};
134+
135+
const singleAction: MaterialStoryInit<StoryKnobs> = {
136+
name: 'Single-action',
137+
styles: [
138+
...styles,
139+
css`
140+
.img {
141+
width: 40px;
142+
height: 40px;
143+
border-radius: 8px;
144+
background-color: var(--md-sys-color-tertiary-container);
145+
}
146+
`,
147+
],
148+
render(knobs) {
149+
return html`
150+
<div class="container" style="width: 360px;">
151+
<md-list ?segmented=${knobs.segmented}>
152+
<md-list-item>
153+
<div class="img" slot="avatar"></div>
154+
Festivals
155+
<span slot="supporting-text">Food, music, arts, community...</span>
156+
<span slot="trailing-text">May 8</span>
157+
</md-list-item>
158+
<md-list-item>
159+
<div class="img" slot="avatar"></div>
160+
Arts
161+
<span slot="supporting-text">
162+
Literature, games, music, physical...
163+
</span>
164+
<span slot="trailing-text">May 8</span>
165+
</md-list-item>
166+
<md-list-item>
167+
<div class="img" slot="avatar"></div>
168+
Family & friends
169+
<span slot="supporting-text">
170+
The relationships that bring and bind...
171+
</span>
172+
<span slot="trailing-text">May 8</span>
173+
</md-list-item>
174+
</md-list>
175+
</div>
176+
`;
177+
},
178+
};
179+
180+
// TODO: add multi-action list examples
181+
182+
const singleSelect: MaterialStoryInit<StoryKnobs> = {
183+
name: 'Single-select',
184+
styles,
185+
render(knobs) {
186+
return html`
187+
<div class="container" style="width: 360px;">
188+
<select class="list-select" size="3">
189+
<div class="${list(knobs)}">
190+
<option class="${listItem()}">
191+
<div class="list-item-leading list-item-radio radio"></div>
192+
List item 1
193+
</option>
194+
<option class="${listItem()}" selected>
195+
<div class="list-item-leading list-item-radio radio"></div>
196+
List item 2
197+
</option>
198+
<option class="${listItem()}">
199+
<div class="list-item-leading list-item-radio radio"></div>
200+
List item 3
201+
</option>
202+
</div>
203+
</select>
204+
</div>
205+
`;
206+
},
207+
};
208+
209+
const multiSelect: MaterialStoryInit<StoryKnobs> = {
210+
name: 'Multi-select',
211+
styles,
212+
render(knobs) {
213+
return html`
214+
<div class="container" style="width: 360px;">
215+
<select class="list-select" multiple size="3">
216+
<div class="${list(knobs)}">
217+
<option class="${listItem()}">
218+
<div class="list-item-leading list-item-checkbox checkbox"></div>
219+
List item 1
220+
</option>
221+
<option class="${listItem()}" selected>
222+
<div class="list-item-leading list-item-checkbox checkbox"></div>
223+
List item 2
224+
</option>
225+
<option class="${listItem()}">
226+
<div class="list-item-leading list-item-checkbox checkbox"></div>
227+
List item 3
228+
</option>
229+
</div>
230+
</select>
231+
</div>
232+
`;
233+
},
234+
};
235+
236+
/** List stories. */
237+
export const stories = [
238+
playground,
239+
singleAction,
240+
singleSelect,
241+
multiSelect,
242+
staticList,
243+
];

0 commit comments

Comments
 (0)