diff --git a/__snapshots__/breadcrumb/component/chromium/DBBreadcrumb-should-match-screenshot.png b/__snapshots__/breadcrumb/component/chromium/DBBreadcrumb-should-match-screenshot.png new file mode 100644 index 000000000000..2c60b3ea190c Binary files /dev/null and b/__snapshots__/breadcrumb/component/chromium/DBBreadcrumb-should-match-screenshot.png differ diff --git a/__snapshots__/breadcrumb/component/firefox/DBBreadcrumb-should-match-screenshot.png b/__snapshots__/breadcrumb/component/firefox/DBBreadcrumb-should-match-screenshot.png new file mode 100644 index 000000000000..3937624bdef7 Binary files /dev/null and b/__snapshots__/breadcrumb/component/firefox/DBBreadcrumb-should-match-screenshot.png differ diff --git a/__snapshots__/breadcrumb/component/mobile-chrome/DBBreadcrumb-should-match-screenshot.png b/__snapshots__/breadcrumb/component/mobile-chrome/DBBreadcrumb-should-match-screenshot.png new file mode 100644 index 000000000000..93da699e6d39 Binary files /dev/null and b/__snapshots__/breadcrumb/component/mobile-chrome/DBBreadcrumb-should-match-screenshot.png differ diff --git a/__snapshots__/breadcrumb/showcase/chromium-highContrast/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png b/__snapshots__/breadcrumb/showcase/chromium-highContrast/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png new file mode 100644 index 000000000000..e187caac9287 Binary files /dev/null and b/__snapshots__/breadcrumb/showcase/chromium-highContrast/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png differ diff --git a/__snapshots__/breadcrumb/showcase/chromium-highContrast/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml b/__snapshots__/breadcrumb/showcase/chromium-highContrast/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml new file mode 100644 index 000000000000..a2d4bc2b5f8d --- /dev/null +++ b/__snapshots__/breadcrumb/showcase/chromium-highContrast/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml @@ -0,0 +1,36 @@ +- main: + - heading "DBBreadcrumb" [level=1] + - link "Size" + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - link "Separator" + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: / + - link "Category" + - listitem: / Current Page \ No newline at end of file diff --git a/__snapshots__/breadcrumb/showcase/chromium/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png b/__snapshots__/breadcrumb/showcase/chromium/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png new file mode 100644 index 000000000000..9ecdc28bda69 Binary files /dev/null and b/__snapshots__/breadcrumb/showcase/chromium/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png differ diff --git a/__snapshots__/breadcrumb/showcase/chromium/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml b/__snapshots__/breadcrumb/showcase/chromium/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml new file mode 100644 index 000000000000..a2d4bc2b5f8d --- /dev/null +++ b/__snapshots__/breadcrumb/showcase/chromium/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml @@ -0,0 +1,36 @@ +- main: + - heading "DBBreadcrumb" [level=1] + - link "Size" + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - link "Separator" + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: / + - link "Category" + - listitem: / Current Page \ No newline at end of file diff --git a/__snapshots__/breadcrumb/showcase/firefox/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png b/__snapshots__/breadcrumb/showcase/firefox/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png new file mode 100644 index 000000000000..2752028de60b Binary files /dev/null and b/__snapshots__/breadcrumb/showcase/firefox/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png differ diff --git a/__snapshots__/breadcrumb/showcase/firefox/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml b/__snapshots__/breadcrumb/showcase/firefox/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml new file mode 100644 index 000000000000..a2d4bc2b5f8d --- /dev/null +++ b/__snapshots__/breadcrumb/showcase/firefox/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml @@ -0,0 +1,36 @@ +- main: + - heading "DBBreadcrumb" [level=1] + - link "Size" + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - link "Separator" + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: / + - link "Category" + - listitem: / Current Page \ No newline at end of file diff --git a/__snapshots__/breadcrumb/showcase/mobile-chrome/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png b/__snapshots__/breadcrumb/showcase/mobile-chrome/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png new file mode 100644 index 000000000000..0bf760c85c38 Binary files /dev/null and b/__snapshots__/breadcrumb/showcase/mobile-chrome/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png differ diff --git a/__snapshots__/breadcrumb/showcase/mobile-chrome/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml b/__snapshots__/breadcrumb/showcase/mobile-chrome/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml new file mode 100644 index 000000000000..a2d4bc2b5f8d --- /dev/null +++ b/__snapshots__/breadcrumb/showcase/mobile-chrome/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml @@ -0,0 +1,36 @@ +- main: + - heading "DBBreadcrumb" [level=1] + - link "Size" + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - link "Separator" + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: / + - link "Category" + - listitem: / Current Page \ No newline at end of file diff --git a/__snapshots__/breadcrumb/showcase/mobile-safari/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png b/__snapshots__/breadcrumb/showcase/mobile-safari/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png new file mode 100644 index 000000000000..5b65205f0511 Binary files /dev/null and b/__snapshots__/breadcrumb/showcase/mobile-safari/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png differ diff --git a/__snapshots__/breadcrumb/showcase/mobile-safari/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml b/__snapshots__/breadcrumb/showcase/mobile-safari/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml new file mode 100644 index 000000000000..a2d4bc2b5f8d --- /dev/null +++ b/__snapshots__/breadcrumb/showcase/mobile-safari/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml @@ -0,0 +1,36 @@ +- main: + - heading "DBBreadcrumb" [level=1] + - link "Size" + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - link "Separator" + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: / + - link "Category" + - listitem: / Current Page \ No newline at end of file diff --git a/__snapshots__/breadcrumb/showcase/webkit/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png b/__snapshots__/breadcrumb/showcase/webkit/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png new file mode 100644 index 000000000000..58e95462e28a Binary files /dev/null and b/__snapshots__/breadcrumb/showcase/webkit/DBBreadcrumb-should-match-screenshot-1/DBBreadcrumb-should-match-screenshot.png differ diff --git a/__snapshots__/breadcrumb/showcase/webkit/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml b/__snapshots__/breadcrumb/showcase/webkit/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml new file mode 100644 index 000000000000..a2d4bc2b5f8d --- /dev/null +++ b/__snapshots__/breadcrumb/showcase/webkit/should-have-same-aria-snapshot/DBBreadcrumb-should-have-same-aria-snapshot.yaml @@ -0,0 +1,36 @@ +- main: + - heading "DBBreadcrumb" [level=1] + - link "Size" + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - link "Separator" + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: › + - link "Category" + - listitem: › Current Page + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" + - listitem: + - text: / + - link "Category" + - listitem: / Current Page \ No newline at end of file diff --git a/packages/components/scripts/post-build/components.ts b/packages/components/scripts/post-build/components.ts index 9177fff99b1d..487da80b794f 100644 --- a/packages/components/scripts/post-build/components.ts +++ b/packages/components/scripts/post-build/components.ts @@ -355,6 +355,9 @@ export const getComponents = (): Component[] => [ { name: 'brand' }, + { + name: 'breadcrumb' + }, { name: 'input', overwrites: { diff --git a/packages/components/src/components/breadcrumb/agent/breadcrumb.agent.lite.tsx b/packages/components/src/components/breadcrumb/agent/breadcrumb.agent.lite.tsx new file mode 100644 index 000000000000..5daac5173da9 --- /dev/null +++ b/packages/components/src/components/breadcrumb/agent/breadcrumb.agent.lite.tsx @@ -0,0 +1,42 @@ +import { DBBreadcrumb } from '../index'; + +export default function Breadcrumb() { + return ( + <> +

DBBreadcrumb Documentation Examples

+ +

1. Default Breadcrumb

+ +
  • + Home +
  • +
  • + Category +
  • +
  • Current Page
  • +
    + +

    2. Long Breadcrumb Path

    + +
  • + Home +
  • +
  • + Category +
  • +
  • + Subcategory +
  • +
  • + Product Group +
  • +
  • Current Product
  • +
    + +

    3. Single Item

    + +
  • Current Page
  • +
    + + ); +} diff --git a/packages/components/src/components/breadcrumb/breadcrumb.lite.tsx b/packages/components/src/components/breadcrumb/breadcrumb.lite.tsx new file mode 100644 index 000000000000..38a20ae51280 --- /dev/null +++ b/packages/components/src/components/breadcrumb/breadcrumb.lite.tsx @@ -0,0 +1,35 @@ +import { + useDefaultProps, + useMetadata, + useRef, + useStore +} from '@builder.io/mitosis'; +import { cls } from '../../utils'; +import type { DBBreadcrumbProps, DBBreadcrumbState } from './model'; + +useMetadata({}); + +useDefaultProps({ + size: 'small', + separator: 'chevron' +}); + +export default function DBBreadcrumb(props: DBBreadcrumbProps) { + const _ref = useRef(null); + + const state = useStore({}); + + return ( + + ); +} diff --git a/packages/components/src/components/breadcrumb/breadcrumb.scss b/packages/components/src/components/breadcrumb/breadcrumb.scss new file mode 100644 index 000000000000..d0804b21947b --- /dev/null +++ b/packages/components/src/components/breadcrumb/breadcrumb.scss @@ -0,0 +1,136 @@ +@charset "utf-8"; +@use "@db-ux/core-foundations/build/styles/fonts"; +@use "@db-ux/core-foundations/build/styles/variables"; +@use "@db-ux/core-foundations/build/styles/colors"; +@use "@db-ux/core-foundations/build/styles/helpers"; + +.db-breadcrumb { + // Default: Small size + .db-breadcrumb-list { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: variables.$db-spacing-fixed-3xs; // 2px gap for small + list-style: none; + margin: 0; + padding: 0; + } + + // Breadcrumb items (li elements) + li { + display: flex; + align-items: center; + gap: variables.$db-spacing-fixed-3xs; // 2px between separator and item (small) + } + + // Link and text styling (small) + a, + span { + display: flex; + align-items: center; + padding: variables.$db-spacing-fixed-3xs variables.$db-spacing-fixed-2xs; // 2px 4px (small) + border-radius: variables.$db-border-radius-xs; // 4px + background-color: colors.$db-adaptive-bg-basic-transparent-full-default; + color: colors.$db-adaptive-on-bg-basic-emphasis-100-default; + font-size: variables.$db-sizing-sm; // 14px (body-sm) + line-height: variables.$db-sizing-md; // 20px + font-family: var(--db-font-family-sans); + font-weight: 400; + text-decoration: none; + white-space: nowrap; + transition: background-color 0.2s ease; + + &:hover, + &:focus { + background-color: colors.$db-adaptive-bg-basic-transparent-semi-default; + text-decoration: underline; + } + + &:active { + background-color: colors.$db-adaptive-bg-basic-transparent-full-pressed; + } + + @media screen and (prefers-reduced-motion: reduce) { + transition: none; + } + } + + // Current page (last item with aria-current="page") + li[aria-current="page"] { + a, + span { + font-weight: 700; // Bold for current item + cursor: default; + pointer-events: none; + + &:hover, + &:focus { + background-color: colors.$db-adaptive-bg-basic-transparent-full-default; + text-decoration: none; + } + } + } + + // Default chevron: apply when the breadcrumb does NOT request a slash + &:not([data-separator="slash"]) + .db-breadcrumb-list + li:not(:first-child)::before { + content: "›"; // chevron right glyph (U+203A) + display: flex; + align-items: center; + justify-content: center; + inline-size: variables.$db-sizing-sm; // 24px (small) + block-size: variables.$db-sizing-sm; + font-size: variables.$db-sizing-md; // 20px + color: colors.$db-adaptive-on-bg-basic-emphasis-100-default; + font-weight: 400; + } + + // Slash separator override when component has data-separator="slash" + &[data-separator="slash"] .db-breadcrumb-list li:not(:first-child)::before { + content: "/"; // ASCII slash (0x2F) + font-weight: 400; + color: colors.$db-adaptive-on-bg-basic-emphasis-100-default; + + /* reduce the inline size so the slash doesn't create extra empty box */ + inline-size: auto; + block-size: auto; + font-size: variables.$db-sizing-sm; // match small text sizing by default + margin: 0 variables.$db-spacing-fixed-2xs; // small horizontal spacing + } + + // Medium size variant + &[data-size="medium"] { + .db-breadcrumb-list { + // Slightly more breathing room for medium size (matches Figma spacing) + gap: variables.$db-spacing-fixed-xs; // 8px gap for medium + + /* ensure list items are vertically centered in medium size */ + align-items: center; + } + + li { + // Increase gap between separator and item for better touch target + gap: variables.$db-spacing-fixed-xs; // 8px between separator and item (medium) + + &:not(:first-child)::before { + // Make the chevron larger to match visual weight in Figma + inline-size: variables.$db-sizing-lg; // ~24px + block-size: variables.$db-sizing-lg; + font-size: variables.$db-sizing-lg; // use large sizing token + font-weight: 600; + color: colors.$db-adaptive-on-bg-basic-emphasis-100-default; + } + + a, + span { + // Keep the medium padding but ensure comfortable tap/click area + padding: variables.$db-spacing-fixed-2xs + variables.$db-spacing-fixed-xs; // 4px 8px (medium) + + font-size: variables.$db-sizing-md; // 16px (body-md) + line-height: variables.$db-sizing-lg; // 24px + } + } + } +} diff --git a/packages/components/src/components/breadcrumb/breadcrumb.spec.tsx b/packages/components/src/components/breadcrumb/breadcrumb.spec.tsx new file mode 100644 index 000000000000..1af69632434d --- /dev/null +++ b/packages/components/src/components/breadcrumb/breadcrumb.spec.tsx @@ -0,0 +1,46 @@ +import AxeBuilder from '@axe-core/playwright'; +import { expect, test } from '@playwright/experimental-ct-react'; + +import { DBBreadcrumb } from './index'; +// @ts-ignore - vue can only find it with .ts as file ending +import { DEFAULT_VIEWPORT } from '../../shared/constants.ts'; + +const defaultBreadcrumb: any = ( + +
  • + Home +
  • +
  • + Category +
  • +
  • Current Page
  • +
    +); + +test.describe('DBBreadcrumb', () => { + test('should render', async ({ mount }) => { + const component = await mount(defaultBreadcrumb); + await expect(component).toBeVisible(); + }); + + test('should have accessible role', async ({ mount }) => { + const component = await mount(defaultBreadcrumb); + await expect(component).toHaveAttribute('aria-label', 'breadcrumb'); + }); + + test('should not have basic accessibility issues', async ({ mount }) => { + const component = await mount(defaultBreadcrumb); + const accessibilityScanResults = await new AxeBuilder({ + page: component.page() + }) + .include('.db-breadcrumb') + .analyze(); + expect(accessibilityScanResults.violations).toEqual([]); + }); + + test('should match screenshot', async ({ mount }) => { + const component = await mount(defaultBreadcrumb); + await component.page().setViewportSize(DEFAULT_VIEWPORT); + await expect(component).toHaveScreenshot(); + }); +}); diff --git a/packages/components/src/components/breadcrumb/docs/Angular.md b/packages/components/src/components/breadcrumb/docs/Angular.md new file mode 100644 index 000000000000..9cee713c86f0 --- /dev/null +++ b/packages/components/src/components/breadcrumb/docs/Angular.md @@ -0,0 +1,22 @@ +## Angular + +For general installation and configuration take a look at the [ngx-core-components](https://www.npmjs.com/package/@db-ux/ngx-core-components) package. + +### Use component + +```ts app.component.ts +// app.component.ts +import { Component } from "@angular/core"; + +@Component({ + selector: "app-root", + template: ` + +
  • Home
  • +
  • Category
  • +
  • Current Page
  • +
    + ` +}) +export class AppComponent {} +``` diff --git a/packages/components/src/components/breadcrumb/docs/HTML.md b/packages/components/src/components/breadcrumb/docs/HTML.md new file mode 100644 index 000000000000..f32ee069f5be --- /dev/null +++ b/packages/components/src/components/breadcrumb/docs/HTML.md @@ -0,0 +1,19 @@ +## HTML + +### Use component + +```html + +``` + +### Import styles + +```scss app.scss +@forward "@db-ux/core-components/build/styles/relative"; +``` diff --git a/packages/components/src/components/breadcrumb/docs/Migration.md b/packages/components/src/components/breadcrumb/docs/Migration.md new file mode 100644 index 000000000000..c31cf696ec13 --- /dev/null +++ b/packages/components/src/components/breadcrumb/docs/Migration.md @@ -0,0 +1,15 @@ +## Migration Guide + +### From v2.x to v3.x + +Currently no migration needed as this is a new component in v3.x. + +### Breaking Changes + +- None (new component) + +### New Features + +- Added `DBBreadcrumb` component for navigation breadcrumbs +- Supports semantic HTML structure with proper ARIA labels +- Responsive design with flexbox layout diff --git a/packages/components/src/components/breadcrumb/docs/React.md b/packages/components/src/components/breadcrumb/docs/React.md new file mode 100644 index 000000000000..25db6cf87a59 --- /dev/null +++ b/packages/components/src/components/breadcrumb/docs/React.md @@ -0,0 +1,24 @@ +## React + +For general installation and configuration take a look at the [react-core-components](https://www.npmjs.com/package/@db-ux/react-core-components) package. + +### Use component + +```tsx App.tsx +// App.tsx +import { DBBreadcrumb } from "@db-ux/react-core-components"; + +const App = () => ( + +
  • + Home +
  • +
  • + Category +
  • +
  • Current Page
  • +
    +); + +export default App; +``` diff --git a/packages/components/src/components/breadcrumb/docs/Vue.md b/packages/components/src/components/breadcrumb/docs/Vue.md new file mode 100644 index 000000000000..66655ae86eac --- /dev/null +++ b/packages/components/src/components/breadcrumb/docs/Vue.md @@ -0,0 +1,19 @@ +## Vue + +For general installation and configuration take a look at the [v-core-components](https://www.npmjs.com/package/@db-ux/v-core-components) package. + +### Use component + +```vue App.vue + + + +``` diff --git a/packages/components/src/components/breadcrumb/index.html b/packages/components/src/components/breadcrumb/index.html new file mode 100644 index 000000000000..afbd1afedfe3 --- /dev/null +++ b/packages/components/src/components/breadcrumb/index.html @@ -0,0 +1,30 @@ + + + + + DBBreadcrumb + + + + + +

    + + + + + diff --git a/packages/components/src/components/breadcrumb/index.ts b/packages/components/src/components/breadcrumb/index.ts new file mode 100644 index 000000000000..dbaad9162cf9 --- /dev/null +++ b/packages/components/src/components/breadcrumb/index.ts @@ -0,0 +1 @@ +export { default as DBBreadcrumb } from './breadcrumb'; diff --git a/packages/components/src/components/breadcrumb/model.ts b/packages/components/src/components/breadcrumb/model.ts new file mode 100644 index 000000000000..0a53a0b7bfa5 --- /dev/null +++ b/packages/components/src/components/breadcrumb/model.ts @@ -0,0 +1,28 @@ +import { GlobalProps, GlobalState } from '../../shared/model'; + +export const BreadcrumbSizeList = ['small', 'medium'] as const; +export type BreadcrumbSize = (typeof BreadcrumbSizeList)[number]; +export const BreadcrumbSeparatorList = ['chevron', 'slash'] as const; +export type BreadcrumbSeparator = (typeof BreadcrumbSeparatorList)[number]; + +export interface DBBreadcrumbDefaultProps { + /** + * The size of the breadcrumb items + */ + size?: BreadcrumbSize; + + /** + * The separator between breadcrumb items: 'chevron' or 'slash' + */ + separator?: BreadcrumbSeparator; +} + +export interface DBBreadcrumbProps + extends DBBreadcrumbDefaultProps, + GlobalProps {} + +export interface DBBreadcrumbDefaultState {} + +export interface DBBreadcrumbState + extends DBBreadcrumbDefaultState, + GlobalState {} diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index b905eb44af91..b842f70f3076 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -6,6 +6,8 @@ export * from './components/badge'; export * from './components/badge/model'; export * from './components/brand'; export * from './components/brand/model'; +export * from './components/breadcrumb'; +export * from './components/breadcrumb/model'; export * from './components/button'; export * from './components/button/model'; export * from './components/card'; diff --git a/packages/components/src/styles/index.scss b/packages/components/src/styles/index.scss index 0830302f8b04..47cf969698b2 100644 --- a/packages/components/src/styles/index.scss +++ b/packages/components/src/styles/index.scss @@ -5,6 +5,7 @@ @forward "../components/card/card"; @forward "../components/input/input"; @forward "../components/brand/brand"; +@forward "../components/breadcrumb/breadcrumb"; @forward "../components/header/header"; @forward "../components/page/page"; @forward "../components/link/link"; diff --git a/showcases/angular-showcase/src/app/components/breadcrumb/breadcrumb.component.html b/showcases/angular-showcase/src/app/components/breadcrumb/breadcrumb.component.html new file mode 100644 index 000000000000..76bc16b150c0 --- /dev/null +++ b/showcases/angular-showcase/src/app/components/breadcrumb/breadcrumb.component.html @@ -0,0 +1,38 @@ + + + + + + diff --git a/showcases/angular-showcase/src/app/components/breadcrumb/breadcrumb.component.ts b/showcases/angular-showcase/src/app/components/breadcrumb/breadcrumb.component.ts new file mode 100644 index 000000000000..c847af745c39 --- /dev/null +++ b/showcases/angular-showcase/src/app/components/breadcrumb/breadcrumb.component.ts @@ -0,0 +1,15 @@ +import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +// TODO: Uncomment after build-outputs: import { DBBreadcrumb } from '../../../../../../output/angular/src'; +import defaultComponentVariants from '../../../../../shared/breadcrumb.json'; +import { DefaultComponent } from '../default.component'; + +@Component({ + selector: 'app-breadcrumb', + templateUrl: './breadcrumb.component.html', + imports: [DefaultComponent], + standalone: true, + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class BreadcrumbComponent { + variants = defaultComponentVariants; +} diff --git a/showcases/angular-showcase/src/app/utils/navigation-item.ts b/showcases/angular-showcase/src/app/utils/navigation-item.ts index b7b06efdd447..718c4a70fd51 100644 --- a/showcases/angular-showcase/src/app/utils/navigation-item.ts +++ b/showcases/angular-showcase/src/app/utils/navigation-item.ts @@ -3,6 +3,7 @@ import { AccordionItemComponent } from '../components/accordion-item/accordion-i import { AccordionComponent } from '../components/accordion/accordion.component'; import { BadgeComponent } from '../components/badge/badge.component'; import { BrandComponent } from '../components/brand/brand.component'; +import { BreadcrumbComponent } from '../components/breadcrumb/breadcrumb.component'; import { ButtonComponent } from '../components/button/button.component'; import { CardComponent } from '../components/card/card.component'; import { CheckboxComponent } from '../components/checkbox/checkbox.component'; @@ -60,6 +61,11 @@ export const NAVIGATION_ITEMS: NavItem[] = [ path: '05', label: '05 Navigation', subNavigation: getSortedNavigationItems([ + { + path: '05/breadcrumb', + label: 'Breadcrumb', + component: BreadcrumbComponent + }, { path: '05/navigation-item', label: 'NavigationItem', diff --git a/showcases/e2e/breadcrumb/breadcrumb-a11y-checker.spec.ts b/showcases/e2e/breadcrumb/breadcrumb-a11y-checker.spec.ts new file mode 100644 index 000000000000..826ae6a20316 --- /dev/null +++ b/showcases/e2e/breadcrumb/breadcrumb-a11y-checker.spec.ts @@ -0,0 +1,6 @@ +import { test } from '@playwright/test'; +import { runA11yCheckerTest } from '../default.ts'; + +test.describe('DBBreadcrumb', () => { + runA11yCheckerTest({ path: '05/breadcrumb' }); +}); diff --git a/showcases/e2e/breadcrumb/breadcrumb-aria-snapshot.spec.ts b/showcases/e2e/breadcrumb/breadcrumb-aria-snapshot.spec.ts new file mode 100644 index 000000000000..dd90e6c03396 --- /dev/null +++ b/showcases/e2e/breadcrumb/breadcrumb-aria-snapshot.spec.ts @@ -0,0 +1,6 @@ +import { test } from '@playwright/test'; +import { runAriaSnapshotTest } from '../default.ts'; + +test.describe('DBBreadcrumb', () => { + runAriaSnapshotTest({ path: '05/breadcrumb' }); +}); diff --git a/showcases/e2e/breadcrumb/breadcrumb-axe-core.spec.ts b/showcases/e2e/breadcrumb/breadcrumb-axe-core.spec.ts new file mode 100644 index 000000000000..7d7da93e1b31 --- /dev/null +++ b/showcases/e2e/breadcrumb/breadcrumb-axe-core.spec.ts @@ -0,0 +1,9 @@ +import { test } from '@playwright/test'; +import { runAxeCoreTest } from '../default.ts'; +import { lvl3 } from '../fixtures/variants'; + +test.describe('DBBreadcrumb', () => { + runAxeCoreTest({ path: '05/breadcrumb' }); + runAxeCoreTest({ path: '05/breadcrumb', color: lvl3 }); + runAxeCoreTest({ path: '05/breadcrumb', density: 'functional' }); +}); diff --git a/showcases/e2e/breadcrumb/breadcrumb-visual-snapshot.spec.ts b/showcases/e2e/breadcrumb/breadcrumb-visual-snapshot.spec.ts new file mode 100644 index 000000000000..1324a7edc3dd --- /dev/null +++ b/showcases/e2e/breadcrumb/breadcrumb-visual-snapshot.spec.ts @@ -0,0 +1,7 @@ +import { test } from '@playwright/test'; +import { getDefaultScreenshotTest } from '../default.ts'; + +const path = '05/breadcrumb'; +test.describe('DBBreadcrumb', () => { + getDefaultScreenshotTest({ path }); +}); diff --git a/showcases/react-showcase/src/components/breadcrumb/index.tsx b/showcases/react-showcase/src/components/breadcrumb/index.tsx new file mode 100644 index 000000000000..5f5c6f8a04f6 --- /dev/null +++ b/showcases/react-showcase/src/components/breadcrumb/index.tsx @@ -0,0 +1,56 @@ +import { DBBreadcrumb } from '../../../../../output/react/src'; +import defaultComponentVariants from '../../../../shared/breadcrumb.json'; +import { getVariants } from '../data'; +import DefaultComponent from '../default-component'; + +type BreadcrumbItem = { + href?: string; + text: string; + ariaCurrent?: 'page' | undefined; +}; + +type BreadcrumbExampleProps = { + children?: BreadcrumbItem[]; + size?: 'small' | 'medium'; + className?: string; + separator?: 'chevron' | 'slash'; +}; + +const getBreadcrumb = ({ + children, + size, + className, + separator +}: BreadcrumbExampleProps) => ( + + {children && Array.isArray(children) + ? children.map((item, index) => + item.href ? ( +
  • + {item.text} +
  • + ) : ( +
  • + {item.text} +
  • + ) + ) + : children} +
    +); + +type BreadcrumbComponentProps = { + slotCode?: Record; +}; + +const BreadcrumbComponent = (props: BreadcrumbComponentProps) => ( + +); + +export default BreadcrumbComponent; diff --git a/showcases/react-showcase/src/components/data.ts b/showcases/react-showcase/src/components/data.ts index cd1333254ce9..4e774471b786 100644 --- a/showcases/react-showcase/src/components/data.ts +++ b/showcases/react-showcase/src/components/data.ts @@ -14,6 +14,8 @@ export const getVariants = ( ], examples: variant.examples.map((example, exampleIndex) => ({ ...example, + // Ensure className from props is available on the example object + className: example.className ?? example.props?.className, example: getExample({ ...example.props, id: example.props?.id ?? example.name, diff --git a/showcases/react-showcase/src/utils/navigation-item.tsx b/showcases/react-showcase/src/utils/navigation-item.tsx index 1f5f0774bb6e..6797ac7b1b22 100644 --- a/showcases/react-showcase/src/utils/navigation-item.tsx +++ b/showcases/react-showcase/src/utils/navigation-item.tsx @@ -2,6 +2,7 @@ import AccordionComponent from '../components/accordion'; import AccordionItemComponent from '../components/accordion-item'; import BadgeComponent from '../components/badge'; import BrandComponent from '../components/brand'; +import BreadcrumbComponent from '../components/breadcrumb'; import ButtonComponent from '../components/button'; import CardComponent from '../components/card'; import CheckboxComponent from '../components/checkbox'; @@ -60,6 +61,11 @@ export const NAVIGATION_ITEMS: NavigationItem[] = [ path: '05', label: '05 Navigation', subNavigation: getSortedNavigationItems([ + { + path: 'breadcrumb', + label: 'Breadcrumb', + component: + }, { path: 'navigation-item', label: 'NavigationItem', diff --git a/showcases/shared/breadcrumb.json b/showcases/shared/breadcrumb.json new file mode 100644 index 000000000000..d603323c3c44 --- /dev/null +++ b/showcases/shared/breadcrumb.json @@ -0,0 +1,60 @@ +[ + { + "name": "Size", + "examples": [ + { + "name": "(Default) Small", + "className": "w-full", + "props": { + "size": "small", + "children": [ + { "href": "#", "text": "Home" }, + { "href": "#", "text": "Category" }, + { "text": "Current Page", "ariaCurrent": "page" } + ] + } + }, + { + "name": "Medium", + "className": "w-full", + "props": { + "size": "medium", + "children": [ + { "href": "#", "text": "Home" }, + { "href": "#", "text": "Category" }, + { "text": "Current Page", "ariaCurrent": "page" } + ] + } + } + ] + }, + { + "name": "Separator", + "examples": [ + { + "name": "Chevron", + "className": "w-full", + "props": { + "separator": "chevron", + "children": [ + { "href": "#", "text": "Home" }, + { "href": "#", "text": "Category" }, + { "text": "Current Page", "ariaCurrent": "page" } + ] + } + }, + { + "name": "Slash", + "className": "w-full", + "props": { + "separator": "slash", + "children": [ + { "href": "#", "text": "Home" }, + { "href": "#", "text": "Category" }, + { "text": "Current Page", "ariaCurrent": "page" } + ] + } + } + ] + } +] diff --git a/showcases/showcase-styles.css b/showcases/showcase-styles.css index b244b174a8f8..dc3c89cacc5a 100644 --- a/showcases/showcase-styles.css +++ b/showcases/showcase-styles.css @@ -67,6 +67,18 @@ db-card:is(.variants-card) > .db-card { margin-block: 0 auto; } +/* Breadcrumb showcase: stack breadcrumb examples vertically */ +@supports selector(:has(*)) { + .variants-list > div:has(.db-breadcrumb) { + flex: 0 0 100%; + } +} + +/* Fallback: if :has unsupported, rely on utility class or force wrappers containing db-breadcrumb to behave as block via descendant width trick */ +.variants-list .db-breadcrumb { + inline-size: 100%; +} + .html-code-container { display: flex; flex-direction: column; diff --git a/showcases/vue-showcase/src/components/breadcrumb/breadcrumb.vue b/showcases/vue-showcase/src/components/breadcrumb/breadcrumb.vue new file mode 100644 index 000000000000..3782b50d8210 --- /dev/null +++ b/showcases/vue-showcase/src/components/breadcrumb/breadcrumb.vue @@ -0,0 +1,38 @@ + + + diff --git a/showcases/vue-showcase/src/utils/navigation-items.ts b/showcases/vue-showcase/src/utils/navigation-items.ts index beee8433a446..a4fb5ae25ef5 100644 --- a/showcases/vue-showcase/src/utils/navigation-items.ts +++ b/showcases/vue-showcase/src/utils/navigation-items.ts @@ -4,6 +4,7 @@ import AccordionItem from '../components/accordion-item/AccordionItem.vue'; import Accordion from '../components/accordion/Accordion.vue'; import Badge from '../components/badge/Badge.vue'; import Brand from '../components/brand/Brand.vue'; +import Breadcrumb from '../components/breadcrumb/breadcrumb.vue'; import Button from '../components/button/Button.vue'; import Card from '../components/card/Card.vue'; import Checkbox from '../components/checkbox/Checkbox.vue'; @@ -61,6 +62,11 @@ export const navigationItems: NavItem[] = [ path: '/05', label: '05 Navigation', subNavigation: getSortedNavigationItems([ + { + path: '/05/breadcrumb', + label: 'Breadcrumb', + component: markRaw(Breadcrumb) + }, { path: '/05/navigation-item', label: 'NavigationItem',