Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(components): segmented-button #3879

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
805e077
feat(component): created initial layout of the segmented-button compo…
Oct 22, 2024
26b4ecd
fix(path): fixed the redirecting to localhost for style import (user …
Oct 23, 2024
bf4ec9f
chore(styles): the hover state was implemented
Oct 26, 2024
018d5f6
fix(styles): adjustments to the styles were made
Oct 26, 2024
481aa46
fix(component): fixed the overflow for the segmented button
Oct 28, 2024
9973efb
fix(docs): removed redundant code from docs
Oct 28, 2024
f1a1a9c
fix(styles): added display property on segmented-button label:last-of…
Oct 28, 2024
309dccf
chore(styles-playground): added the code to the styles playground
Oct 28, 2024
fbd79bb
chore(component): refactored the styles
alionazherdetska Nov 13, 2024
906d42c
chore(styles):refactored focus styles
alionazherdetska Nov 13, 2024
29b8c38
fix(typo): fixed the typo
alionazherdetska Nov 13, 2024
802295f
Merge branch 'main' into 3460-component-segmented-button
alionazherdetska Nov 13, 2024
457117f
chore(styles): refactored css component
alionazherdetska Nov 14, 2024
7d78942
chore(docs): added docs for segmented button
alionazherdetska Nov 14, 2024
42424c1
fix: code style
alionazherdetska Nov 14, 2024
0ae33d3
feat(segmented-button): add controls for default selection and label …
alionazherdetska Nov 14, 2024
070c167
feat(styles): add segmented-button styles
oliverschuerch Nov 15, 2024
4259ba0
Merge branch 'main' into 3460-component-segmented-button
oliverschuerch Nov 15, 2024
6fceb28
chore(styles): remove comments
oliverschuerch Nov 15, 2024
33ce386
fix(styles): fix issue reported by sonar
oliverschuerch Nov 15, 2024
cbca5a2
fix(styles): fix segemented-button mobile hover styles
oliverschuerch Nov 15, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getComponentStyleImports } from './styles-package-import-individual.sam

<p>
<small>*Make sure the `@swisspost/design-system-styles` package is already present in your project
or follow the [installation guidelines](http://localhost:9000/?path=/docs/e53e2de8-0bbf-4f70-babc-074c5466f700--docs).*</small>
or follow the [installation guidelines](/?path=/docs/e53e2de8-0bbf-4f70-babc-074c5466f700--docs).*</small>
</p>

To import all Design System styles:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Canvas, Controls, Meta } from '@storybook/blocks';
import * as SegmentedButtonStories from './segmented-button.stories';
import StylesPackageImport from '@/shared/styles-package-import.mdx';

<Meta of={SegmentedButtonStories} />

<div className="docs-title">
# Segmented Button

<nav>
<link-design of={JSON.stringify(SegmentedButtonStories)}></link-design>
</nav>
</div>

<div className="lead">
The segmented button is a single-select component.
It allows users to toggle between two or more content sections within the same area on the screen.
</div>

<Canvas sourceState="shown" of={SegmentedButtonStories.Default} />
<div className="hide-col-default">
<Controls of={SegmentedButtonStories.Default} />
</div>

<StylesPackageImport components={['segmented-button']} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Args, StoryObj } from '@storybook/web-components';
import { html } from 'lit';
import { MetaComponent } from '@root/types';

const meta: MetaComponent = {
id: '78509712-d45e-462c-bde3-405cfaff5421',
title: 'Components/Buttons/Segmented button',
tags: ['package:HTML'],
render: renderSegmentedButton,
parameters: {
badges: [],
design: {
type: 'figma',
url: 'https://www.figma.com/design/JIT5AdGYqv6bDRpfBPV8XR/Foundations-%26-Components-Next-Level?node-id=2864-83396&node-type=instance&m=dev',
},
},
args: {
name: 'five',
labels: ['Option 1', 'Option 2', 'Option 3', 'Option 4', 'Option 5'],
selected: 0, // Default selection for ease of testing
},
argTypes: {
name: {
name: 'Name',
description: 'Sets the name attribute for the segmented button component.',
control: { type: 'text' },
table: { category: 'General' },
},
labels: {
name: 'Labels',
description: 'Defines the labels for each option in the segmented button. Maximum of 10 options allowed.',
control: { type: 'object' },
table: { category: 'Content' },
validation: { maxLength: 10 },
},
selected: {
name: 'Selected',
description: 'Specifies the index of the default selected option.',
control: { type: 'number', min: 0, max: 9 },
table: { category: 'State' },
},
},
};

export default meta;

type Story = StoryObj;

function renderSegmentedButton(args: Args) {
const labelsArray = args.labels || [];
const selectedIndex = args.selected || 0;

return html`
<div class="segmented-button-container">
<fieldset class="segmented-button segmented-button-${labelsArray.length}">
${labelsArray.slice(0, 10).map(
(label, index) => html`
<input id="${args.name}-${index + 1}"
tabindex="-1"
name="${args.name}"
type="radio"
?checked="${index === selectedIndex}" />
<label for="${args.name}-${index + 1}" tabindex="0">${label}</label>
`
)}
</fieldset>
</div>
`;
}

export const Default: Story = {
args: {
labels: ['Option 1', 'Option 2', 'Option 3', 'Option 4', 'Option 5'],
selected: 0,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,3 @@ Our aim is to make things simple, trustworthy, and inclusive. We create easy-to-
</div>
))}
</div>

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class MigrationV45Component extends LitElement {
<a
aria-hidden="true"
tabindex="-1"
href="http://localhost:9000/?path=/docs/c23b1d0b-76b3-4e38-aa76-b10c29bb873f--docs#migration-from-v5-to-v6#migration-from-v4-to-v5"
href="/?path=/docs/c23b1d0b-76b3-4e38-aa76-b10c29bb873f--docs#migration-from-v5-to-v6#migration-from-v4-to-v5"
>
<post-icon name="2037"></post-icon>
</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class MigrationV56Component extends LitElement {
<a
aria-hidden="true"
tabindex="-1"
href="http://localhost:9000/?path=/docs/c23b1d0b-76b3-4e38-aa76-b10c29bb873f--docs#migration-from-v5-to-v6"
href="/?path=/docs/c23b1d0b-76b3-4e38-aa76-b10c29bb873f--docs#migration-from-v5-to-v6"
>
<post-icon name="2037"></post-icon>
</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class MigrationV67Component extends LitElement {
<a
aria-hidden="true"
tabindex="-1"
href="http://localhost:9000/?path=/docs/c23b1d0b-76b3-4e38-aa76-b10c29bb873f--docs#migration-from-v6-to-v7"
href="/?path=/docs/c23b1d0b-76b3-4e38-aa76-b10c29bb873f--docs#migration-from-v6-to-v7"
>
<post-icon name="2037"></post-icon>
</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class MigrationV78Component extends LitElement {
<a
aria-hidden="true"
tabindex="-1"
href="http://localhost:9000/?path=/docs/c23b1d0b-76b3-4e38-aa76-b10c29bb873f--docs#migration-from-v7-to-v8"
href="/?path=/docs/c23b1d0b-76b3-4e38-aa76-b10c29bb873f--docs#migration-from-v7-to-v8"
>
<post-icon name="2037"></post-icon>
</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class MigrationV89Component extends LitElement {
<a
aria-hidden="true"
tabindex="-1"
href="http://localhost:9000/?path=/docs/c23b1d0b-76b3-4e38-aa76-b10c29bb873f--docs#migration-from-v8-to-v9"
href="/?path=/docs/c23b1d0b-76b3-4e38-aa76-b10c29bb873f--docs#migration-from-v8-to-v9"
>
<post-icon name="2037"></post-icon>
</a>
Expand Down
27 changes: 27 additions & 0 deletions packages/styles/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,32 @@ <h1>Styles package playground</h1>

<!-- Place your component markup here -->
<button class="btn btn-primary">Primary button</button>

<div style="padding: 2rem">
<div class="segmented-button-container">
<fieldset class="segmented-button">
<label>
<input type="radio" name="{uuid}" />
The Good</label
>
<label>
<input type="radio" name="{uuid}" />
The Bad</label
>
<label>
<input type="radio" name="{uuid}" />
The Ugly</label
>
<label>
<input type="radio" name="{uuid}" />
The Beauty</label
>
<label>
<input type="radio" name="{uuid}" />
The Small</label
>
</fieldset>
</div>
</div>
</body>
</html>
1 change: 1 addition & 0 deletions packages/styles/src/elements/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
@use 'list-bullet';
@use 'paragraph';
@use 'fieldset-legend';
@use 'segmented-button';
@use 'list';
@use 'heading';
178 changes: 178 additions & 0 deletions packages/styles/src/elements/segmented-button.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
@use '../functions/tokens';
@use '../tokens/components';
@use '../mixins/utilities';

tokens.$default-map: components.$post-segmented-button;

$post-segmented-button-max-count: 8;

.segmented-button-container {
container-name: segmented-container;
container-type: inline-size;
}

.segmented-button {
display: flex;
gap: tokens.get('button-segmented-gap-inline');
align-items: stretch;
outline: tokens.get('button-segmented-border-width') solid
tokens.get('button-segmented-enabled-border');
outline-offset: calc(tokens.get('button-segmented-border-width') * -1);
background-color: tokens.get('button-segmented-enabled-bg');
border-radius: tokens.get('button-segmented-horizontal-border-radius');
box-shadow: tokens.get('button-segmented-elevation');

label {
flex: 0 1 100%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
z-index: 2;
padding-inline: tokens.get('button-segmented-padding-inline');
height: tokens.get('button-segmented-elements-height');
border-radius: inherit;
cursor: pointer;
font-weight: tokens.get('button-segmented-font-weight');
color: tokens.get('button-segmented-enabled-fg');
text-align: center;
line-height: 1.2;
overflow-wrap: anywhere;

input {
appearance: none !important;
user-select: none;
pointer-events: none;
position: absolute;
inset: 0;
margin: 0;
padding: 0;
border: tokens.get('button-segmented-border-width') solid transparent;
border-radius: inherit;
@include utilities.focus-style();
}

&:last-of-type {
z-index: 1;
}

&:not(:last-of-type) {
&:hover {
z-index: 3;
}
}

&:hover {
color: tokens.get('button-segmented-hover-fg');

input {
border-color: tokens.get('button-segmented-hover-border');
}
}

&:has(input:checked) {
color: tokens.get('button-segmented-selected-fg');
cursor: default;

~ :last-of-type,
&:last-of-type {
&::after {
display: block;
content: '';
position: absolute;
inset: 0;
z-index: -1;
background-color: tokens.get('button-segmented-selected-bg');
border: tokens.get('button-segmented-border-width') solid
tokens.get('button-segmented-selected-border');
border-radius: inherit;
transition: transform 0.4s cubic-bezier(0.25, 1.4, 0.5, 0.9);
}
}

&:hover {
input {
border-color: tokens.get('button-segmented-selected-border');
}
}
}

@for $i from 1 through $post-segmented-button-max-count {
&:nth-last-of-type(#{$i + 1}):has(input:checked) ~ label:last-of-type::after {
transform: translateX(calc($i * -100% - $i * tokens.get('button-segmented-gap-inline')));
}
}
}
}

@container segmented-container (max-width: 600px) {
.segmented-button {
flex-direction: column;
border-radius: tokens.get('button-segmented-vertical-border-radius');

label {
flex: 1 0 auto;
border-radius: 0;

&:first-of-type {
border-top-left-radius: inherit;
border-top-right-radius: inherit;

&:has(input:checked) ~ :last-of-type::after {
border-top-left-radius: tokens.get('button-segmented-vertical-border-radius');
border-top-right-radius: tokens.get('button-segmented-vertical-border-radius');
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}

&:last-of-type {
border-bottom-left-radius: inherit;
border-bottom-right-radius: inherit;

&::after {
border-radius: 0;
}

&:has(input:checked)::after {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-left-radius: tokens.get('button-segmented-vertical-border-radius');
border-bottom-right-radius: tokens.get('button-segmented-vertical-border-radius');
}
}

&:not(:first-of-type, :last-of-type):has(input:checked) ~ :last-of-type::after {
border-radius: 0;
}

input {
border-top-color: tokens.get('button-segmented-enabled-border');
}

&:not(:first-of-type) {
input {
top: calc(tokens.get('button-segmented-border-width') * -1);
}
}

&:not(:last-of-type) {
input {
bottom: calc(tokens.get('button-segmented-gap-inline') * -1);
}
}

&:hover {
input {
border-color: tokens.get('button-segmented-hover-border');
}
}

@for $i from 1 through $post-segmented-button-max-count {
&:nth-last-of-type(#{$i + 1}):has(input:checked) ~ label:last-of-type::after {
transform: translateY(calc($i * -100% - $i * tokens.get('button-segmented-gap-inline')));
}
}
}
}
}