diff --git a/packages/components/src/_components/tag/tag.stories.ts b/packages/components/src/_components/tag/tag.stories.ts deleted file mode 100644 index 3fc395579..000000000 --- a/packages/components/src/_components/tag/tag.stories.ts +++ /dev/null @@ -1,26 +0,0 @@ -import '../../solid-components'; -import { storybookDefaults, storybookTemplate } from '../../../scripts/storybook/helper'; -import { withActions } from '@storybook/addon-actions/decorator'; - -const { argTypes, args, parameters } = storybookDefaults('sd-tag'); -const { generateTemplate } = storybookTemplate('sd-tag'); - -export default { - title: 'Components/sd-tag', - component: 'sd-tag', - args, - argTypes, - parameters: {...parameters}, - decorators: [withActions] as any -}; - - -/** - * Default: This shows sd-tag in its default state. - */ - -export const Default = { - render: (args: any) => { - return generateTemplate({ args }); - } -}; diff --git a/packages/components/src/_components/tag/tag.styles.ts b/packages/components/src/_components/tag/tag.styles.ts deleted file mode 100644 index b76234263..000000000 --- a/packages/components/src/_components/tag/tag.styles.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { css } from 'lit'; -import componentStyles from '../../styles/component.styles'; - -export default css` - ${componentStyles} - - :host { - display: inline-block; - } - - .tag { - display: flex; - align-items: center; - border: solid 1px; - line-height: 1; - white-space: nowrap; - user-select: none; - } - - .tag__remove::part(base) { - color: inherit; - padding: 0; - } - - /* - * Variant modifiers - */ - - .tag--primary { - background-color: var(--sd-color-primary-50); - border-color: var(--sd-color-primary-200); - color: var(--sd-color-primary-800); - } - - .tag--primary:active > sd-icon-button { - color: var(--sd-color-primary-600); - } - - .tag--success { - background-color: var(--sd-color-success-50); - border-color: var(--sd-color-success-200); - color: var(--sd-color-success-800); - } - - .tag--success:active > sd-icon-button { - color: var(--sd-color-success-600); - } - - .tag--neutral { - background-color: var(--sd-color-neutral-50); - border-color: var(--sd-color-neutral-200); - color: var(--sd-color-neutral-800); - } - - .tag--neutral:active > sd-icon-button { - color: var(--sd-color-neutral-600); - } - - .tag--warning { - background-color: var(--sd-color-warning-50); - border-color: var(--sd-color-warning-200); - color: var(--sd-color-warning-800); - } - - .tag--warning:active > sd-icon-button { - color: var(--sd-color-warning-600); - } - - .tag--danger { - background-color: var(--sd-color-danger-50); - border-color: var(--sd-color-danger-200); - color: var(--sd-color-danger-800); - } - - .tag--danger:active > sd-icon-button { - color: var(--sd-color-danger-600); - } - - /* - * Size modifiers - */ - - .tag--small { - font-size: var(--sd-button-font-size-small); - height: calc(var(--sd-input-height-small) * 0.8); - line-height: calc(var(--sd-input-height-small) - var(--sd-input-border-width) * 2); - border-radius: var(--sd-input-border-radius-small); - padding: 0 var(--sd-spacing-x-small); - } - - .tag--medium { - font-size: var(--sd-button-font-size-medium); - height: calc(var(--sd-input-height-medium) * 0.8); - line-height: calc(var(--sd-input-height-medium) - var(--sd-input-border-width) * 2); - border-radius: var(--sd-input-border-radius-medium); - padding: 0 var(--sd-spacing-small); - } - - .tag--large { - font-size: var(--sd-button-font-size-large); - height: calc(var(--sd-input-height-large) * 0.8); - line-height: calc(var(--sd-input-height-large) - var(--sd-input-border-width) * 2); - border-radius: var(--sd-input-border-radius-large); - padding: 0 var(--sd-spacing-medium); - } - - .tag__remove { - margin-inline-start: var(--sd-spacing-x-small); - } - - /* - * Pill modifier - */ - - .tag--pill { - border-radius: var(--sd-border-radius-pill); - } -`; diff --git a/packages/components/src/_components/tag/tag.test.ts b/packages/components/src/_components/tag/tag.test.ts deleted file mode 100644 index e108695e7..000000000 --- a/packages/components/src/_components/tag/tag.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { expect, fixture, html } from '@open-wc/testing'; -import sinon from 'sinon'; -import type SdTag from './tag'; - -describe('', () => { - it('should render default tag', async () => { - const el = await fixture(html` Test `); - - const base = el.shadowRoot!.querySelector('[part~="base"]')!; - - expect(el.getAttribute('size')).to.equal('medium'); - expect(base.getAttribute('class')).to.equal(' tag tag--neutral tag--medium '); - }); - - it('should set variant by attribute', async () => { - const el = await fixture(html` Test `); - - const base = el.shadowRoot!.querySelector('[part~="base"]')!; - - expect(base.getAttribute('class')).to.equal(' tag tag--danger tag--medium '); - }); - - it('should set size by attribute', async () => { - const el = await fixture(html` Test `); - - const base = el.shadowRoot!.querySelector('[part~="base"]')!; - - expect(base.getAttribute('class')).to.equal(' tag tag--neutral tag--large '); - }); - - it('should set pill-attribute by attribute', async () => { - const el = await fixture(html` Test `); - - const base = el.shadowRoot!.querySelector('[part~="base"]')!; - - expect(base.getAttribute('class')).to.equal(' tag tag--neutral tag--medium tag--pill '); - }); - - it('should set removable by attribute', async () => { - const el = await fixture(html` Test `); - - const base = el.shadowRoot!.querySelector('[part~="base"]')!; - const removeButton = el.shadowRoot!.querySelector('[part~="remove-button"]'); - - expect(el.removable).to.equal(true); - expect(base.getAttribute('class')).to.equal(' tag tag--neutral tag--medium tag--removable '); - expect(removeButton).not.to.be.null; - }); - - describe('removable', () => { - it('should emit remove event when remove button clicked', async () => { - const el = await fixture(html` Test `); - - const removeButton = el.shadowRoot!.querySelector('[part~="remove-button"]')!; - const spy = sinon.spy(); - - el.addEventListener('sd-remove', spy, { once: true }); - - removeButton.click(); - - expect(spy.called).to.equal(true); - }); - }); -}); diff --git a/packages/components/src/_components/tag/tag.ts b/packages/components/src/_components/tag/tag.ts deleted file mode 100644 index 7db804c65..000000000 --- a/packages/components/src/_components/tag/tag.ts +++ /dev/null @@ -1,98 +0,0 @@ -import '../icon-button/icon-button'; -import { classMap } from 'lit/directives/class-map.js'; -import { customElement, property } from 'lit/decorators.js'; -import { html } from 'lit'; -import { LocalizeController } from '../../utilities/localize'; -import SolidElement from '../../internal/solid-element'; -import styles from './tag.styles'; -import type { CSSResultGroup } from 'lit'; - -/** - * @summary Tags are used as labels to organize things or to indicate a selection. - * @documentation https://solid.union-investment.com/[storybook-link]/tag - * @status stable - * @since 1.0 - * - * @dependency sd-icon-button - * - * @slot - The tag's content. - * - * @event sd-remove - Emitted when the remove button is activated. - * - * @csspart base - The component's base wrapper. - * @csspart content - The tag's content. - * @csspart remove-button - The tag's remove button, an ``. - * @csspart remove-button__base - The remove button's exported `base` part. - */ -@customElement('sd-tag') -export default class SdTag extends SolidElement { - static styles: CSSResultGroup = styles; - private readonly localize = new LocalizeController(this); - - /** The tag's theme variant. */ - @property({ reflect: true }) variant: 'primary' | 'success' | 'neutral' | 'warning' | 'danger' | 'text' = 'neutral'; - - /** The tag's size. */ - @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium'; - - /** Draws a pill-style tag with rounded edges. */ - @property({ type: Boolean, reflect: true }) pill = false; - - /** Makes the tag removable and shows a remove button. */ - @property({ type: Boolean }) removable = false; - - private handleRemoveClick() { - this.emit('sd-remove'); - } - - render() { - return html` - - - - ${this.removable - ? html` - - ` - : ''} - - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'sd-tag': SdTag; - } -} diff --git a/packages/components/src/components/tag/tag.stories.ts b/packages/components/src/components/tag/tag.stories.ts new file mode 100644 index 000000000..210abf97c --- /dev/null +++ b/packages/components/src/components/tag/tag.stories.ts @@ -0,0 +1,201 @@ +import '../../solid-components'; +import { html } from 'lit'; +import { storybookDefaults, storybookHelpers, storybookTemplate } from '../../../scripts/storybook/helper'; +import { userEvent } from '@storybook/testing-library'; +import { waitUntil } from '@open-wc/testing-helpers'; +import { withActions } from '@storybook/addon-actions/decorator'; + +const { argTypes, parameters } = storybookDefaults('sd-tag'); +const { generateTemplate } = storybookTemplate('sd-tag'); +const { overrideArgs } = storybookHelpers('sd-tag'); + +export default { + title: 'Components/sd-tag', + component: 'sd-tag', + args: overrideArgs({ type: 'slot', name: 'default', value: 'Tag' }), + argTypes, + parameters: { ...parameters }, + decorators: [withActions] as any +}; + +/** + * Default: This shows sd-tag in its default state. + */ + +export const Default = { + render: (args: any) => { + return generateTemplate({ args }); + } +}; + +/** + * The tag in all possible combinations of `selected` and `size`. + */ + +export const selectedAndSize = { + name: 'Selected ร— Size', + parameters: { controls: { exclude: ['size', 'selected'] } }, + render: (args: any) => { + return generateTemplate({ + axis: { + x: { type: 'attribute', name: 'selected', values: ['false', 'true'] }, + y: { type: 'attribute', name: 'size' } + }, + args + }); + } +}; + +/** + * The tag in all possible combinations of `removable` and `size`. + */ + +export const removableAndSize = { + name: 'Removable ร— Size', + parameters: { controls: { exclude: ['size', 'removable'] } }, + render: (args: any) => { + return generateTemplate({ + axis: { + x: { type: 'attribute', name: 'removable', values: ['false', 'true'] }, + y: { type: 'attribute', name: 'size' } + }, + args + }); + } +}; + +/** + * Use the `disabled` attribute to disable a tag. Clicks will be suppressed until the disabled state is removed. + * + * __Hint:__ If the href attribute is set i.e. the tag is rendered as a link (``), + * the disabled attribute is ignored, as links may not be disabled. + * To disable the tag in this case the href attribute has to be removed as well. + */ + +export const Disabled = { + name: 'Disabled', + parameters: { controls: { exclude: ['selected', 'removable', 'disabled'] } }, + render: (args: any) => { + return generateTemplate({ + axis: { + x: { type: 'attribute', name: 'selected', values: ['false', 'true'] }, + y: { type: 'attribute', name: 'removable', values: ['false', 'true'] } + }, + constants: [ + { + type: 'attribute', + name: 'disabled', + value: 'true' + } + ], + args + }); + } +}; + +/** + * Use the `default` slot to add content to the tag. + * Use the `removable-indicator` slot to change the removability indicator. + * + * If you add icons to the slot, please make sure to account for accessibility by providing an alt-text. + */ + +export const Slots = { + parameters: { + controls: { exclude: ['size', 'selected', 'removable', 'disabled'] } + }, + render: (args: any) => { + return html` + ${['default', 'removable-indicator'].map(slot => + generateTemplate({ + axis: { + x: { + type: 'slot', + name: slot, + title: 'slot=...', + values: [ + { + title: slot, + value: + slot === 'default' + ? `` + : `` + } + ] + } + }, + args, + constants: [ + { type: 'attribute', name: 'removable', value: 'true' }, + { type: 'attribute', name: 'size', value: 'lg' } + ] + }) + )} + `; + } +}; + +/** + * Use the `base`, `content` and `removable-indicator` part selectors to customize the button. + */ + +export const Parts = { + parameters: { + controls: { exclude: ['size', 'selected', 'removable', 'disabled'] } + }, + render: (args: any) => { + return generateTemplate({ + axis: { + y: { + type: 'template', + name: 'sd-tag::part(...){outline: solid 2px red}', + values: ['base', 'content', 'removable-indicator'].map(part => { + return { + title: part, + value: `
%TEMPLATE%
` + }; + }) + } + }, + constants: [ + { type: 'template', name: 'width', value: '
%TEMPLATE%
' }, + { + type: 'slot', + name: 'default', + value: 'Tag' + }, + { + type: 'slot', + name: 'removable-indicator', + value: + '' + }, + { type: 'attribute', name: 'removable', value: 'true' }, + { type: 'attribute', name: 'size', value: 'lg' } + ], + args + }); + } +}; + +/** + * sd-tags are fully accessibile via keyboard. + */ + +export const Mouseless = { + render: (args: any) => { + return html`
${generateTemplate({ args })}
`; + }, + + play: async ({ canvasElement }: { canvasElement: HTMLUnknownElement }) => { + const el = canvasElement.querySelector('.mouseless sd-tag'); + await waitUntil(() => el?.shadowRoot?.querySelector('button')); + + if (el?.shadowRoot) { + const button = el.shadowRoot.querySelector('button'); + if (button) { + await userEvent.type(button, '{space}', { pointerEventsCheck: 0 }); + } + } + } +}; diff --git a/packages/components/src/components/tag/tag.test.ts b/packages/components/src/components/tag/tag.test.ts new file mode 100644 index 000000000..8e128a6ba --- /dev/null +++ b/packages/components/src/components/tag/tag.test.ts @@ -0,0 +1,149 @@ +import { expect, fixture, html, waitUntil } from '@open-wc/testing'; +import sinon from 'sinon'; +import type SdTag from './tag'; + +const sizes = ['lg', 'sm']; + +describe('', () => { + let el: SdTag; + + describe('when provided no parameters', () => { + it('should pass accessibility tests', async () => { + el = await fixture(html`Tag`); + await expect(el).to.be.accessible(); + }); + + it('should have the primary values set correctly', async () => { + el = await fixture(html`Tag`); + expect(el.size).to.equal('lg'); + expect(el.selected).to.equal(false); + expect(el.removable).to.equal(false); + expect(el.disabled).to.equal(false); + }); + + it('should render as a