Skip to content

Commit

Permalink
feat(components): implemented accordion styles using tokens (#3858)
Browse files Browse the repository at this point in the history
Co-authored-by: Alizé Debray <[email protected]>
  • Loading branch information
schaertim and alizedebray authored Dec 4, 2024
1 parent 36433b6 commit 354aa2e
Show file tree
Hide file tree
Showing 15 changed files with 225 additions and 194 deletions.
5 changes: 5 additions & 0 deletions .changeset/grumpy-lobsters-agree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@swisspost/design-system-components': minor
---

Added the capability to use a slotted img as a logo inside ´accordion-items´.
5 changes: 5 additions & 0 deletions .changeset/itchy-meals-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@swisspost/design-system-documentation': minor
---

Added a ´post-accordion´ example showing logo usage.
5 changes: 5 additions & 0 deletions .changeset/large-hornets-complain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@swisspost/design-system-styles': patch
---

Updated the ´post-accordion´ styles to match the new Post design.
Original file line number Diff line number Diff line change
@@ -1,22 +1,149 @@
@use '@swisspost/design-system-styles/components/accordion';
@use '@swisspost/design-system-styles/mixins/accordion' as accordion-mx;
@use '@swisspost/design-system-styles/mixins/button' as button-mx;
@use '@swisspost/design-system-styles/mixins/focus' as focus-mx;
@use '@swisspost/design-system-styles/mixins/icons' as icon-mx;
@use '@swisspost/design-system-styles/mixins/utilities' as utility-mx;

@use '@swisspost/design-system-styles/tokens/components';
@use '@swisspost/design-system-styles/tokens/elements';
@use '@swisspost/design-system-styles/tokens/helpers';
@use '@swisspost/design-system-styles/functions/tokens';
@use '@swisspost/design-system-styles/variables/components/accordion';

tokens.$default-map: components.$post-accordion;

:host {
display: block;
&::after {
display: block;
content: '';
margin-top: calc(tokens.get('accordion-border-bottom-width') * -1);
border-bottom: tokens.get('accordion-border-bottom-width')
tokens.get('accordion-border-bottom-style') tokens.get('accordion-enabled-border');
}
&:has(.accordion-button:hover)::after {
border-color: tokens.get('accordion-hover-border');
}
}

.accordion-header {
color: tokens.get('accordion-enabled-fg');
font-size: tokens.get('accordion-header-font-size');
margin: 0;
line-height: tokens.get('heading-line-height', elements.$post-heading);
font-weight: tokens.get('heading-font-weight', elements.$post-heading);
}

.accordion-button {
@include button-mx.reset-button;
display: flex;
position: relative;
width: 100%;
align-items: center;
text-align: start;
gap: tokens.get('accordion-header-content-gap-inline');
padding: calc(
tokens.get('accordion-header-padding-block-open') +
tokens.get('accordion-group-border-top-width')
)
calc(
tokens.get('accordion-header-padding-inline') + tokens.get('accordion-border-bottom-width')
);
transition: accordion.$accordion-button-transition;
cursor: pointer;

&::before {
display: block;
position: absolute;
content: '';
inset: 0;
bottom: auto;
border-top: var(--post-accordion-button-border-top);
}

@include utility-mx.focus-style() {
border-radius: tokens.get('focus-border-radius', helpers.$post-focus);
}

slot::slotted(span[slot='header']) {
flex-grow: 1;
}

post-icon {
flex: none;
width: tokens.get('accordion-icon-size');
aspect-ratio: 1;
transition: accordion.$accordion-icon-transition;
}

&.collapsed {
padding-block-start: calc(
tokens.get('accordion-header-padding-block-closed') +
tokens.get('accordion-group-border-top-width')
);
padding-block-end: calc(
tokens.get('accordion-header-padding-block-closed') +
tokens.get('accordion-border-bottom-width')
);

post-icon {
transform: accordion.$accordion-icon-transform;
}
}

&:hover {
color: tokens.get('accordion-hover-fg');
}

> ::slotted(.text-truncate) {
display: block;
}

@include utility-mx.high-contrast-mode() {
&:hover,
&:focus-visible {
&:not(:disabled) {
outline: tokens.get('accordion-border-bottom-width') solid Highlight;
}
}

&:disabled {
opacity: 1 !important;
}
}
}

.accordion-body {
padding-block-start: tokens.get('accordion-content-padding-block-start');
padding-block-end: calc(
tokens.get('accordion-content-padding-block-end') + tokens.get('accordion-border-bottom-width')
);
padding-inline: tokens.get('accordion-content-padding-inline');
}

post-accordion-item::part(accordion-item) {
@include accordion-mx.background-color;
.logo-container {
display: none;

width: tokens.get('accordion-header-logo-size');
aspect-ratio: 1;

&.has-image {
display: block;
}

slot::slotted(img) {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
}

:host(:not(:first-of-type)) .accordion-item {
border-block-start: 0;
.accordion-button,
.accordion-body {
> ::slotted(:first-child) {
margin-block-start: 0 !important;
}

> ::slotted(:last-child) {
margin-block-end: 0 !important;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { HEADING_LEVELS, HeadingLevel } from '@/types';
import { checkEmptyOrOneOf } from '@/utils';

/**
* @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.
*/
Expand All @@ -20,6 +21,8 @@ export class PostAccordionItem {

@State() id: string;

@State() slottedLogo: HTMLElement;

/**
* If `true`, the element is collapsed otherwise it is displayed.
*/
Expand All @@ -40,14 +43,14 @@ export class PostAccordionItem {
);
}

componentDidLoad() {
this.validateHeadingLevel();
}

componentWillLoad() {
this.id = this.host.id || `a${crypto.randomUUID()}`;
}

componentDidLoad() {
this.validateHeadingLevel();
}

// capture to make sure the "collapsed" property is updated before the event is consumed
@Listen('postToggle', { capture: true })
onCollapseToggle(event: CustomEvent<boolean>): void {
Expand All @@ -67,6 +70,14 @@ export class PostAccordionItem {
return this.collapsible.toggle(force);
}

private onSlotLogoChange() {
this.slottedLogo = this.host.querySelector('img[slot="logo"]');
}

componentWillRender() {
this.slottedLogo = this.host.querySelector('img[slot="logo"]');
}

render() {
const HeadingTag = `h${this.headingLevel ?? 2}`;

Expand All @@ -76,7 +87,16 @@ export class PostAccordionItem {
<post-collapsible-trigger for={`${this.id}--collapse`}>
<HeadingTag class="accordion-header" id={`${this.id}--header`}>
<button type="button" class={`accordion-button${this.collapsed ? ' collapsed' : ''}`}>
<span
class={{
'logo-container': true,
'has-image': !!this.slottedLogo,
}}
>
<slot name="logo" onSlotchange={this.onSlotLogoChange.bind(this)}></slot>
</span>
<slot name="header" />
<post-icon name="2051"></post-icon>
</button>
</HeadingTag>
</post-collapsible-trigger>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Type: `Promise<boolean>`
| ----------- | ------------------------------------------------------------------- |
| `"default"` | Slot for placing content within the accordion item's body. |
| `"header"` | Slot for placing custom content within the accordion item's header. |
| `"logo"` | Slot for the placing a logo before the header. |


## Shadow Parts
Expand All @@ -52,12 +53,14 @@ Type: `Promise<boolean>`
### Depends on

- [post-collapsible-trigger](../post-collapsible-trigger)
- [post-icon](../post-icon)
- [post-collapsible](../post-collapsible)

### Graph
```mermaid
graph TD;
post-accordion-item --> post-collapsible-trigger
post-accordion-item --> post-icon
post-accordion-item --> post-collapsible
style post-accordion-item fill:#f9f,stroke:#333,stroke-width:4px
```
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
@use '@swisspost/design-system-styles/tokens/components';
@use '@swisspost/design-system-styles/functions/tokens';

tokens.$default-map: components.$post-accordion;

:host {
display: block;
}

::slotted(post-accordion-item) {
display: block;
}

::slotted(post-accordion-item:not(:only-of-type:first-of-type)) {
margin-top: calc(tokens.get('accordion-group-border-top-width') * -1);
}

::slotted(post-accordion-item:not(:only-of-type):first-of-type) {
--post-accordion-button-border-top: #{tokens.get('accordion-group-border-top-width')} #{tokens.get(
'accordion-border-top-style'
)} #{tokens.get('accordion-enabled-border')};
}
2 changes: 2 additions & 0 deletions packages/components/src/components/post-icon/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ some content

### Used by

- [post-accordion-item](../post-accordion-item)
- [post-banner](../post-banner)
- [post-breadcrumb-item](../post-breadcrumb-item)
- [post-card-control](../post-card-control)
Expand All @@ -32,6 +33,7 @@ some content
### Graph
```mermaid
graph TD;
post-accordion-item --> post-icon
post-banner --> post-icon
post-breadcrumb-item --> post-icon
post-card-control --> post-icon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import SampleCustomTrigger from './accordion-custom-trigger.sample?raw';
</nav>
</div>


<p className="lead">Toggle the visibility of a set of related content in your project.</p>

The `<post-accordion>` is a container for `<post-accordion-item>` components.
Expand Down Expand Up @@ -42,6 +43,13 @@ To allow multiple panels to be open at the same time, use the `multiple="true"`

<Canvas of={accordionStories.MultipleOpenPanels} />

### Logos

To enhance individual accordion items with a logo, add an `<img>` to the logo slot.
This will display the logo in front of the accordion item's heading.

<Canvas of={accordionStories.Logos}/>

### Initially Closed Panel

Use the `collapsed="true"` property on all `post-accordion-item` you want to be initially closed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const meta: MetaComponent<HTMLPostAccordionElement & HTMLPostCollapsibleElementE
args: {
multiple: false,
headingLevel: 4,
logoSrc: '',
},
argTypes: {
postToggle: {
Expand All @@ -31,6 +32,14 @@ const meta: MetaComponent<HTMLPostAccordionElement & HTMLPostCollapsibleElementE
type: { summary: 'CustomEvent<boolean>' },
},
},
logoSrc: {
control: 'text',
description:
'Define an image `src` to insert a custom image.<div className="mt-8 banner banner-info banner-sm">Do you need an example? Try our logo <strong>/assets/images/logo-swisspost.svg</strong>.</div>',
table: {
category: 'Content',
},
},
},
};

Expand All @@ -41,14 +50,17 @@ function getAccordionItemContent(position: number | string, headingLevel?: numbe
const level = headingLevel ? html` <code>h${headingLevel}</code>` : nothing;
return html`
<span slot="header">Titulum ${position}${level}</span>
<p>Contentus momentus vero siteos et accusam iretea et justo.</p>
<div>
<p>Contentus momentus vero siteos et accusam iretea et justo.</p>
</div>
`;
}

function getDefaultAccordionItem(args: Partial<HTMLPostAccordionElement>, index: number) {
const isCollapsed = !!args.multiple && index > 0;
return html`
<post-accordion-item collapsed=${ifDefined(isCollapsed || undefined)}>
<post-accordion-item ?collapsed=${isCollapsed}
>${args.logoSrc ? html`<img slot="logo" src="${args.logoSrc}" alt="logo" />` : nothing}
${getAccordionItemContent(index + 1)}
</post-accordion-item>
`;
Expand Down Expand Up @@ -76,6 +88,12 @@ export const Default: Story = {
},
};

export const Logos: Story = {
args: {
logoSrc: '/assets/images/logo-swisspost.svg',
},
};

export const MultipleOpenPanels: Story = {
args: {
multiple: true,
Expand Down
1 change: 0 additions & 1 deletion packages/styles/src/components/_index.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
@forward './../variables/options';

@use 'accordion';
@use 'appstore-badge';
@use 'avatar';
@use 'badge';
Expand Down
Loading

0 comments on commit 354aa2e

Please sign in to comment.