Skip to content

Commit

Permalink
refactor: tag group migration to Lit
Browse files Browse the repository at this point in the history
  • Loading branch information
dauriamarco committed Oct 5, 2023
1 parent 05c14a7 commit 1e0aac3
Show file tree
Hide file tree
Showing 10 changed files with 433 additions and 464 deletions.
1 change: 1 addition & 0 deletions src/components/sbb-tag-group/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './sbb-tag-group';
647 changes: 301 additions & 346 deletions src/components/sbb-tag-group/sbb-tag-group.e2e.ts

Large diffs are not rendered by default.

86 changes: 44 additions & 42 deletions src/components/sbb-tag-group/sbb-tag-group.spec.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,51 @@
import { SbbTagGroup } from './sbb-tag-group';
import { newSpecPage } from '@stencil/core/testing';
import { expect, fixture } from '@open-wc/testing';
import { html } from 'lit/static-html.js';
import './sbb-tag-group';

describe('sbb-tag-group', () => {
it('renders', async () => {
const { root } = await newSpecPage({
components: [SbbTagGroup],
html: `
<sbb-tag-group>
<sbb-tag value="tag-1">First tag</sbb-tag>
<sbb-tag value="tag-2">Second tag</sbb-tag>
<sbb-tag value="tag-3">Third tag</sbb-tag>
</sbb-tag-group>
`,
});

expect(root).toEqualHtml(`
<sbb-tag-group role="group">
<mock:shadow-root>
<div class="sbb-tag-group">
<ul class="sbb-tag-group__list">
<li class="sbb-tag-group__list-item">
<slot name="tag-0"></slot>
</li>
<li class="sbb-tag-group__list-item">
<slot name="tag-1"></slot>
</li>
<li class="sbb-tag-group__list-item">
<slot name="tag-2"></slot>
</li>
</ul>
<span hidden="">
<slot></slot>
</span>
</div>
</mock:shadow-root>
<sbb-tag slot="tag-0" value="tag-1">
First tag
</sbb-tag>
<sbb-tag slot="tag-1" value="tag-2">
Second tag
</sbb-tag>
<sbb-tag slot="tag-2" value="tag-3">
Third tag
</sbb-tag>
const root = await fixture(html`
<sbb-tag-group>
<sbb-tag value="tag-1">First tag</sbb-tag>
<sbb-tag value="tag-2">Second tag</sbb-tag>
<sbb-tag value="tag-3">Third tag</sbb-tag>
</sbb-tag-group>
`);

expect(root).dom.to.be.equal(
`
<sbb-tag-group role="group">
<sbb-tag slot="tag-0" value="tag-1">
First tag
</sbb-tag>
<sbb-tag slot="tag-1" value="tag-2">
Second tag
</sbb-tag>
<sbb-tag slot="tag-2" value="tag-3">
Third tag
</sbb-tag>
</sbb-tag-group>
`,
);
expect(root).shadowDom.to.be.equal(
`
<div class="sbb-tag-group">
<ul class="sbb-tag-group__list">
<li class="sbb-tag-group__list-item">
<slot name="tag-0"></slot>
</li>
<li class="sbb-tag-group__list-item">
<slot name="tag-1"></slot>
</li>
<li class="sbb-tag-group__list-item">
<slot name="tag-2"></slot>
</li>
</ul>
<span hidden="">
<slot></slot>
</span>
</div>
`,
);
});
});
6 changes: 4 additions & 2 deletions src/components/sbb-tag-group/sbb-tag-group.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/** @jsx h */
import { Fragment, h, JSX } from 'jsx-dom';
import readme from './readme.md';
import readme from './readme.md?raw';
import { withActions } from '@storybook/addon-actions/decorator';
import type { Meta, StoryObj, ArgTypes, Args, Decorator } from '@storybook/html';
import type { Meta, StoryObj, ArgTypes, Args, Decorator } from '@storybook/web-components';
import type { InputType } from '@storybook/types';
import './sbb-tag-group';
import '../sbb-tag';

const uncheckAllTag = (): void => {
document.getElementById('all').removeAttribute('checked');
Expand Down
126 changes: 71 additions & 55 deletions src/components/sbb-tag-group/sbb-tag-group.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,41 @@
import {
Component,
ComponentInterface,
Element,
h,
Host,
JSX,
Listen,
Prop,
Watch,
} from '@stencil/core';
import { TagStateChange } from '../sbb-tag/sbb-tag.custom';
import { CSSResult, html, LitElement, TemplateResult, PropertyValues, nothing } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { ConnectedAbortController } from '../../global/eventing';
import { SbbTag } from '../sbb-tag/index';
import { setAttribute } from '../../global/dom';
import Style from './sbb-tag-group.scss?lit&inline';

/**
* @slot unnamed - Provide one or more 'sbb-tag' to add to the group.
*/
@Component({
shadow: true,
styleUrl: 'sbb-tag-group.scss',
tag: 'sbb-tag-group',
})
export class SbbTagGroup implements ComponentInterface {
@Element() private _element: HTMLElement;
@customElement('sbb-tag-group')
export class SbbTagGroup extends LitElement {
public static override styles: CSSResult = Style;

/**
* This will be forwarded as aria-label to the inner list.
*/
@Prop() public listAccessibilityLabel?: string;
@property({ attribute: 'list-accessibility-label' }) public listAccessibilityLabel?: string;

/**
* If set multiple to false, the selection is exclusive and the value is a string (or null).
* If set multiple to true, the selection can have multiple values and therefore value is an array.
*
* Changing multiple during run time is not supported.
*/
@Prop() public multiple = false;
@property({ type: Boolean }) public multiple = false;

/**
* Value of the sbb-tag-group.
* If set multiple to false, the value is a string (or null).
* If set multiple to true, the value is an array.
*/
@Prop({ mutable: true }) public value: string | string[] | null = null;
@property() public value: string | string[] | null = null;

@Watch('value')
public valueChanged(value: string | string[] | null): void {
private _abort = new ConnectedAbortController(this);

private _valueChanged(value: string | string[] | null): void {
if (Array.isArray(value) && !this.multiple) {
console.warn(
'Trying to set array value for sbb-tag-group but multiple mode is not activated.',
Expand All @@ -63,7 +55,7 @@ export class SbbTagGroup implements ComponentInterface {
return;
}

const isChecked: (tag: HTMLSbbTagElement) => boolean = this.multiple
const isChecked: (tag: SbbTag) => boolean = this.multiple
? (t) => value.includes(t.value)
: (t) => t.value === value;

Expand All @@ -82,9 +74,8 @@ export class SbbTagGroup implements ComponentInterface {
.forEach((tag) => (tag.checked = false));
}

@Listen('state-change', { passive: true })
public handleStateChange(event: CustomEvent<TagStateChange>): void {
const target: HTMLSbbTagElement = event.target as HTMLSbbTagElement;
private _handleStateChange(event: CustomEvent<TagStateChange>): void {
const target: SbbTag = event.target as SbbTag;
event.stopPropagation();

if (this.multiple || (event.detail.type === 'checked' && !event.detail.checked)) {
Expand All @@ -107,44 +98,69 @@ export class SbbTagGroup implements ComponentInterface {
}
}

public connectedCallback(): void {
public override connectedCallback(): void {
super.connectedCallback();
const signal = this._abort.signal;
this.addEventListener(
'state-change',
(e: CustomEvent<TagStateChange>) => this._handleStateChange(e),
{
signal,
passive: true,
},
);
if (this.value) {
this.valueChanged(this.value);
this._valueChanged(this.value);
}
}

public override willUpdate(changedProperties: PropertyValues<this>): void {
if (changedProperties.has('value')) {
this._valueChanged(this.value);
}
}

private get _tags(): HTMLSbbTagElement[] {
return Array.from(this._element.querySelectorAll('sbb-tag')) as HTMLSbbTagElement[];
private get _tags(): SbbTag[] {
return Array.from(this.querySelectorAll('sbb-tag')) as SbbTag[];
}

public render(): JSX.Element {
protected override render(): TemplateResult {
this._tags.forEach((tag, index) => tag.setAttribute('slot', `tag-${index}`));
return (
<Host role={this.listAccessibilityLabel ? '' : 'group'}>
<div class="sbb-tag-group">
<ul class="sbb-tag-group__list" aria-label={this.listAccessibilityLabel}>
{this._tags.map((_, index) => (
<li class="sbb-tag-group__list-item">

setAttribute(this, 'role', this.listAccessibilityLabel ? '' : 'group');

return html`
<div class="sbb-tag-group">
<ul class="sbb-tag-group__list" aria-label=${this.listAccessibilityLabel ?? nothing}>
${this._tags.map(
(_, index) =>
html`<li class="sbb-tag-group__list-item">
<slot
name={`tag-${index}`}
onSlotchange={(): void => {
name=${`tag-${index}`}
@slotchange=${(): void => {
this._ensureOnlyOneTagSelected();
this._updateValueByReadingTags();
}}
/>
</li>
))}
</ul>
<span hidden>
<slot
onSlotchange={() => {
this._ensureOnlyOneTagSelected();
this._updateValueByReadingTags();
}}
/>
</span>
</div>
</Host>
);
></slot>
</li>`,
)}
</ul>
<span hidden>
<slot
@slotchange=${() => {
this._ensureOnlyOneTagSelected();
this._updateValueByReadingTags();
}}
></slot>
</span>
</div>
`;
}
}

declare global {
interface HTMLElementTagNameMap {
// eslint-disable-next-line @typescript-eslint/naming-convention
'sbb-tag-group': SbbTagGroup;
}
}
1 change: 1 addition & 0 deletions src/components/sbb-tag/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './sbb-tag';
12 changes: 6 additions & 6 deletions src/components/sbb-tag/sbb-tag.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('sbb-tag', () => {
});

it('should be checked after click', async () => {
expect(element).to.have.attribute('checked', null);
expect(element).not.to.have.attribute('checked');
const changeSpy = new EventSpy('change', document);
const inputSpy = new EventSpy('input', document);

Expand All @@ -25,11 +25,11 @@ describe('sbb-tag', () => {

expect(changeSpy.count).to.be.greaterThan(0);
expect(inputSpy.count).to.be.greaterThan(0);
expect(element).to.have.attribute('checked', '');
expect(element).to.have.attribute('checked');
});

it('should not be checked after click when disabled', async () => {
expect(element).to.have.attribute('checked', null);
expect(element).not.to.have.attribute('checked');
element.setAttribute('disabled', '');
await element.updateComplete;

Expand All @@ -45,7 +45,7 @@ describe('sbb-tag', () => {
});

it('should be checked after "Space" keypress', async () => {
expect(element).to.have.attribute('checked', null);
expect(element).not.to.have.attribute('checked');
const changeSpy = new EventSpy('change', document);
const inputSpy = new EventSpy('input', document);

Expand All @@ -55,7 +55,7 @@ describe('sbb-tag', () => {
await element.updateComplete;
expect(changeSpy.count).to.be.greaterThan(0);
expect(inputSpy.count).to.be.greaterThan(0);
expect(element).to.have.attribute('checked', '');
expect(element).to.have.attribute('checked');
});

it('should be unchecked after "Space" keypress', async () => {
Expand All @@ -70,6 +70,6 @@ describe('sbb-tag', () => {
await element.updateComplete;
expect(changeSpy.count).to.be.greaterThan(0);
expect(inputSpy.count).to.be.greaterThan(0);
expect(element).to.have.attribute('checked', null);
expect(element).not.to.have.attribute('checked');
});
});
10 changes: 0 additions & 10 deletions src/components/sbb-tag/sbb-tag.events.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/sbb-tag/sbb-tag.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('sbb-tag', () => {

expect(root).dom.to.be.equal(
`
<sbb-tag aria-pressed="true" checked role="button" tabindex="0" value="info" dir="ltr">
<sbb-tag aria-pressed="true" checked="true" role="button" tabindex="0" value="info" dir="ltr">
Info
</sbb-tag>
`,
Expand Down
Loading

0 comments on commit 1e0aac3

Please sign in to comment.