diff --git a/packages/components/src/components/teaser-media/teaser-media.stories.ts b/packages/components/src/components/teaser-media/teaser-media.stories.ts new file mode 100644 index 000000000..c1d575a56 --- /dev/null +++ b/packages/components/src/components/teaser-media/teaser-media.stories.ts @@ -0,0 +1,251 @@ +import '../../solid-components'; +import { html } from 'lit'; +import { storybookDefaults, storybookHelpers, storybookTemplate } from '../../../scripts/storybook/helper'; + +const { argTypes, parameters } = storybookDefaults('sd-teaser-media'); +const { overrideArgs } = storybookHelpers('sd-teaser-media'); +const { generateTemplate } = storybookTemplate('sd-teaser-media'); + +export default { + title: 'Components/sd-teaser-media', + component: 'sd-teaser-media', + args: overrideArgs([ + { + type: 'slot', + name: 'default', + value: `
Main slot
` + }, + { + type: 'slot', + name: 'media', + value: `
Media slot
` + }, + { + type: 'slot', + name: 'meta', + value: `
Meta slot
` + } + ]), + argTypes, + parameters +}; + +/** + * This shows sd-teaser-media in its default state. + */ + +export const Default = { + render: (args: any) => { + return generateTemplate({ args }); + } +}; + +/** + * Teaser-Media in all possible `variant`. + */ + +export const Variant = { + name: 'Variant', + parameters: { controls: { exclude: ['variant'] } }, + render: (args: any) => { + return generateTemplate({ + axis: { + y: { type: 'attribute', name: 'variant' } + }, + args, + constants: [ + { + type: 'template', + name: 'style', + value: '
%TEMPLATE%
' + }, + { + type: 'slot', + name: 'media', + value: + 'A skyline of a city by night' + } + ] + }); + } +}; + +/** + * Use the 'default', 'media', 'meta', 'expandable' and 'headline' slots to add content to the teaser. Please make sure to use semantically correct headline tags for the `headline` slot to provide accessible content. + */ +export const Slots = { + parameters: { + controls: { exclude: ['default', 'media', 'meta', 'headline', 'expandable'] } + }, + render: (args: any) => { + return html` + ${['default', 'media', 'meta', 'headline', 'expandable'].map(slot => { + let value = `
`; + + if (slot === 'default') { + value = `
`; + } else if (slot === 'media') { + value = `
`; + } else if (slot === 'expandable') { + value = `
`; + } + + return generateTemplate({ + axis: { + x: { + type: 'slot', + name: slot, + title: 'slot=..', + values: [ + { + value: value, + title: slot + } + ] + } + }, + args, + constants: [ + { + type: 'slot', + name: 'media', + value: `Test` + }, + { + type: 'slot', + name: 'meta', + value: `Teaser's Meta information` + }, + { + type: 'slot', + name: 'default', + value: `Teaser's Main content` + }, + { + type: 'slot', + name: 'headline', + value: `Teaser's Headline` + }, + { + type: 'slot', + name: 'expandable', + value: `Teaser's Expandable content` + }, + { + type: 'template', + name: 'style', + value: '
%TEMPLATE%
' + } + ] + }); + })} + `; + } +}; + +export const Parts = { + parameters: { + controls: { exclude: ['base', 'media', 'content', 'meta', 'headline', 'main', 'expandable', 'variant'] } + }, + render: (args: any) => { + return generateTemplate({ + axis: { + y: { + type: 'template', + name: 'sd-teaser-media::part(...){outline: solid 2px red}', + values: ['base', 'media', 'content', 'meta', 'headline', 'main', 'expandable'].map(part => { + return { + title: part, + value: `
%TEMPLATE%
` + }; + }) + } + }, + args, + constants: [ + { + type: 'template', + name: 'style', + value: '
%TEMPLATE%
' + }, + { + type: 'attribute', + name: 'variant', + value: 'white' + }, + { + type: 'slot', + name: 'expandable', + value: `

Expandable slot

` + } + ] + }); + } +}; + +export const Samples = { + name: 'Samples: Teaser-Media', + parameters: { + controls: { + disable: true + }, + backgrounds: { + default: 'white' + } + }, + render: () => { + return html` + +
+ +
+ A skyline of a city +
+
+ 01.12.2013 + | Author name +
+

Not expandable teaser-media

+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

+
+ Link +
+
+
+ +
+ A skyline of a city +
+
+ 01.12.2013 + | Author name +
+

Expandable teaser-media

+
+

+ Expandable: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut + labore et dolore magna aliqua. +

+
+
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et + dolore magna aliqua. +

+
+ Link +
+
+

@Copyright Lorem ipsum

+
+
+
+
+ `; + } +}; diff --git a/packages/components/src/components/teaser-media/teaser-media.test.ts b/packages/components/src/components/teaser-media/teaser-media.test.ts new file mode 100644 index 000000000..61152ecad --- /dev/null +++ b/packages/components/src/components/teaser-media/teaser-media.test.ts @@ -0,0 +1,43 @@ +import { expect, fixture, html } from '@open-wc/testing'; +import type SdTeaserMedia from './teaser-media'; + +describe('', () => { + it('renders default values correctly', async () => { + const el = await fixture(html``); + + expect(el.variant).to.equal('white'); + }); + + it('renders assigned values correctly', async () => { + const el = await fixture(html``); + + expect(el.variant).to.equal('neutral-100'); + }); + + it('renders slots correctly', async () => { + const el = await fixture(html` + +
Media
+
Meta
+
Headline
+
Main
+
Expandable
+
+ `); + + expect(el.shadowRoot!.querySelector('[part~="media"]')).to.exist; + expect(el.shadowRoot!.querySelector('[part~="meta"]')).to.exist; + expect(el.shadowRoot!.querySelector('[part~="headline"]')).to.exist; + expect(el.shadowRoot!.querySelector('[part~="main"]')).to.exist; + expect(el.shadowRoot!.querySelector('[part~="expandable"]')).to.exist; + }); + + it('hides slots correctly', async () => { + const el = await fixture(html` `); + + expect(el.shadowRoot!.querySelector('[part~="media"]')?.classList.contains('hidden')).to.be.true; + expect(el.shadowRoot!.querySelector('[part~="meta"]')?.classList.contains('hidden')).to.be.true; + expect(el.shadowRoot!.querySelector('[part~="main"]')?.classList.contains('hidden')).to.be.true; + expect(el.shadowRoot!.querySelector('[part~="expandable"]')?.classList.contains('hidden')).to.be.true; + }); +}); diff --git a/packages/components/src/components/teaser-media/teaser-media.ts b/packages/components/src/components/teaser-media/teaser-media.ts new file mode 100644 index 000000000..084854f4f --- /dev/null +++ b/packages/components/src/components/teaser-media/teaser-media.ts @@ -0,0 +1,129 @@ +import { css, html } from 'lit'; +import { customElement } from '../../internal/register-custom-element'; +import { HasSlotController } from '../../internal/slot'; +import { property, query } from 'lit/decorators.js'; +import cx from 'classix'; +import SolidElement from '../../internal/solid-element'; +/** + * @summary Teasers group information into flexible containers so users can browse a collection of related items and actions. + * @documentation https://solid-design-system.fe.union-investment.de/x.x.x/storybook/?path=/docs/components-sd-teaser-media--docs + * + * @status stable + * @since 2.4.0 + * * + * @slot - An optional main content slot. + * @slot media - An optional media slot. + * @slot meta - An optional meta slot. + * @slot expandable - An optional expandable slot, not shown on small devices. + * @slot headline - headline slot. + * + * @csspart base - The component's base wrapper. + * @csspart media - The container that wraps the media. + * @csspart content - The container that wraps the content. + * @csspart meta - The container that wraps the meta. + * @csspart headline - The container that wraps the headline. + * @csspart expandable - The container that wraps the expandable. + * @csspart main - The container that wraps the main content. + */ + +@customElement('sd-teaser-media') +export default class SdTeaserMedia extends SolidElement { + @property({ reflect: true }) + variant: 'white' | 'neutral-100' | 'primary' | 'primary-100' | 'gradient-white' | 'gradient-dark' = 'white'; + + @query('[part="base"]') teaserMedia: HTMLElement; + + private readonly hasSlotController = new HasSlotController( + this, + '[default]', + 'media', + 'meta', + 'headline', + 'expandable' + ); + + render() { + const slots = { + 'teaser-has-default': this.hasSlotController.test('[default]'), + 'teaser-has-media': this.hasSlotController.test('media'), + 'teaser-has-meta': this.hasSlotController.test('meta'), + 'teaser-has-headline': this.hasSlotController.test('headline'), + 'teaser-has-expandable': this.hasSlotController.test('expandable') + }; + + return html` +
+
+ +
+ + +
+
+
+
+
+ +
+ +
+ Always insert one semantically correct heading element here (e. g. <h2>) +
+ +
+ +
+ +
+ +
+
+
+
+
+ `; + } + + static styles = [ + SolidElement.styles, + css` + :host { + @apply block; + } + + ::slotted(*) { + @apply m-0; + } + + ::slotted([slot='headline']) { + @apply font-bold !m-0 !text-lg; + } + ` + ]; +}