Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fresh-rules-make.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@swisspost/design-system-documentation': patch
---

Cleaned up card documentation.
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,3 @@ To read more about it, read our [post-linkarea docs](/?path=/docs/1d52b794-768b-

<Canvas sourceState="shown" of={CardStories.Foundation} />

## Examples

### Palettes

Cards should never be the same color as its section's background. Therefore, depending on the palette, the card's background color will adapt.

<Canvas sourceState="hidden" of={CardStories.Palette} />

### Custom Content

A card is simply a box which can contain a wide variety of content, including images, text, links, and more.

<Canvas of={CardStories.CustomContent} />

### List Interactive

Create lists of content in a card by simply using a list interactive in place of the card body.

<Canvas of={CardStories.ListGroup} />
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Args, StoryContext, StoryObj } from '@storybook/web-components-vite';
import meta, { CustomContent, Default, ListGroup } from './card.stories';
import meta, { Default } from './card.stories';
import { html } from 'lit';
import { schemes } from '@/shared/snapshots/schemes';
import { bombArgs } from '@/utils';
Expand All @@ -19,8 +19,6 @@ export const Card: Story = {
const defaultTemplateVariants =
// Layout related combinations
bombArgs({
showImage: [false, true],
imagePosition: meta.argTypes?.imagePosition?.options,
action: ['none', 'button', 'links'],
})
.filter(args => !(!args.showImage && args.imagePosition === 'bottom'))
Expand All @@ -33,32 +31,13 @@ export const Card: Story = {
`,
);

// Define custom template variant
const customTemplateVariant = html`
<div class="p-16 col-6">
${CustomContent.render &&
CustomContent.render({ ...meta.args, ...CustomContent.args }, context)}
</div>
`;

// Define list group variant
const listGroupVariant = html`
<div class="p-16 col-6">
${ListGroup.render && ListGroup.render({ ...meta.args, ...ListGroup.args }, context)}
</div>
`;

return schemes(
() =>
html`
<div class="row">
<h1>Cards</h1>
<h2 class="mt-32">Default template variants cards</h2>
${defaultTemplateVariants}
<h2 class="mt-32">Custom template variants cards</h2>
${customTemplateVariant}
<h2 class="mt-32">Custom template variants cards</h2>
${listGroupVariant}
</div>
`,
{
Expand Down
217 changes: 74 additions & 143 deletions packages/documentation/src/stories/components/card/card.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,65 +17,74 @@ const meta: MetaComponent = {
},
},
args: {
showImage: false,
imagePosition: 'top',
content: "<h5>This is my card's title</h5><p>This is my card's content.</p>",
title: "This is my card's title",
body: "This is my card's content.",
interactive: false,
action: 'button',
interactiveAction: 'button',
label: 'Button Text',
variant: 'btn-primary',
},
argTypes: {
showImage: {
name: 'Show Image',
description: 'When set to `true`, an image is shown in the card.',
control: {
type: 'boolean',
},
table: {
category: 'Card Image',
},
title: {
name: 'Title',
control: { type: 'text' },
table: { category: 'Card Content' },
},
imagePosition: {
name: 'Image Position',
description: 'Defines the position of the image within the card.',
if: {
arg: 'showImage',
},
body: {
name: 'Body',
description: 'The content within the card.',
control: { type: 'text' },
table: { category: 'Card Content' },
},
label: {
name: 'label',
description: 'The content button',
control: { type: 'text' },
table: { category: 'Card Content' },
},
interactive: {
name: 'Interactive',
description: 'Wrap in <post-linkarea> and use Interactive Action.',
control: { type: 'boolean' },
table: { category: 'General' },
},
action: {
name: 'Action',
description: 'Non-interactive card action.',
control: {
type: 'inline-radio',
labels: {
top: 'Top',
bottom: 'Bottom',
},
},
options: ['top', 'bottom'],
table: {
category: 'Card Image',
labels: { button: 'Button', link: 'Link', none: 'None' },
},
options: ['button', 'link', 'none'],
if: { arg: 'interactive', eq: false },
table: { category: 'Card Content' },
},
content: {
name: 'Content',
description: 'The content within the card.',
interactiveAction: {
name: 'Interactive Action',
description: 'Interactive card action.',
control: {
type: 'text',
},
table: {
category: 'Card Content',
type: 'inline-radio',
labels: { button: 'Button', link: 'Link' },
},
options: ['button', 'link'],
if: { arg: 'interactive', eq: true },
table: { category: 'Card Content' },
},
action: {
name: 'Action',
description: 'Defines the call-to-action to show in the card.',
variant: {
name: 'Variant',
description: 'Defines a style variant.',
control: {
type: 'inline-radio',
labels: {
button: 'Button',
links: 'Links',
none: 'None',
'btn-primary': 'Primary',
'btn-secondary': 'Secondary',
'btn-tertiary': 'Tertiary',
},
},
options: ['button', 'links', 'none'],
table: {
category: 'Card Content',
},
options: ['btn-primary', 'btn-secondary', 'btn-tertiary'],
table: { category: 'Card Content' },
if: { arg: 'action', eq: 'button' },
},
},
};
Expand All @@ -91,54 +100,50 @@ function clickBlocker(story: StoryFn, context: StoryContext) {

function gridContainer(story: StoryFn, context: StoryContext) {
return html`
<div class="row gy-16">
<div class="col-lg-4 col-sm-6 col-12">${story(context.args, context)}</div>
<div class="container">
<div class="row gy-16 ">
<div class="col-lg-4 col-sm-6 col-12">${story(context.args, context)}</div>
</div>
</div>
`;
}

// RENDERER
function getCardLinks() {
return html`
${['Link Text', 'More Link'].map(label => html` <a class="card-link" href="#">${label}</a> `)}
<div class="card-links">
<a href="#" class="btn-link px-0">Link Text</a>
</div>
`;
}

function getCardButton() {
function getCardButton({ label, variant }: Args) {
return html`
<button class="btn btn-primary">
<span>Button Text</span>
<button class="btn ${variant}">
<span>${label}</span>
</button>
`;
}

function getCardContent({ content, action }: Args) {
function getCardContent(args: Args) {
const { action, title, body = '' } = args;
return html`
<div class="card-body">
${unsafeHTML(content)}
${title ? html`<h3 class="card-title">${title}</h3>` : nothing} ${unsafeHTML(body)}
${choose(
action,
[
['button', getCardButton],
['links', getCardLinks],
['button', () => getCardButton(args)],
['link', getCardLinks],
],
() => html` ${nothing} `,
)}
</div>
`;
}

function getCardImage() {
return html` <img src="https://picsum.photos/id/38/500/300" alt="" /> `;
}

function renderCardContent(args: Args) {
const { showImage, imagePosition } = args;

return html`
${showImage && imagePosition === 'top' ? getCardImage() : nothing} ${getCardContent(args)}
${showImage && imagePosition === 'bottom' ? getCardImage() : nothing}
`;
return html` ${getCardContent(args)} `;
}

function renderNoninteractiveCard(args: Args) {
Expand All @@ -152,7 +157,11 @@ function renderInteractiveCard(args: Args) {
const renderSimpleInteractiveCard = html`
<post-linkarea class="card">
<div class="card-body">
<p><a href="http://google.com">Interactive card</a></p>
<p>
<a href="http://google.com"
>Interactive card <post-icon name="arrowright" aria-hidden="true"></post-icon
></a>
</p>
</div>
</post-linkarea>
`;
Expand All @@ -163,7 +172,7 @@ type Story = StoryObj;
export const Default: Story = {
decorators: [gridContainer],
render: (args: Args) =>
html`${args.action === 'button' ? renderInteractiveCard(args) : renderNoninteractiveCard(args)}`,
html`${args.interactive ? renderInteractiveCard(args) : renderNoninteractiveCard(args)}`,
};

export const Foundation: Story = {
Expand All @@ -177,81 +186,3 @@ export const Foundation: Story = {
${renderSimpleInteractiveCard}
`,
};

export const Palette: Story = {
parameters: {
layout: 'fullscreen',
},
render: () => html`
<div class="palette palette-default">
<div class="container py-32">
<div class="row gy-16">
<div class="col-sm-6 col-12">${renderSimpleInteractiveCard}</div>
<div class="col-sm-6 col-12">${renderSimpleInteractiveCard}</div>
</div>
</div>
</div>
<div class="palette palette-alternate">
<div class="container py-32">
<div class="row gy-16">
<div class="col-sm-6 col-12">${renderSimpleInteractiveCard}</div>
<div class="col-sm-6 col-12">${renderSimpleInteractiveCard}</div>
</div>
</div>
</div>
`,
};

export const ListGroup: Story = {
decorators: [gridContainer],
render: () => {
return html`
<div class="card">
<ul class="list-interactive">
${['First Item', 'Second Item', 'Another Item'].map(
label => html` <li class="list-interactive-item">${label}</li> `,
)}
</ul>
</div>
`;
},
};

export const CustomContent: Story = {
decorators: [gridContainer],
render: () => {
return html`
<div class="card">
<div class="d-flex px-16 py-32 gap-16 align-items-center">
<post-icon aria-hidden="true" scale="1.5" name="profile"></post-icon>
<h3 class="fw-bold my-0 me-auto">User Details</h3>
<a href="#" aria-labelledby="details-title">
<post-icon aria-hidden="true" name="arrowright"></post-icon>
<span class="visually-hidden">Account Management</span>
</a>
</div>
<ul class="list-interactive">
<li class="list-interactive-item d-flex align-items-center justify-content-between">
<address class="mb-0">
Mr<br />First Name Last Name<br />Street 1<br />1234 City
</address>
<a href="#">
<post-icon aria-label="Edit Address" name="edit"></post-icon>
<span class="visually-hidden">Edit Address</span>
</a>
</li>
<li class="list-interactive-item d-flex align-items-center justify-content-between">
<p class="mb-0">Language: <span class="fw-bold">English</span></p>
<a href="#">
<post-icon aria-label="Edit Language" name="edit"></post-icon>
<span class="visually-hidden">Edit Language</span>
</a>
</li>
</ul>
<div class="card-links p-16">
<a href="#">Add Address</a>
</div>
</div>
`;
},
};
Loading
Loading