diff --git a/.changeset/calm-rabbits-pull.md b/.changeset/calm-rabbits-pull.md new file mode 100644 index 0000000000..36ee70ea3d --- /dev/null +++ b/.changeset/calm-rabbits-pull.md @@ -0,0 +1,6 @@ +--- +'@swisspost/design-system-components': minor +'@swisspost/design-system-styles': minor +--- + +Added composable footer component. diff --git a/.changeset/gorgeous-needles-run.md b/.changeset/gorgeous-needles-run.md new file mode 100644 index 0000000000..732850bd1a --- /dev/null +++ b/.changeset/gorgeous-needles-run.md @@ -0,0 +1,5 @@ +--- +'@swisspost/design-system-components': patch +--- + +Added a fixed slot `post-list-item` on the `post-list-item` host element, so it is no longer necessary to add it manually. diff --git a/.changeset/heavy-eyes-live.md b/.changeset/heavy-eyes-live.md new file mode 100644 index 0000000000..e2d5076e51 --- /dev/null +++ b/.changeset/heavy-eyes-live.md @@ -0,0 +1,5 @@ +--- +'@swisspost/design-system-components': patch +--- + +Fixed the used `headingLevel` in the `post-accorddion-item` component. The component now uses the value from its closest `post-accorddion` parent component, if this is specified and falls back to `h2` if not specified. diff --git a/.changeset/hungry-penguins-turn.md b/.changeset/hungry-penguins-turn.md new file mode 100644 index 0000000000..f18caa33d0 --- /dev/null +++ b/.changeset/hungry-penguins-turn.md @@ -0,0 +1,5 @@ +--- +'@swisspost/design-system-styles': patch +--- + +Fixed the `btn-icon` styles, so icons within can no longer be rendered too small, because of the inline-padding on the button. diff --git a/.changeset/new-cougars-count.md b/.changeset/new-cougars-count.md new file mode 100644 index 0000000000..a06891fb5e --- /dev/null +++ b/.changeset/new-cougars-count.md @@ -0,0 +1,5 @@ +--- +'@swisspost/design-system-components': minor +--- + +Added the parts `button` and `body` in the `post-accordion-item` component, so one can override styles from the outside. diff --git a/.changeset/nine-baboons-rule.md b/.changeset/nine-baboons-rule.md new file mode 100644 index 0000000000..ecc4c1652d --- /dev/null +++ b/.changeset/nine-baboons-rule.md @@ -0,0 +1,5 @@ +--- +'@swisspost/design-system-styles': patch +--- + +Fixed the appstore-badge styles to get rid of the inline gap below. diff --git a/.changeset/old-spiders-travel.md b/.changeset/old-spiders-travel.md new file mode 100644 index 0000000000..7f4fc4731a --- /dev/null +++ b/.changeset/old-spiders-travel.md @@ -0,0 +1,5 @@ +--- +'@swisspost/design-system-styles': minor +--- + +Added the possibility to define a `$child-selector` parameter with our list mixins, so they can be used also with custom elements. diff --git a/.changeset/tender-laws-confess.md b/.changeset/tender-laws-confess.md new file mode 100644 index 0000000000..49878a3ecc --- /dev/null +++ b/.changeset/tender-laws-confess.md @@ -0,0 +1,6 @@ +--- +'@swisspost/design-system-documentation': minor +'@swisspost/design-system-components': minor +--- + +Added the css parts `button` and `body` in the `post-accorddion-item` component. diff --git a/packages/components/cypress/e2e/footer.cy.ts b/packages/components/cypress/e2e/footer.cy.ts new file mode 100644 index 0000000000..77c7719d2b --- /dev/null +++ b/packages/components/cypress/e2e/footer.cy.ts @@ -0,0 +1,54 @@ +const FOOTER_ID = 'd97528b3-a9ef-4201-bf28-9caf6e8997dc'; + +describe('Footer', () => { + describe('Structure & Props', () => { + beforeEach(() => { + cy.getComponent('footer', FOOTER_ID); + cy.get('@footer').find('> footer h2.visually-hidden').as('label'); + }); + + it('should render', () => { + cy.get('@footer').should('exist'); + }); + + it('should set label text according to "label" prop', () => { + cy.get('@label').should('have.text', 'Footer label'); + }); + + it('should render the post-accorddion on mobile', () => { + cy.viewport('iphone-3'); + cy.get('@footer').find('post-accorddion').as('accorddion'); + + cy.get('@accorddion').should('exist'); + }); + + it('should have accorddion-items with slotted elements on mobile', () => { + cy.viewport('iphone-3'); + cy.get('@footer').find('post-accorddion').as('accorddion'); + cy.get('@accorddion').find('post-accordion-item').as('accordionItems'); + + cy.get('@accordionItems').should('have.length', 4); + cy.get('@accordionItems') + .find('slot[name="header"]') + .each($slot => { + const headerSlot = $slot.get(0) as HTMLSlotElement; + + expect(headerSlot.assignedElements().length).to.be.greaterThan(0); + }); + cy.get('@accordionItems') + .find('slot:not([name])') + .each($slot => { + const slotDefault = $slot.get(0) as HTMLSlotElement; + + expect(slotDefault.assignedElements().length).to.be.greaterThan(0); + }); + }); + }); + + describe('Accessibility', () => { + it('Has no detectable a11y violations', () => { + cy.getSnapshots('footer'); + cy.checkA11y('#root-inner'); + }); + }); +}); diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index f8c52eb83a..075f07063b 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -174,6 +174,12 @@ export namespace Components { */ "update": () => Promise; } + interface PostFooter { + /** + * The label to add to the footer (visually hidden). + */ + "label": string; + } interface PostHeader { /** * Toggles the mobile navigation. @@ -604,6 +610,12 @@ declare global { prototype: HTMLPostCollapsibleTriggerElement; new (): HTMLPostCollapsibleTriggerElement; }; + interface HTMLPostFooterElement extends Components.PostFooter, HTMLStencilElement { + } + var HTMLPostFooterElement: { + prototype: HTMLPostFooterElement; + new (): HTMLPostFooterElement; + }; interface HTMLPostHeaderElement extends Components.PostHeader, HTMLStencilElement { } var HTMLPostHeaderElement: { @@ -828,6 +840,7 @@ declare global { "post-closebutton": HTMLPostClosebuttonElement; "post-collapsible": HTMLPostCollapsibleElement; "post-collapsible-trigger": HTMLPostCollapsibleTriggerElement; + "post-footer": HTMLPostFooterElement; "post-header": HTMLPostHeaderElement; "post-icon": HTMLPostIconElement; "post-language-option": HTMLPostLanguageOptionElement; @@ -993,6 +1006,12 @@ declare namespace LocalJSX { */ "for"?: string; } + interface PostFooter { + /** + * The label to add to the footer (visually hidden). + */ + "label": string; + } interface PostHeader { } /** @@ -1247,6 +1266,7 @@ declare namespace LocalJSX { "post-closebutton": PostClosebutton; "post-collapsible": PostCollapsible; "post-collapsible-trigger": PostCollapsibleTrigger; + "post-footer": PostFooter; "post-header": PostHeader; "post-icon": PostIcon; "post-language-option": PostLanguageOption; @@ -1288,6 +1308,7 @@ declare module "@stencil/core" { "post-closebutton": LocalJSX.PostClosebutton & JSXBase.HTMLAttributes; "post-collapsible": LocalJSX.PostCollapsible & JSXBase.HTMLAttributes; "post-collapsible-trigger": LocalJSX.PostCollapsibleTrigger & JSXBase.HTMLAttributes; + "post-footer": LocalJSX.PostFooter & JSXBase.HTMLAttributes; "post-header": LocalJSX.PostHeader & JSXBase.HTMLAttributes; /** * @class PostIcon - representing a stencil component diff --git a/packages/components/src/components/post-accordion-item/post-accordion-item.tsx b/packages/components/src/components/post-accordion-item/post-accordion-item.tsx index 7421822a10..235d9ce6e1 100644 --- a/packages/components/src/components/post-accordion-item/post-accordion-item.tsx +++ b/packages/components/src/components/post-accordion-item/post-accordion-item.tsx @@ -4,6 +4,8 @@ import { HEADING_LEVELS, HeadingLevel } from '@/types'; import { checkEmptyOrOneOf } from '@/utils'; /** + * @part button - The pseudo-element, used to override styles on the components internal header `button` element. + * @part body - The pseudo-element, used to override styles on the components internal `body` element. * @slot logo - Slot for the placing a logo before the header. * @slot header - Slot for placing custom content within the accordion item's header. * @slot default - Slot for placing content within the accordion item's body. @@ -79,14 +81,19 @@ export class PostAccordionItem { } render() { - const HeadingTag = `h${this.headingLevel ?? 2}`; + const headingLevel = this.host.closest('post-accorddion')?.getAttribute('heading-level'); + const HeadingTag = `h${headingLevel ?? this.headingLevel ?? 2}`; return (
- + + + + © Copyright 2024 by Swiss Post Ltd. + All rights reserved. + `; +} + +export const Default: Story = {}; diff --git a/packages/documentation/src/stories/components/breadcrumbs/breadcrumbs.docs.mdx b/packages/documentation/src/stories/raw-components/breadcrumbs/breadcrumbs.docs.mdx similarity index 100% rename from packages/documentation/src/stories/components/breadcrumbs/breadcrumbs.docs.mdx rename to packages/documentation/src/stories/raw-components/breadcrumbs/breadcrumbs.docs.mdx diff --git a/packages/documentation/src/stories/components/breadcrumbs/breadcrumbs.stories.ts b/packages/documentation/src/stories/raw-components/breadcrumbs/breadcrumbs.stories.ts similarity index 97% rename from packages/documentation/src/stories/components/breadcrumbs/breadcrumbs.stories.ts rename to packages/documentation/src/stories/raw-components/breadcrumbs/breadcrumbs.stories.ts index 60dcfad632..24c46fb995 100644 --- a/packages/documentation/src/stories/components/breadcrumbs/breadcrumbs.stories.ts +++ b/packages/documentation/src/stories/raw-components/breadcrumbs/breadcrumbs.stories.ts @@ -7,7 +7,7 @@ import { MetaComponent } from '@root/types'; const meta: MetaComponent = { id: '4347e5bf-8bf2-4f44-9075-9faaa53591ed', - title: 'Components/Breadcrumbs', + title: 'Raw Components/Breadcrumbs', component: 'swisspost-internet-breadcrumbs', tags: ['package:InternetHeader'], render: renderInternetBreadcrumbs, diff --git a/packages/documentation/src/stories/components/breadcrumbs/custom-items.ts b/packages/documentation/src/stories/raw-components/breadcrumbs/custom-items.ts similarity index 100% rename from packages/documentation/src/stories/components/breadcrumbs/custom-items.ts rename to packages/documentation/src/stories/raw-components/breadcrumbs/custom-items.ts diff --git a/packages/documentation/src/stories/components/footer/custom-footer-config.ts b/packages/documentation/src/stories/raw-components/footer/custom-footer-config.ts similarity index 100% rename from packages/documentation/src/stories/components/footer/custom-footer-config.ts rename to packages/documentation/src/stories/raw-components/footer/custom-footer-config.ts diff --git a/packages/documentation/src/stories/components/footer/footer.docs.mdx b/packages/documentation/src/stories/raw-components/footer/footer.docs.mdx similarity index 100% rename from packages/documentation/src/stories/components/footer/footer.docs.mdx rename to packages/documentation/src/stories/raw-components/footer/footer.docs.mdx diff --git a/packages/documentation/src/stories/components/footer/footer.stories.ts b/packages/documentation/src/stories/raw-components/footer/footer.stories.ts similarity index 98% rename from packages/documentation/src/stories/components/footer/footer.stories.ts rename to packages/documentation/src/stories/raw-components/footer/footer.stories.ts index 2c0dbec051..86fbed7f1d 100644 --- a/packages/documentation/src/stories/components/footer/footer.stories.ts +++ b/packages/documentation/src/stories/raw-components/footer/footer.stories.ts @@ -6,7 +6,7 @@ import { MetaComponent } from '@root/types'; const meta: MetaComponent = { id: '27fc009d-3eec-43a9-b3a2-55531e721817', - title: 'Components/Footer', + title: 'Raw Components/Footer', component: 'swisspost-internet-footer', tags: ['package:InternetHeader'], render: renderInternetFooter, diff --git a/packages/documentation/src/stories/components/header/components/header.markup.ts b/packages/documentation/src/stories/raw-components/header/components/header.markup.ts similarity index 100% rename from packages/documentation/src/stories/components/header/components/header.markup.ts rename to packages/documentation/src/stories/raw-components/header/components/header.markup.ts diff --git a/packages/documentation/src/stories/components/header/header.docs.mdx b/packages/documentation/src/stories/raw-components/header/header.docs.mdx similarity index 100% rename from packages/documentation/src/stories/components/header/header.docs.mdx rename to packages/documentation/src/stories/raw-components/header/header.docs.mdx diff --git a/packages/documentation/src/stories/components/header/header.stories.ts b/packages/documentation/src/stories/raw-components/header/header.stories.ts similarity index 94% rename from packages/documentation/src/stories/components/header/header.stories.ts rename to packages/documentation/src/stories/raw-components/header/header.stories.ts index 9b2cc7b912..21c5eb7178 100644 --- a/packages/documentation/src/stories/components/header/header.stories.ts +++ b/packages/documentation/src/stories/raw-components/header/header.stories.ts @@ -4,7 +4,7 @@ import HeaderMarkup from './components/header.markup'; const meta: MetaComponent = { id: 'header', - title: 'Components/Header', + title: 'Raw Components/Header', tags: ['package:HTML'], parameters: { layout: 'fullscreen', diff --git a/packages/styles/src/components/_index.scss b/packages/styles/src/components/_index.scss index 54a7d43734..51cda318d0 100644 --- a/packages/styles/src/components/_index.scss +++ b/packages/styles/src/components/_index.scss @@ -1,5 +1,7 @@ @forward './../variables/options'; +@use 'breakpoints'; +@use 'globals'; @use 'appstore-badge'; @use 'avatar'; @use 'badge'; diff --git a/packages/styles/src/components/appstore-badge.scss b/packages/styles/src/components/appstore-badge.scss index 0c4005bb9d..6c76601ba7 100644 --- a/packages/styles/src/components/appstore-badge.scss +++ b/packages/styles/src/components/appstore-badge.scss @@ -7,9 +7,12 @@ tokens.$default-map: components.$post-app-store-badge; .app-store-badge { display: inline-flex; border-radius: tokens.get('app-store-border-radius'); + height: tokens.get('app-store-height'); + vertical-align: text-bottom; img { display: block; - height: tokens.get('app-store-height'); + width: auto; + height: 100%; } } diff --git a/packages/styles/src/components/breakpoints.scss b/packages/styles/src/components/breakpoints.scss new file mode 100644 index 0000000000..3e36eafb96 --- /dev/null +++ b/packages/styles/src/components/breakpoints.scss @@ -0,0 +1,17 @@ +@use 'sass:list'; +@use 'sass:map'; +@use 'sass:math'; +@use '../variables/breakpoints'; + +:root { + $breakpoint-list: (); + + @each $key, $value in breakpoints.$grid-breakpoints { + $unitless-value: math.div($value, $value * 0 + 1); + $breakpoint-list: list.append($breakpoint-list, $unitless-value, comma); + } + + --post-breakpoint-widths: #{$breakpoint-list}; + --post-breakpoint-keys: #{map.keys(breakpoints.$grid-breakpoints)}; + --post-breakpoint-names: #{map.values(breakpoints.$grid-breakpoints-key-name-map)}; +} diff --git a/packages/styles/src/components/globals/_index.scss b/packages/styles/src/components/globals/_index.scss new file mode 100644 index 0000000000..99119af291 --- /dev/null +++ b/packages/styles/src/components/globals/_index.scss @@ -0,0 +1,3 @@ +@forward './../../variables/options'; + +@use 'post-footer'; diff --git a/packages/styles/src/components/globals/post-footer.scss b/packages/styles/src/components/globals/post-footer.scss new file mode 100644 index 0000000000..087415d8ab --- /dev/null +++ b/packages/styles/src/components/globals/post-footer.scss @@ -0,0 +1,92 @@ +@use '../../variables/color'; +@use '../../mixins/media'; +@use '../../mixins/list'; + +post-footer { + // mobile + --post-footer-grid-list-title-display: none; + --post-footer-grid-list-title-gap: 0; + --post-footer-grid-list-item-gap: 8px; + + --post-footer-socialmedia-list-item-gap: 8px; + --post-footer-app-list-item-gap: 8px; + --post-footer-businesssector-list-item-gap: 8px; + --post-footer-meta-list-item-gap: 8px; + + // tablet sm + @include media.min(sm) { + --post-footer-grid-list-title-display: block; + --post-footer-grid-list-title-gap: 8px; + + --post-footer-socialmedia-list-item-gap: 16px; + --post-footer-businesssector-list-item-gap: 24px; + --post-footer-meta-list-item-gap: 16px; + } + + // desktop lg + @include media.min(lg) { + --post-footer-meta-list-item-gap: 24px; + } + + :is(h3, .h3) { + margin: 0; + font-size: inherit; + } + + a { + &:not(.btn-icon, .app-store-badge) { + display: block; + text-decoration: none; + } + } + + post-list { + &[slot|='grid'] { + :is(h3, .h3) { + display: var(--post-footer-grid-list-title-display); + margin-block-end: var(--post-footer-grid-list-title-gap); + } + + > [role='list'] { + @include list.list-bullet($child-selector: 'post-list-item'); + margin-block: 0; + padding-inline-start: 0; + list-style: none; + + > post-list-item { + &::before { + display: none; + } + + ~ post-list-item { + margin-block-start: var(--post-footer-grid-list-item-gap); + } + } + } + } + + &:is([slot='socialmedia'], [slot='app'], [slot='businesssectors'], [slot='meta']) { + > [role='list'] { + @include list.list-inline($child-selector: 'post-list-item'); + margin: 0; + } + } + + &[slot='socialmedia'] > [role='list'] { + gap: var(--post-footer-socialmedia-list-item-gap); + } + + &[slot='app'] > [role='list'] { + gap: var(--post-footer-app-list-item-gap); + } + + &[slot='businesssectors'] > [role='list'] { + gap: var(--post-footer-businesssector-list-item-gap); + } + + &[slot='meta'] > [role='list'] { + row-gap: 0; + column-gap: var(--post-footer-meta-list-item-gap); + } + } +} diff --git a/packages/styles/src/mixins/_icon-button.scss b/packages/styles/src/mixins/_icon-button.scss index ec62913afc..78a3544d8a 100644 --- a/packages/styles/src/mixins/_icon-button.scss +++ b/packages/styles/src/mixins/_icon-button.scss @@ -13,12 +13,15 @@ tokens.$default-map: components.$post-icon-button; 'lg': 'large', ); $actual-size: map.get($size-map, $size); - min-width: tokens.get('icon-button-#{$actual-size}-outer'); + padding: 0; width: tokens.get('icon-button-#{$actual-size}-outer'); height: tokens.get('icon-button-#{$actual-size}-outer'); + min-height: 0; + vertical-align: text-bottom; > post-icon { - min-width: tokens.get('icon-button-#{$actual-size}-icon'); + display: block; + width: tokens.get('icon-button-#{$actual-size}-icon'); height: tokens.get('icon-button-#{$actual-size}-icon'); } } diff --git a/packages/styles/src/mixins/list.scss b/packages/styles/src/mixins/_list.scss similarity index 86% rename from packages/styles/src/mixins/list.scss rename to packages/styles/src/mixins/_list.scss index c654fb92e1..03a136a240 100644 --- a/packages/styles/src/mixins/list.scss +++ b/packages/styles/src/mixins/_list.scss @@ -1,7 +1,7 @@ @use '../functions/tokens'; @use '../tokens/elements'; -@mixin list-bullet() { +@mixin list-bullet($child-selector: 'li') { list-style: none; margin-block: tokens.get('list-bullet-margin-block', elements.$post-listbullet); padding-inline-start: calc( @@ -11,12 +11,12 @@ )} ); - > li { + > #{$child-selector} { margin: 0; padding-inline: 0; padding-block: tokens.get('list-bullet-item-text-padding-block', elements.$post-listbullet); - ~ li { + ~ #{$child-selector} { margin-block-start: tokens.get('list-bullet-item-gap-block', elements.$post-listbullet); } @@ -42,7 +42,7 @@ } } -@mixin list-number() { +@mixin list-number($child-selector: 'li') { margin-block: tokens.get('list-number-item-gap-block', elements.$post-listnumber); padding-inline-end: tokens.get('list-number-item-gap-block', elements.$post-listnumber); padding-inline-start: calc( @@ -50,7 +50,7 @@ tokens.get('list-number-item-icon-gap-inline', elements.$post-listnumber) ); - > li { + > #{$child-selector} { margin-block-end: tokens.get('list-number-margin-block', elements.$post-listnumber); padding-inline-start: tokens.get('list-number-item-icon-gap-inline', elements.$post-listnumber); padding-block: tokens.get('list-number-item-text-padding-block', elements.$post-listnumber); @@ -61,8 +61,8 @@ } } -@mixin list-inline() { - @include list-unstyled() { +@mixin list-inline($child-selector: 'li') { + @include list-unstyled($child-selector) { display: flex; flex-wrap: wrap; row-gap: tokens.get('list-bullet-item-gap-block', elements.$post-listbullet); @@ -73,14 +73,14 @@ } } -@mixin list-revert() { +@mixin list-revert($child-selector: 'li') { all: revert; @content; - > li { + > #{$child-selector} { all: revert; - ~ li { + ~ #{$child-selector} { all: revert; } @@ -90,17 +90,17 @@ } } -@mixin list-unstyled() { +@mixin list-unstyled($child-selector: 'li') { all: unset; display: block; list-style: none; @content; - > li { + > #{$child-selector} { all: unset; display: list-item; - ~ li { + ~ #{$child-selector} { all: unset; display: list-item; } diff --git a/packages/styles/src/variables/_breakpoints.scss b/packages/styles/src/variables/_breakpoints.scss index b78ff9ea19..06fa9bf964 100644 --- a/packages/styles/src/variables/_breakpoints.scss +++ b/packages/styles/src/variables/_breakpoints.scss @@ -5,3 +5,11 @@ $grid-breakpoints: ( lg: 1024px, xl: 1280px, ) !default; + +$grid-breakpoints-key-name-map: ( + xs: 'mobile', + sm: 'tablet', + md: 'tablet', + lg: 'desktop', + xl: 'desktop', +); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 147b78436b..5031b6aa97 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15803,7 +15803,7 @@ snapshots: axios@1.7.7: dependencies: - follow-redirects: 1.15.6(debug@4.3.7) + follow-redirects: 1.15.6(debug@4.3.6) form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -18513,7 +18513,7 @@ snapshots: http-proxy-middleware@2.0.6(@types/express@4.17.21): dependencies: '@types/http-proxy': 1.17.15 - http-proxy: 1.18.1(debug@4.3.7) + http-proxy: 1.18.1 is-glob: 4.0.3 is-plain-obj: 3.0.0 micromatch: 4.0.8 @@ -18533,6 +18533,14 @@ snapshots: transitivePeerDependencies: - supports-color + http-proxy@1.18.1: + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.15.6(debug@4.3.6) + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + http-proxy@1.18.1(debug@4.3.7): dependencies: eventemitter3: 4.0.7 @@ -18548,7 +18556,7 @@ snapshots: corser: 2.0.1 he: 1.2.0 html-encoding-sniffer: 3.0.0 - http-proxy: 1.18.1(debug@4.3.7) + http-proxy: 1.18.1 mime: 1.6.0 minimist: 1.2.8 opener: 1.5.2 @@ -19684,7 +19692,7 @@ snapshots: dom-serialize: 2.2.1 glob: 7.2.3 graceful-fs: 4.2.11 - http-proxy: 1.18.1(debug@4.3.7) + http-proxy: 1.18.1 isbinaryfile: 4.0.10 lodash: 4.17.21 log4js: 6.9.1