diff --git a/.changeset/real-gorillas-behave.md b/.changeset/real-gorillas-behave.md new file mode 100644 index 0000000000..e90fc39826 --- /dev/null +++ b/.changeset/real-gorillas-behave.md @@ -0,0 +1,6 @@ +--- +'@swisspost/design-system-documentation': minor +'@swisspost/design-system-components': minor +--- + +Added the `post-breadcrumb` component to provide a standalone breadcrumb navigation solution. diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index 075f07063b..76172f1f38 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -97,6 +97,16 @@ export namespace Components { */ "type": BannerType; } + interface PostBreadcrumb { + /** + * The text label for the home breadcrumb item. + */ + "homeText": string; + /** + * The URL for the home breadcrumb item. + */ + "homeUrl": string; + } interface PostBreadcrumbItem { /** * The optional URL to which the breadcrumb item will link. @@ -554,6 +564,12 @@ declare global { prototype: HTMLPostBannerElement; new (): HTMLPostBannerElement; }; + interface HTMLPostBreadcrumbElement extends Components.PostBreadcrumb, HTMLStencilElement { + } + var HTMLPostBreadcrumbElement: { + prototype: HTMLPostBreadcrumbElement; + new (): HTMLPostBreadcrumbElement; + }; interface HTMLPostBreadcrumbItemElement extends Components.PostBreadcrumbItem, HTMLStencilElement { } var HTMLPostBreadcrumbItemElement: { @@ -835,6 +851,7 @@ declare global { "post-avatar": HTMLPostAvatarElement; "post-back-to-top": HTMLPostBackToTopElement; "post-banner": HTMLPostBannerElement; + "post-breadcrumb": HTMLPostBreadcrumbElement; "post-breadcrumb-item": HTMLPostBreadcrumbItemElement; "post-card-control": HTMLPostCardControlElement; "post-closebutton": HTMLPostClosebuttonElement; @@ -933,6 +950,16 @@ declare namespace LocalJSX { */ "type"?: BannerType; } + interface PostBreadcrumb { + /** + * The text label for the home breadcrumb item. + */ + "homeText"?: string; + /** + * The URL for the home breadcrumb item. + */ + "homeUrl"?: string; + } interface PostBreadcrumbItem { /** * The optional URL to which the breadcrumb item will link. @@ -1261,6 +1288,7 @@ declare namespace LocalJSX { "post-avatar": PostAvatar; "post-back-to-top": PostBackToTop; "post-banner": PostBanner; + "post-breadcrumb": PostBreadcrumb; "post-breadcrumb-item": PostBreadcrumbItem; "post-card-control": PostCardControl; "post-closebutton": PostClosebutton; @@ -1300,6 +1328,7 @@ declare module "@stencil/core" { "post-avatar": LocalJSX.PostAvatar & JSXBase.HTMLAttributes; "post-back-to-top": LocalJSX.PostBackToTop & JSXBase.HTMLAttributes; "post-banner": LocalJSX.PostBanner & JSXBase.HTMLAttributes; + "post-breadcrumb": LocalJSX.PostBreadcrumb & JSXBase.HTMLAttributes; "post-breadcrumb-item": LocalJSX.PostBreadcrumbItem & JSXBase.HTMLAttributes; /** * @class PostCardControl - representing a stencil component diff --git a/packages/components/src/components/post-breadcrumb-item/post-breadcrumb-item.scss b/packages/components/src/components/post-breadcrumb-item/post-breadcrumb-item.scss index a1c5c021e7..5d827d7593 100644 --- a/packages/components/src/components/post-breadcrumb-item/post-breadcrumb-item.scss +++ b/packages/components/src/components/post-breadcrumb-item/post-breadcrumb-item.scss @@ -2,34 +2,43 @@ @use '@swisspost/design-system-styles/mixins/media'; @use '@swisspost/design-system-styles/functions/tokens'; @use '@swisspost/design-system-styles/tokens/components'; +@use '@swisspost/design-system-styles/tokens/helpers'; @use '@swisspost/design-system-styles/mixins/utilities'; tokens.$default-map: components.$post-breadcrumb; :host { - display: inline-block; - @include utilities.focus-style; -} - -.breadcrumb-item { - display: inline-flex; + display: flex; align-items: center; - justify-content: center; - padding-block: tokens.get('breadcrumb-padding-block-text'); + justify-content: start; gap: tokens.get('breadcrumb-gap-inline-inner'); - color: tokens.get('breadcrumb-enabled-fg'); - text-decoration: tokens.get('breadcrumb-link-enabled-text-decoration'); post-icon { + box-sizing: border-box; height: tokens.get('breadcrumb-icon-size'); width: tokens.get('breadcrumb-icon-size'); + padding-block: tokens.get('breadcrumb-padding-block-icon-link'); + padding-inline: tokens.get('breadcrumb-padding-inline-icon-link'); } +} + +.breadcrumb-item { + white-space: nowrap; + line-height: 150%; + padding-block: tokens.get('breadcrumb-padding-block-text'); + color: tokens.get('breadcrumb-enabled-fg'); + text-decoration: tokens.get('breadcrumb-link-enabled-text-decoration'); + @include utilities.focus-style(); &:hover { color: tokens.get('breadcrumb-hover-fg'); text-decoration: tokens.get('breadcrumb-link-hover-text-decoration'); } + &:focus-visible { + border-radius: tokens.get('focus-border-radius', helpers.$post-focus); + } + @include utilities.high-contrast-mode() { &, &:focus, diff --git a/packages/components/src/components/post-breadcrumb-item/post-breadcrumb-item.tsx b/packages/components/src/components/post-breadcrumb-item/post-breadcrumb-item.tsx index 29380dd7f1..6a86b2ea09 100644 --- a/packages/components/src/components/post-breadcrumb-item/post-breadcrumb-item.tsx +++ b/packages/components/src/components/post-breadcrumb-item/post-breadcrumb-item.tsx @@ -45,13 +45,24 @@ export class PostBreadcrumbItem { this.validateUrl(); } + private handleKeyDown(event: KeyboardEvent) { + if (event.key === 'Enter' || event.key === ' ') { + const linkElement = this.host.shadowRoot?.querySelector('a'); + if (linkElement) { + event.preventDefault(); + (linkElement as HTMLElement).click(); + } + } + } + render() { const BreadcrumbTag = this.validUrl ? 'a' : 'span'; return ( - - + + this.handleKeyDown(event)}> diff --git a/packages/components/src/components/post-breadcrumb-item/readme.md b/packages/components/src/components/post-breadcrumb-item/readme.md index 660b1142c6..ea72aec663 100644 --- a/packages/components/src/components/post-breadcrumb-item/readme.md +++ b/packages/components/src/components/post-breadcrumb-item/readme.md @@ -21,6 +21,10 @@ ## Dependencies +### Used by + + - [post-breadcrumb](../post-breadcrumb) + ### Depends on - [post-icon](../post-icon) @@ -29,6 +33,7 @@ ```mermaid graph TD; post-breadcrumb-item --> post-icon + post-breadcrumb --> post-breadcrumb-item style post-breadcrumb-item fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/packages/components/src/components/post-breadcrumb/post-breadcrumb.scss b/packages/components/src/components/post-breadcrumb/post-breadcrumb.scss new file mode 100644 index 0000000000..567d410736 --- /dev/null +++ b/packages/components/src/components/post-breadcrumb/post-breadcrumb.scss @@ -0,0 +1,203 @@ +@use 'sass:map'; +@use '@swisspost/design-system-styles/mixins/media'; +@use '@swisspost/design-system-styles/functions/tokens'; +@use '@swisspost/design-system-styles/tokens/components'; +@use '@swisspost/design-system-styles/tokens/elements'; +@use '@swisspost/design-system-styles/mixins/utilities'; +@use '@swisspost/design-system-styles/core' as post; +@use '@swisspost/design-system-styles/tokens/helpers'; + +tokens.$default-map: components.$post-breadcrumb; + +:host { + display: flex; + align-items: center; +} + +.breadcrumbs-nav { + display: flex; + align-items: center; + +} + +.hidden-items { + gap: tokens.get('breadcrumb-gap-inline-outer'); + position: absolute; + height: 0; + overflow: hidden; + white-space: nowrap; +} + +.breadcrumbs-list { + display: flex; + flex-wrap: nowrap; + position: relative; + margin: 0; + padding: 0; + list-style: none; + align-items: center; + height: 100%; + gap: tokens.get('breadcrumb-gap-inline-outer'); +} + +post-icon { + display: inline-block; + box-sizing: border-box; + color: tokens.get('breadcrumb-enabled-fg'); + height: tokens.get('breadcrumb-icon-size'); + width: tokens.get('breadcrumb-icon-size'); +} + +.breadcrumb-item-icon { + padding-block: tokens.get('breadcrumb-padding-block-icon-link'); + padding-inline: tokens.get('breadcrumb-padding-inline-icon-link'); +} + +li { + a { + display: flex; + align-items: center; + @include utilities.focus-style; + + &:focus { + border-radius: tokens.get('focus-border-radius', helpers.$post-focus); + } + + .home-icon { + padding-block: tokens.get('breadcrumb-padding-block-icon-home'); + padding-inline: tokens.get('breadcrumb-padding-inline-icon-home'); + + &:hover { + color: tokens.get('breadcrumb-hover-fg'); + } + + @include utilities.high-contrast-mode() { + a, + &:focus, + &:hover { + color: CanvasText !important; + } + } + } + } +} + +.menu-trigger-wrapper { + display: flex; + align-items: center; + gap: tokens.get('breadcrumb-gap-inline-inner'); +} + +.actual-menu { + display: flex; + align-items: center; +} + +post-menu-trigger { + display: flex; + align-items: center; + padding-block: tokens.get('breadcrumb-padding-block-text'); + @include utilities.focus-style; + + &:focus { + border-radius: tokens.get('focus-border-radius', helpers.$post-focus); + } + + button { + background: none; + border: none; + line-height: 150%; + font-size: tokens.get('body-font-size', elements.$post-body); + cursor: pointer; + padding: 0; + color: tokens.get('breadcrumb-enabled-fg'); + + &:hover { + color: tokens.get('breadcrumb-hover-fg'); + } + + @include utilities.high-contrast-mode() { + a, + &:focus, + &:hover { + color: LinkText !important; + } + } + } +} + +post-menu::part(popover-container) { + display: flex; + flex-direction: column; + align-items: start; + padding: 0.6rem; + gap: tokens.get('breadcrumb-gap-inline-outer'); + + ::slotted(post-menu-item:not(:last-child)) { + margin-bottom: tokens.get('breadcrumb-gap-inline-outer'); + } +} + +.breadcrumb-item { + display: flex; + align-items: center; + justify-content: center; + gap: tokens.get('breadcrumb-gap-inline-inner'); + + a { + text-decoration: none; + color: inherit; + line-height: 150%; + padding-block: tokens.get('breadcrumb-padding-block-text'); + font-size: tokens.get('body-font-size', elements.$post-body); + @include utilities.focus-style; + + &:hover { + color: tokens.get('breadcrumb-hover-fg'); + text-decoration: tokens.get('breadcrumb-link-hover-text-decoration'); + } + + &:focus-visible { + border-radius: tokens.get('focus-border-radius', helpers.$post-focus); + } + } + + span { + &:hover { + color: tokens.get('breadcrumb-hover-fg'); + text-decoration: tokens.get('breadcrumb-link-hover-text-decoration'); + } + + &:focus-visible { + border-radius: tokens.get('focus-border-radius', helpers.$post-focus); + } + } + + @include utilities.high-contrast-mode() { + a, + &:focus, + &:hover { + color: LinkText !important; + } + + &:visited { + color: VisitedText !important; + } + } +} + +post-breadcrumb-item:last-of-type { + pointer-events: none; + color: tokens.get('breadcrumb-selected-fg'); + font-weight: tokens.get('breadcrumb-selected-font-weight'); + text-decoration: tokens.get('breadcrumb-link-selected-text-decoration'); + + &:hover { + color: tokens.get('breadcrumb-selected-fg'); + text-decoration: none; + } +} + +.visually-hidden { + @include post.visually-hidden(); +} diff --git a/packages/components/src/components/post-breadcrumb/post-breadcrumb.tsx b/packages/components/src/components/post-breadcrumb/post-breadcrumb.tsx new file mode 100644 index 0000000000..2e428f56c0 --- /dev/null +++ b/packages/components/src/components/post-breadcrumb/post-breadcrumb.tsx @@ -0,0 +1,206 @@ +import { Component, Element, h, Host, Prop, State, Watch } from '@stencil/core'; +import { version } from '@root/package.json'; +import { checkUrl, debounce } from '@/utils'; + +@Component({ + tag: 'post-breadcrumb', + styleUrl: 'post-breadcrumb.scss', + shadow: true, +}) +export class PostBreadcrumb { + @Element() host: HTMLPostBreadcrumbElement; + + /** + * The URL for the home breadcrumb item. + */ + @Prop() homeUrl: string; + + /** + * The text label for the home breadcrumb item. + */ + @Prop() homeText: string = 'Home'; + + @State() breadcrumbItems: { url: string; text: string }[] = []; + @State() isConcatenated: boolean; + @State() lastWindowWidth: number; + + private breadcrumbNavRef?: HTMLElement; + private lastItem: { url: string; text: string }; + + @Watch('homeUrl') + validateUrl() { + checkUrl(this.homeUrl, 'The "url" property of the home-icon is invalid'); + } + + componentWillLoad() { + this.updateBreadcrumbItems(); + } + + componentDidLoad() { + window.addEventListener('resize', this.handleResize); + this.waitForBreadcrumbRef(); + } + + disconnectedCallback() { + window.removeEventListener('resize', this.handleResize); + } + + // Waits for breadcrumb navigation reference to be available + private waitForBreadcrumbRef = debounce(() => { + if (this.breadcrumbNavRef?.clientWidth > 0) { + this.checkConcatenation(); + } else { + this.waitForBreadcrumbRef(); + } + }, 50); + + // Updates breadcrumb items and sets the last item + private updateBreadcrumbItems() { + this.breadcrumbItems = Array.from( + this.host.querySelectorAll('post-breadcrumb-item') + ).map((item) => ({ + text: item.textContent || '', + url: item.getAttribute('url') || '', + })); + this.lastItem = this.breadcrumbItems[this.breadcrumbItems.length - 1]; + } + + // Handles resizing to check concatenation + private handleResize = () => { + if (window.innerWidth === this.lastWindowWidth) return; + this.lastWindowWidth = window.innerWidth; + this.checkConcatenation(); + }; + + // Determines parent width for concatenation logic + private getParentWidth(): number { + let parent = this.host.parentNode; + while (parent && !(parent instanceof HTMLElement)) { + parent = parent.parentNode; + } + return parent instanceof HTMLElement ? parent.clientWidth : window.innerWidth; + } + + private checkConcatenation() { + if (!this.breadcrumbNavRef) return; + + const visibleWidth = this.getParentWidth(); + + // Measure all hidden breadcrumb items + const hiddenItems = Array.from( + this.host.shadowRoot?.querySelectorAll('.hidden-breadcrumb-item') || [] + ); + + const totalWidth = hiddenItems.reduce((accum, element) => { + const rect = (element as HTMLElement).getBoundingClientRect(); + return accum + rect.width; + }, 0); + + this.isConcatenated = totalWidth > visibleWidth; + } + + // Handles breadcrumb item click to open the menu + private handleBreadcrumbItemClick() { + if (this.host.shadowRoot) { + const menuTrigger = this.host.shadowRoot + ?.querySelector('.menu-trigger-wrapper') + ?.querySelector('button'); + + if (menuTrigger) { + menuTrigger.click(); + } + } + } + + render() { + const visibleItems = this.breadcrumbItems.slice(0, -1); + + return ( + + + + ); + } +} diff --git a/packages/components/src/components/post-breadcrumb/readme.md b/packages/components/src/components/post-breadcrumb/readme.md new file mode 100644 index 0000000000..916ff74906 --- /dev/null +++ b/packages/components/src/components/post-breadcrumb/readme.md @@ -0,0 +1,41 @@ +# post-breadcrumbs-new + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ---------- | ----------- | -------------------------------------------- | -------- | ----------- | +| `homeText` | `home-text` | The text label for the home breadcrumb item. | `string` | `'Home'` | +| `homeUrl` | `home-url` | The URL for the home breadcrumb item. | `string` | `undefined` | + + +## Dependencies + +### Depends on + +- [post-icon](../post-icon) +- [post-menu-trigger](../post-menu-trigger) +- [post-menu](../post-menu) +- [post-menu-item](../post-menu-item) +- [post-breadcrumb-item](../post-breadcrumb-item) + +### Graph +```mermaid +graph TD; + post-breadcrumb --> post-icon + post-breadcrumb --> post-menu-trigger + post-breadcrumb --> post-menu + post-breadcrumb --> post-menu-item + post-breadcrumb --> post-breadcrumb-item + post-menu --> post-popovercontainer + post-breadcrumb-item --> post-icon + style post-breadcrumb fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/components/src/components/post-icon/readme.md b/packages/components/src/components/post-icon/readme.md index b5967432b0..9160671c20 100644 --- a/packages/components/src/components/post-icon/readme.md +++ b/packages/components/src/components/post-icon/readme.md @@ -25,6 +25,8 @@ some content - [post-accordion-item](../post-accordion-item) - [post-back-to-top](../post-back-to-top) - [post-banner](../post-banner) + - [post-breadcrumb](../post-breadcrumb) + - [post-breadcrumb-item](../post-breadcrumb-item) - [post-breadcrumb-item](../post-breadcrumb-item) - [post-card-control](../post-card-control) - [post-closebutton](../post-closebutton) @@ -38,6 +40,8 @@ graph TD; post-accordion-item --> post-icon post-back-to-top --> post-icon post-banner --> post-icon + post-breadcrumb --> post-icon + post-breadcrumb-item --> post-icon post-breadcrumb-item --> post-icon post-card-control --> post-icon post-closebutton --> post-icon diff --git a/packages/components/src/components/post-menu-item/readme.md b/packages/components/src/components/post-menu-item/readme.md index d6c88bc316..eeeef359b7 100644 --- a/packages/components/src/components/post-menu-item/readme.md +++ b/packages/components/src/components/post-menu-item/readme.md @@ -5,6 +5,19 @@ +## Dependencies + +### Used by + + - [post-breadcrumb](../post-breadcrumb) + +### Graph +```mermaid +graph TD; + post-breadcrumb --> post-menu-item + style post-menu-item fill:#f9f,stroke:#333,stroke-width:4px +``` + ---------------------------------------------- *Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/components/src/components/post-menu-trigger/readme.md b/packages/components/src/components/post-menu-trigger/readme.md index 5198f2ceaf..d8af67b195 100644 --- a/packages/components/src/components/post-menu-trigger/readme.md +++ b/packages/components/src/components/post-menu-trigger/readme.md @@ -16,11 +16,13 @@ ### Used by + - [post-breadcrumb](../post-breadcrumb) - [post-language-switch](../post-language-switch) ### Graph ```mermaid graph TD; + post-breadcrumb --> post-menu-trigger post-language-switch --> post-menu-trigger style post-menu-trigger fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/packages/components/src/components/post-menu/post-menu.tsx b/packages/components/src/components/post-menu/post-menu.tsx index dd5c90f3bf..865a24a729 100644 --- a/packages/components/src/components/post-menu/post-menu.tsx +++ b/packages/components/src/components/post-menu/post-menu.tsx @@ -2,6 +2,7 @@ import { Component, Element, Event, EventEmitter, h, Host, Method, Prop, State } import { Placement } from '@floating-ui/dom'; import { version } from '@root/package.json'; import { isFocusable } from '@/utils/is-focusable'; +import { getRoot } from '@/utils'; @Component({ tag: 'post-menu', @@ -20,7 +21,7 @@ export class PostMenu { TAB: 'Tab', HOME: 'Home', END: 'End', - ESCAPE: 'Escape' + ESCAPE: 'Escape', }; @Element() host: HTMLPostMenuElement; @@ -45,7 +46,10 @@ export class PostMenu { **/ @Event() toggleMenu: EventEmitter; + private root?: Document | ShadowRoot; + connectedCallback() { + this.root = getRoot(this.host); this.host.addEventListener('keydown', this.handleKeyDown); this.host.addEventListener('click', this.handleClick); } @@ -79,7 +83,7 @@ export class PostMenu { async show(target: HTMLElement) { if (this.popoverRef) { await this.popoverRef.show(target); - this.lastFocusedElement = document.activeElement as HTMLElement; + this.lastFocusedElement = this.root.activeElement as HTMLElement; // Use root's activeElement const menuItems = this.getSlottedItems(); if (menuItems.length > 0) { @@ -131,7 +135,7 @@ export class PostMenu { return; } - const currentFocusedElement = document.activeElement as HTMLElement; + const currentFocusedElement = this.root.activeElement as HTMLElement; // Use root's activeElement let currentIndex = menuItems.findIndex(el => el === currentFocusedElement); switch (e.key) { @@ -185,7 +189,7 @@ export class PostMenu { return ( (this.popoverRef = e)}> -
+
diff --git a/packages/components/src/components/post-menu/readme.md b/packages/components/src/components/post-menu/readme.md index 2372e47c18..3361253421 100644 --- a/packages/components/src/components/post-menu/readme.md +++ b/packages/components/src/components/post-menu/readme.md @@ -64,10 +64,18 @@ Type: `Promise` +## Shadow Parts + +| Part | Description | +| --------------------- | ----------- | +| `"popover-container"` | | + + ## Dependencies ### Used by + - [post-breadcrumb](../post-breadcrumb) - [post-language-switch](../post-language-switch) ### Depends on @@ -78,6 +86,7 @@ Type: `Promise` ```mermaid graph TD; post-menu --> post-popovercontainer + post-breadcrumb --> post-menu post-language-switch --> post-menu style post-menu fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 5724f2d27d..ee02397ab8 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -6,6 +6,7 @@ export { PostAccordionItem } from './components/post-accordion-item/post-accordi export { PostAvatar } from './components/post-avatar/post-avatar'; export { PostBackToTop } from './components/post-back-to-top/post-back-to-top'; export { PostBanner } from './components/post-banner/post-banner'; +export { PostBreadcrumb } from './components/post-breadcrumb/post-breadcrumb'; export { PostBreadcrumbItem } from './components/post-breadcrumb-item/post-breadcrumb-item'; export { PostCardControl } from './components/post-card-control/post-card-control'; export { PostClosebutton } from './components/post-closebutton/post-closebutton'; diff --git a/packages/documentation/cypress/snapshots/components/breadcrumb.snapshot.ts b/packages/documentation/cypress/snapshots/components/breadcrumb.snapshot.ts new file mode 100644 index 0000000000..d8757cd4dd --- /dev/null +++ b/packages/documentation/cypress/snapshots/components/breadcrumb.snapshot.ts @@ -0,0 +1,7 @@ +describe('Breadcrumb', () => { + it('default', () => { + cy.visit('/iframe.html?id=snapshots--breadcrumb'); + cy.get('post-breadcrumb.hydrated', { timeout: 30000 }).should('be.visible'); + cy.percySnapshot('Breadcrumb', { widths: [400] }); + }); +}); diff --git a/packages/documentation/src/stories/components/breadcrumb/breadcrumb.mdx b/packages/documentation/src/stories/components/breadcrumb/breadcrumb.mdx new file mode 100644 index 0000000000..3e546a981a --- /dev/null +++ b/packages/documentation/src/stories/components/breadcrumb/breadcrumb.mdx @@ -0,0 +1,29 @@ +import { Meta, Canvas, Controls } from '@storybook/blocks'; +import * as BreadcrumbStories from './breadcrumb.stories'; + + + +# Post Breadcrumb + +
+ The breadcrumb is a secondary navigation pattern that helps users understand the hierarchy among levels and navigate back through them. +
+ +The `` is a container for `` components, used to display a navigational trail, showing the user's location within the app and enabling quick access to parent pages. + +## `` + + + + +## Concatenated Breadcrumb + +When space is constrained, the breadcrumb concatenates middle items into a dropdown menu. + + + + +## `` + + + diff --git a/packages/documentation/src/stories/components/breadcrumb/breadcrumb.snapshot.stories.ts b/packages/documentation/src/stories/components/breadcrumb/breadcrumb.snapshot.stories.ts new file mode 100644 index 0000000000..666932dc92 --- /dev/null +++ b/packages/documentation/src/stories/components/breadcrumb/breadcrumb.snapshot.stories.ts @@ -0,0 +1,45 @@ +import { Args, StoryContext, StoryObj } from '@storybook/web-components'; +import meta, { Default, Concatenated } from './breadcrumb.stories'; +import { html } from 'lit'; +import { schemes } from '@/shared/snapshots/schemes'; + +const { id, ...metaWithoutId } = meta; + +export default { + ...metaWithoutId, + title: 'Snapshots', +}; + +type Story = StoryObj; + +export const BreadcrumbSnapshots: Story = { + render: (_args: Args, context: StoryContext) => { + const scenarios = [ + { label: 'Default', story: Default.render?.(context.args, context) || html`

Error rendering Default

` }, + { label: 'Concatenated', story: Concatenated.render?.(context.args, context) || html`

Error rendering Concatenated

` }, + { + label: 'Long Text', + story: html` + + This is a very long breadcrumb item + Another long breadcrumb item + Yet another long item that tests wrapping behavior + + `, + }, + ]; + + return schemes(() => html` +
+ ${scenarios.map( + (scenario) => html` +
+

${scenario.label}

+ ${scenario.story} +
+ ` + )} +
+ `); + }, +}; diff --git a/packages/documentation/src/stories/components/breadcrumb/breadcrumb.stories.ts b/packages/documentation/src/stories/components/breadcrumb/breadcrumb.stories.ts new file mode 100644 index 0000000000..b218e22640 --- /dev/null +++ b/packages/documentation/src/stories/components/breadcrumb/breadcrumb.stories.ts @@ -0,0 +1,91 @@ +import type { Args, StoryObj } from '@storybook/web-components'; +import { html } from 'lit'; +import { MetaComponent } from '@root/types'; + +const meta: MetaComponent = { + id: 'b7db7391-f893-4b1e-a125-b30c6f0b028b', + title: 'Components/Breadcrumb', + tags: ['package:WebComponents'], + parameters: { + badges: [], + design: { + type: 'figma', + url: 'https://www.figma.com/design/JIT5AdGYqv6bDRpfBPV8XR/Foundations-%26-Components-Next-Level?node-id=1787-20607&node-type=instance&m=dev', + }, + }, + args: { + homeUrl: '/', + homeText: 'Home', + }, + argTypes: { + homeUrl: { + name: 'Home URL', + description: 'URL for the home breadcrumb link.', + control: { type: 'text' }, + table: { category: 'Props' }, + }, + homeText: { + name: 'Home Text', + description: 'Text for the home breadcrumb link.', + control: { type: 'text' }, + table: { category: 'Props' }, + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: (args: Args) => html` + + Section 1 + Section 2 + Section 3 + + `, +}; + +export const Concatenated: Story = { + render: (args: Args) => html` + + Section 1 + Section 2 + Section 3 + Section 4 + Section 5 + Section 6 + Section 7 + Section 8 + Section 9 + Section 10 + + `, +}; + +export const BreadcrumbItem: Story = { + render: (args: Args) => html` + ${args.content} + `, + args: { + url: '/section1', + content: 'Section 1', + }, + argTypes: { + url: { + name: 'URL', + description: 'The URL of the breadcrumb item.', + control: { type: 'text' }, + table: { category: 'Props' }, + }, + content: { + name: 'Content', + description: 'The visible label of the breadcrumb item.', + control: { type: 'text' }, + table: { category: 'Props' }, + }, + homeUrl: { table: { disable: true } }, + homeText: { table: { disable: true } }, + }, +};