Skip to content

Commit

Permalink
feat(sbb-checkbox, sbb-radio-button): split into regular and panel va…
Browse files Browse the repository at this point in the history
…riants (#2552)

Closes #2395 

BREAKING CHANGE: `sbb-selection-panel` has been renamed to `sbb-selection-expansion-panel`. The `sbb-checkbox` and `sbb-radio-button` components cannot be used anymore with `sbb-selection-expansion-panel` (does not apply for cases where they are slotted inside the `content` slot). As a replacement, we introduce the new components `sbb-checkbox-panel` and `sbb-radio-button-panel`, which could also be used standalone in cases where there is no content. `sbb-checkbox-group` and `sbb-radio-button-group` also support the panel variants.
How to migrate?
- Rename usages of `sbb-selection-panel` to `sbb-selection-expansion-panel`.
- Inside the `sbb-selection-expansion-panel`, replace `sbb-checkbox` with `sbb-checkbox-panel` and `sbb-radio-button` with `sbb-radio-button-panel` (does not apply for cases where they are slotted inside the `content` slot of the `sbb-selection-expansion-panel`)
- In cases where there was no content (slot), don't use `sbb-selection-panel`/`sbb-selection-expansion-panel` anymore, but directly use `sbb-checkbox-panel` or `sbb-radio-button-panel`.

---------

Co-authored-by: Davide Mininni <[email protected]>
Co-authored-by: Jeremias Peier <[email protected]>
  • Loading branch information
3 people authored Jun 14, 2024
1 parent 73d585e commit 2ad13f4
Show file tree
Hide file tree
Showing 62 changed files with 4,395 additions and 2,866 deletions.
4 changes: 2 additions & 2 deletions src/elements/card/card-badge/readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The `sbb-card-badge` can contain some information like prices or discounts,
and can be used in [sbb-card](/docs/elements-sbb-card-sbb-card--docs) or
[sbb-selection-panel](/docs/elements-sbb-selection-panel--docs).
and can be used in [sbb-card](/docs/components-sbb-card-sbb-card--docs) or
[sbb-selection-expansion-panel](/docs/components-sbb-selection-expansion-panel--docs).

To achieve the correct spacing between elements inside the card badge, we recommend to use `span`-elements.
All content parts are presented with a predefined gap in between.
Expand Down
2 changes: 2 additions & 0 deletions src/elements/checkbox.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './checkbox/checkbox.js';
export * from './checkbox/checkbox-group.js';
export * from './checkbox/checkbox-panel.js';
export * from './checkbox/common.js';
21 changes: 16 additions & 5 deletions src/elements/checkbox/checkbox-group/checkbox-group.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,21 @@ $breakpoints: 'zero', 'micro', 'small', 'medium', 'large', 'wide', 'ultra';
--sbb-checkbox-group-orientation: column;
--sbb-checkbox-group-width: 100%;
--sbb-checkbox-group-checkbox-width: 100%;

::slotted(sbb-checkbox-panel) {
width: 100%;
}
}

:host([data-has-selection-panel]) {
:host([data-has-panel]) {
--sbb-checkbox-group-width: 100%;

::slotted(sbb-checkbox-panel) {
flex: auto;
}
}

:host([data-has-selection-panel][orientation='vertical']) {
:host([data-has-panel][orientation='vertical']) {
--sbb-checkbox-group-gap: var(--sbb-spacing-fixed-2x) var(--sbb-spacing-fixed-4x);
}

Expand All @@ -38,11 +46,14 @@ $breakpoints: 'zero', 'micro', 'small', 'medium', 'large', 'wide', 'ultra';
// horizontal-from overrides orientation vertical
:host([orientation='vertical'][horizontal-from='#{$breakpoint}']) {
@include horizontal-orientation;

// We need to unset the 100% width of the vertical mode if it starts to be horizontal
::slotted(sbb-checkbox-panel) {
width: initial;
}
}

:host(
[orientation='vertical'][horizontal-from='#{$breakpoint}']:not([data-has-selection-panel])
) {
:host([orientation='vertical'][horizontal-from='#{$breakpoint}']:not([data-has-panel])) {
--sbb-checkbox-group-width: max-content;
}
}
Expand Down
133 changes: 100 additions & 33 deletions src/elements/checkbox/checkbox-group/checkbox-group.stories.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { withActions } from '@storybook/addon-actions/decorator';
import type { InputType } from '@storybook/types';
import type { Meta, StoryObj, ArgTypes, Args, Decorator } from '@storybook/web-components';
import type { ArgTypes, Args, Decorator, Meta, StoryObj } from '@storybook/web-components';
import type { TemplateResult } from 'lit';
import { html, nothing } from 'lit';
import { styleMap } from 'lit/directives/style-map.js';
import { styleMap, type StyleInfo } from 'lit/directives/style-map.js';

import { sbbSpread } from '../../../storybook/helpers/spread.js';
import type { SbbCheckboxElement } from '../checkbox.js';
Expand All @@ -12,19 +12,40 @@ import readme from './readme.md?raw';

import './checkbox-group.js';
import '../checkbox.js';
import '../checkbox-panel.js';
import '../../form-error.js';
import '../../icon.js';
import '../../card/card-badge.js';

const longLabelText = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer enim elit, ultricies in tincidunt
quis, mattis eu quam. Nulla sit amet lorem fermentum, molestie nunc ut, hendrerit risus. Vestibulum rutrum elit et
lacus sollicitudin, quis malesuada lorem vehicula. Suspendisse at augue quis tellus vulputate tempor. Vivamus urna
velit, varius nec est ac, mollis efficitur lorem. Quisque non nisl eget massa interdum tempus. Praesent vel feugiat
metus.`;

const suffixStyle: Readonly<StyleInfo> = {
display: 'flex',
alignItems: 'center',
};

const cardBadge = (): TemplateResult => html`<sbb-card-badge>%</sbb-card-badge>`;

const suffixAndSubtext = (): TemplateResult => html`
<span slot="subtext">Subtext</span>
<span slot="suffix" style="margin-inline-start: auto;">
<span style=${styleMap(suffixStyle)}>
<sbb-icon name="diamond-small" style="margin-inline: var(--sbb-spacing-fixed-2x);"></sbb-icon>
<span class="sbb-text-m sbb-text--bold">CHF 40.00</span>
</span>
</span>
${cardBadge()}
`;

const checkboxes = (
checked: boolean,
disabledSingle: boolean,
iconName: string,
iconPlacement: string,
iconPlacement: 'start' | 'end',
label: string,
): TemplateResult => html`
<sbb-checkbox
Expand All @@ -48,6 +69,20 @@ const checkboxes = (
</sbb-checkbox>
`;

const checkboxPanels = (
checked: boolean,
disabledSingle: boolean,
label: string,
): TemplateResult => html`
<sbb-checkbox-panel value="checkbox-1" ?checked=${checked}>
${label} 1 ${suffixAndSubtext()}</sbb-checkbox-panel
>
<sbb-checkbox-panel value="checkbox-2" ?disabled=${disabledSingle}>
${label} 2 ${suffixAndSubtext()}
</sbb-checkbox-panel>
<sbb-checkbox-panel value="checkbox-3"> ${label} 3 ${suffixAndSubtext()} </sbb-checkbox-panel>
`;

const DefaultTemplate = ({
checked,
disabledSingle,
Expand All @@ -61,6 +96,12 @@ const DefaultTemplate = ({
</sbb-checkbox-group>
`;

const PanelTemplate = ({ checked, disabledSingle, label, ...args }: Args): TemplateResult => html`
<sbb-checkbox-group ${sbbSpread(args)}>
${checkboxPanels(checked, disabledSingle, label)}
</sbb-checkbox-group>
`;

const ErrorMessageTemplate = ({
checked,
disabledSingle,
Expand Down Expand Up @@ -254,6 +295,10 @@ const basicArgTypes: ArgTypes = {
label,
checked,
disabledSingle,
};

const checkboxArgTypes: ArgTypes = {
...basicArgTypes,
iconName,
iconPlacement,
};
Expand All @@ -267,12 +312,16 @@ const basicArgs: Args = {
label: 'Label',
checked: true,
disabledSingle: false,
};

const checkboxArgs: Args = {
...basicArgs,
iconName: undefined,
iconPlacement: undefined,
};

const basicArgsVertical = {
...basicArgs,
const checkboxArgsVertical = {
...checkboxArgs,
orientation: orientation.options![1],
};

Expand All @@ -288,86 +337,104 @@ const iconEnd: Args = {

export const horizontal: StoryObj = {
render: DefaultTemplate,
argTypes: basicArgTypes,
args: { ...basicArgs },
argTypes: checkboxArgTypes,
args: { ...checkboxArgs },
};

export const vertical: StoryObj = {
render: DefaultTemplate,
argTypes: basicArgTypes,
args: { ...basicArgsVertical },
argTypes: checkboxArgTypes,
args: { ...checkboxArgsVertical },
};

export const verticalToHorizontal: StoryObj = {
render: DefaultTemplate,
argTypes: basicArgTypes,
args: { ...basicArgsVertical, 'horizontal-from': 'medium' },
argTypes: checkboxArgTypes,
args: { ...checkboxArgsVertical, 'horizontal-from': 'medium' },
};

export const horizontalSizeM: StoryObj = {
render: DefaultTemplate,
argTypes: basicArgTypes,
args: { ...basicArgs, size: 'm' },
argTypes: checkboxArgTypes,
args: { ...checkboxArgs, size: 'm' },
};

export const horizontalDisabled: StoryObj = {
render: DefaultTemplate,
argTypes: basicArgTypes,
args: { ...basicArgs, disabled: true, disabledSingle: true },
argTypes: checkboxArgTypes,
args: { ...checkboxArgs, disabled: true, disabledSingle: true },
};

export const verticalDisabled: StoryObj = {
render: DefaultTemplate,
argTypes: basicArgTypes,
args: { ...basicArgsVertical, disabled: true, disabledSingle: true },
argTypes: checkboxArgTypes,
args: { ...checkboxArgsVertical, disabled: true, disabledSingle: true },
};

export const horizontalIconStart: StoryObj = {
render: DefaultTemplate,
argTypes: basicArgTypes,
args: { ...basicArgs, ...iconStart },
argTypes: checkboxArgTypes,
args: { ...checkboxArgs, ...iconStart },
};

export const verticalIconStart: StoryObj = {
render: DefaultTemplate,
argTypes: basicArgTypes,
args: { ...basicArgsVertical, ...iconStart },
argTypes: checkboxArgTypes,
args: { ...checkboxArgsVertical, ...iconStart },
};

export const horizontalIconEnd: StoryObj = {
render: DefaultTemplate,
argTypes: basicArgTypes,
args: { ...basicArgs, ...iconEnd },
argTypes: checkboxArgTypes,
args: { ...checkboxArgs, ...iconEnd },
};

export const verticalIconEnd: StoryObj = {
render: DefaultTemplate,
argTypes: basicArgTypes,
args: { ...basicArgsVertical, ...iconEnd },
argTypes: checkboxArgTypes,
args: { ...checkboxArgsVertical, ...iconEnd },
};

export const verticalIconEndLongLabel: StoryObj = {
render: DefaultTemplate,
argTypes: basicArgTypes,
args: { ...basicArgsVertical, ...iconEnd, label: longLabelText },
argTypes: checkboxArgTypes,
args: { ...checkboxArgsVertical, ...iconEnd, label: longLabelText },
};

export const horizontalWithSbbFormError: StoryObj = {
render: ErrorMessageTemplate,
argTypes: basicArgTypes,
args: { ...basicArgs, required: true },
argTypes: checkboxArgTypes,
args: { ...checkboxArgs, required: true },
};

export const verticalWithSbbFormError: StoryObj = {
render: ErrorMessageTemplate,
argTypes: basicArgTypes,
args: { ...basicArgsVertical, required: true },
argTypes: checkboxArgTypes,
args: { ...checkboxArgsVertical, required: true },
};

export const indeterminateGroup: StoryObj = {
render: IndeterminateGroupTemplate,
argTypes: { ...basicArgTypes },
args: { ...basicArgsVertical, checked: undefined },
argTypes: { ...checkboxArgTypes },
args: { ...checkboxArgsVertical, checked: undefined },
};

export const horizontalPanel: StoryObj = {
render: PanelTemplate,
argTypes: basicArgTypes,
args: { ...basicArgs },
};

export const verticalPanel: StoryObj = {
render: PanelTemplate,
argTypes: basicArgTypes,
args: { ...basicArgs, orientation: orientation.options![1] },
};

export const verticalToHorizontalPanel: StoryObj = {
render: PanelTemplate,
argTypes: checkboxArgTypes,
args: { ...basicArgs, orientation: orientation.options![1], 'horizontal-from': 'medium' },
};

const meta: Meta = {
Expand Down
32 changes: 20 additions & 12 deletions src/elements/checkbox/checkbox-group/checkbox-group.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
import { html, LitElement } from 'lit';
import { LitElement, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';

import { getNextElementIndex, interactivityChecker, isArrowKeyPressed } from '../../core/a11y.js';
import { SbbConnectedAbortController, SbbSlotStateController } from '../../core/controllers.js';
import type { SbbHorizontalFrom, SbbOrientation } from '../../core/interfaces.js';
import { SbbDisabledMixin } from '../../core/mixins.js';
import type { SbbCheckboxElement, SbbCheckboxSize } from '../checkbox.js';
import type { SbbCheckboxPanelElement } from '../checkbox-panel.js';
import type { SbbCheckboxElement } from '../checkbox.js';
import type { SbbCheckboxSize } from '../common.js';

import style from './checkbox-group.scss?lit&inline';

Expand Down Expand Up @@ -35,9 +37,11 @@ export class SbbCheckboxGroupElement extends SbbDisabledMixin(LitElement) {
public orientation: SbbOrientation = 'horizontal';

/** List of contained checkbox elements. */
public get checkboxes(): SbbCheckboxElement[] {
return Array.from(this.querySelectorAll?.('sbb-checkbox') ?? []).filter(
(el: SbbCheckboxElement) => el.closest('sbb-checkbox-group') === this,
public get checkboxes(): (SbbCheckboxElement | SbbCheckboxPanelElement)[] {
return <(SbbCheckboxElement | SbbCheckboxPanelElement)[]>(
Array.from(this.querySelectorAll?.('sbb-checkbox, sbb-checkbox-panel') ?? []).filter(
(el) => el.closest('sbb-checkbox-group') === this,
)
);
}

Expand All @@ -52,7 +56,10 @@ export class SbbCheckboxGroupElement extends SbbDisabledMixin(LitElement) {
super.connectedCallback();
const signal = this._abort.signal;
this.addEventListener('keydown', (e) => this._handleKeyDown(e), { signal });
this.toggleAttribute('data-has-selection-panel', !!this.querySelector?.('sbb-selection-panel'));
this.toggleAttribute(
'data-has-panel',
!!this.querySelector?.('sbb-selection-expansion-panel, sbb-checkbox-panel'),
);
}

protected override willUpdate(changedProperties: PropertyValues<this>): void {
Expand All @@ -70,24 +77,25 @@ export class SbbCheckboxGroupElement extends SbbDisabledMixin(LitElement) {
}

private _handleKeyDown(evt: KeyboardEvent): void {
const enabledCheckboxes: SbbCheckboxElement[] = this.checkboxes.filter(
(checkbox: SbbCheckboxElement) =>
!checkbox.disabled && interactivityChecker.isVisible(checkbox),
);
const enabledCheckboxes: (SbbCheckboxElement | SbbCheckboxPanelElement)[] =
this.checkboxes.filter(
(checkbox: SbbCheckboxElement | SbbCheckboxPanelElement) =>
!checkbox.disabled && interactivityChecker.isVisible(checkbox as HTMLElement),
);

if (
!enabledCheckboxes ||
// don't trap nested handling
((evt.target as HTMLElement) !== this &&
(evt.target as HTMLElement).parentElement !== this &&
(evt.target as HTMLElement).parentElement!.nodeName !== 'SBB-SELECTION-PANEL')
(evt.target as HTMLElement).parentElement!.localName !== 'sbb-selection-expansion-panel')
) {
return;
}

if (isArrowKeyPressed(evt)) {
const current: number = enabledCheckboxes.findIndex(
(e: SbbCheckboxElement) => e === evt.target,
(e: SbbCheckboxElement | SbbCheckboxPanelElement) => e === evt.target,
);
const nextIndex: number = getNextElementIndex(evt, current, enabledCheckboxes.length);
enabledCheckboxes[nextIndex]?.focus();
Expand Down
Loading

0 comments on commit 2ad13f4

Please sign in to comment.