Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions labs/gb/components/segmentedbutton/_segmented-button-tokens.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0
//

// Hand-authored from tokens/versions/latest/sass/_md-comp-outlined-segmented-button.scss.

@mixin set-root {
// LINT.IfChange
--container-height: 40px;
--container-shape: var(--md-sys-shape-corner-full);
--outline-color: var(--md-sys-color-outline);
--outline-width: 1px;
--touch-target-size: 48px; // EDIT: Material 48px minimum touch target (exceeds WCAG 2.5.8); no upstream design token exists
// LINT.ThenChange(segmented-button-set-element.ts) keep in sync with `@cssprop` jsdoc tags
}

@mixin dense-1 {
--container-height: 36px; // EDIT: density -1 reduces default 40px height by 4px per M3 density scale
}

@mixin dense-2 {
--container-height: 32px; // EDIT: density -2 reduces default 40px height by 8px per M3 density scale
}

@mixin dense-3 {
--container-height: 28px; // EDIT: density -3 reduces default 40px height by 12px per M3 density scale
}

@mixin root {
// LINT.IfChange
--container-color: transparent; // EDIT: unselected outlined segment has no background fill; transparent container matches M3 outlined button
--icon-color: var(--md-sys-color-on-surface);
--icon-label-space: 8px; // EDIT: 8px space between leading icon/checkmark and label text per M3 spec spacing
--icon-size: 18px;
--label-text: var(--md-sys-typescale-label-lg);
--label-text-axes: var(--md-sys-typescale-label-lg-axes);
--label-text-color: var(--md-sys-color-on-surface);
--label-text-tracking: var(--md-sys-typescale-label-lg-tracking);
--leading-space: 12px; // EDIT: 12px leading padding for selected and icon+label segments per M3 spec
--state-layer-color: var(--md-sys-color-on-surface);
--trailing-space: 12px; // EDIT: 12px trailing padding for selected and icon+label segments per M3 spec
// LINT.ThenChange(segmented-button-element.ts) keep in sync with `@cssprop` jsdoc tags
}

@mixin selected {
--container-color: var(--md-sys-color-secondary-container);
--icon-color: var(--md-sys-color-on-secondary-container);
--label-text-color: var(--md-sys-color-on-secondary-container);
--state-layer-color: var(--md-sys-color-on-secondary-container);
}

@mixin disabled {
--container-color: transparent;
--icon-color: hsl(from var(--md-sys-color-on-surface) h s l / 38%);
--label-text-color: hsl(from var(--md-sys-color-on-surface) h s l / 38%);
--outline-color: hsl(from var(--md-sys-color-on-surface) h s l / 12%);
--state-layer-color: transparent;
}
98 changes: 98 additions & 0 deletions labs/gb/components/segmentedbutton/demo/demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import './material-collection.js';
import './index.js';

import {
type KnobTypesToKnobs,
MaterialCollection,
materialInitsToStoryInits,
setUpDemo,
} from './material-collection.js';
import {type SegmentedButtonSelection} from '@material/web/labs/gb/components/segmentedbutton/segmented-button.js';
import {
boolInput,
Knob,
selectDropdown,
textInput,
} from './index.js';

import {stories, type StoryKnobs} from './stories.js';

const collection = new MaterialCollection<KnobTypesToKnobs<StoryKnobs>>(
'Segmented Button',
[
new Knob<SegmentedButtonSelection | undefined, 'selection'>('selection', {
defaultValue: 'single',
ui: selectDropdown<SegmentedButtonSelection>({
options: [
{value: 'single', label: 'Single'},
{value: 'multiple', label: 'Multiple'},
],
}),
}),
new Knob<'2' | '3' | '4' | '5' | undefined, 'segments'>('segments', {
defaultValue: '3',
ui: selectDropdown<'2' | '3' | '4' | '5'>({
options: [
{value: '2', label: '2'},
{value: '3', label: '3'},
{value: '4', label: '4'},
{value: '5', label: '5'},
],
}),
}),
new Knob<'label' | 'icon' | 'both' | undefined, 'content'>('content', {
defaultValue: 'label',
ui: selectDropdown<'label' | 'icon' | 'both'>({
options: [
{value: 'label', label: 'Label'},
{value: 'icon', label: 'Icon'},
{value: 'both', label: 'Both'},
],
}),
}),
new Knob<'0' | '-1' | '-2' | '-3' | undefined, 'density'>('density', {
defaultValue: '0',
ui: selectDropdown<'0' | '-1' | '-2' | '-3'>({
options: [
{value: '0', label: '0 (Default)'},
{value: '-1', label: '-1'},
{value: '-2', label: '-2'},
{value: '-3', label: '-3'},
],
}),
}),
new Knob('disabled', {
ui: boolInput(),
}),
new Knob<
'none' | '0' | '1' | '2' | '3' | '4' | undefined,
'disabledSegment'
>('disabledSegment', {
defaultValue: 'none',
ui: selectDropdown<'none' | '0' | '1' | '2' | '3' | '4'>({
options: [
{value: 'none', label: 'None'},
{value: '0', label: 'First'},
{value: '1', label: 'Second'},
{value: '2', label: 'Third'},
{value: '3', label: 'Fourth'},
{value: '4', label: 'Fifth'},
],
}),
}),
new Knob('ariaLabel', {
ui: textInput(),
defaultValue: 'Select option',
}),
],
);

collection.addStories(...materialInitsToStoryInits(stories));

setUpDemo(collection, {fonts: 'roboto', icons: 'material-symbols'});
Loading
Loading