diff --git a/src/components/breadcrumb/breadcrumb.ts b/src/components/breadcrumb/breadcrumb.ts new file mode 100644 index 000000000..19f2e85b9 --- /dev/null +++ b/src/components/breadcrumb/breadcrumb.ts @@ -0,0 +1,86 @@ +import { css, html, LitElement, type PropertyValues } from 'lit'; +import { property } from 'lit/decorators.js'; +import { addInternalsController } from '../common/controllers/internals.js'; +import { registerComponent } from '../common/definitions/register.js'; +import IgcIconComponent from '../icon/icon.js'; + +/** + * @element igc-breadcrumb + * + * @slot - Default slot for the breadcrumb item. One will usually put an anchor tag here. + * @slot prefix - Renders content before the default content of the breadcrumb item. + * @slot suffix - Renders content after the default content of the breadcrumb item. + * @slot separator - Renders a custom separator content after the breadcrumb item. + */ +export default class IgcBreadcrumbComponent extends LitElement { + public static readonly tagName = 'igc-breadcrumb'; + + static override styles = css` + :host { + display: inline-flex; + flex: 0 0 auto; + gap: 0.5rem; + + ::slotted(a) { + color: var(--ig-primary-500); + text-decoration: none; + } + } + + [part='separator'] { + &:dir(rtl) igc-icon, + &:dir(rtl) ::slotted(igc-icon) { + transform: rotateY(180deg); + } + } + + :host([current]) { + ::slotted(a) { + color: var(--ig-gray-900); + } + } + + :host(:last-of-type) [part='separator'] { + display: none; + } + `; + + /* blazorSuppress */ + public static register(): void { + registerComponent(IgcBreadcrumbComponent, IgcIconComponent); + } + + private readonly _internals = addInternalsController(this, { + initialARIA: { role: 'listitem' }, + }); + + @property({ type: Boolean, reflect: true }) + public current = false; + + protected override updated(changedProperties: PropertyValues): void { + if (changedProperties.has('current')) { + this._internals.setARIA({ ariaCurrent: this.current ? 'page' : null }); + } + } + + protected override render() { + return html` + + + + + + + + + + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'igc-breadcrumb': IgcBreadcrumbComponent; + } +} diff --git a/src/components/breadcrumb/breadcrumbs.ts b/src/components/breadcrumb/breadcrumbs.ts new file mode 100644 index 000000000..654e7162a --- /dev/null +++ b/src/components/breadcrumb/breadcrumbs.ts @@ -0,0 +1,48 @@ +import { css, html, LitElement } from 'lit'; +import { addInternalsController } from '../common/controllers/internals.js'; +import { registerComponent } from '../common/definitions/register.js'; +import IgcBreadcrumbComponent from './breadcrumb.js'; + +/** + * @element igc-breadcrumbs + * + * @slot - Default slot where igc-breadcrumbs are slotted. + */ +export default class IgcBreadcrumbsComponent extends LitElement { + public static readonly tagName = 'igc-breadcrumbs'; + + static override styles = css` + :host { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem; + } + `; + + /* blazorSuppress */ + public static register(): void { + registerComponent(IgcBreadcrumbsComponent, IgcBreadcrumbComponent); + } + + constructor() { + super(); + + addInternalsController(this, { + initialARIA: { + role: 'list', + ariaLabel: 'breadcrumbs', + }, + }); + } + + protected override render() { + return html``; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'igc-breadcrumbs': IgcBreadcrumbsComponent; + } +} diff --git a/src/index.ts b/src/index.ts index 01af701f4..ca2b94e54 100644 --- a/src/index.ts +++ b/src/index.ts @@ -69,6 +69,8 @@ export { default as IgcTreeItemComponent } from './components/tree/tree-item.js' export { default as IgcStepperComponent } from './components/stepper/stepper.js'; export { default as IgcStepComponent } from './components/stepper/step.js'; export { default as IgcTooltipComponent } from './components/tooltip/tooltip.js'; +export { default as IgcBreadcrumbsComponent } from './components/breadcrumb/breadcrumbs.js'; +export { default as IgcBreadcrumbComponent } from './components/breadcrumb/breadcrumb.js'; // definitions export { defineComponents } from './components/common/definitions/defineComponents.js'; diff --git a/stories/breadcrumbs.stories.ts b/stories/breadcrumbs.stories.ts new file mode 100644 index 000000000..d69687e09 --- /dev/null +++ b/stories/breadcrumbs.stories.ts @@ -0,0 +1,102 @@ +import type { Meta, StoryObj } from '@storybook/web-components-vite'; +import { + type IgcBreadcrumbComponent, + IgcBreadcrumbsComponent, + defineComponents, +} from 'igniteui-webcomponents'; +import { html } from 'lit'; + +defineComponents(IgcBreadcrumbsComponent); + +// region default +const metadata: Meta = { + title: 'Breadcrumbs', + component: 'igc-breadcrumbs', + parameters: { docs: { description: { component: '' } } }, +}; + +export default metadata; + +type Story = StoryObj; + +// endregion + +function setBreadCrumpCurrentState(anchor: HTMLAnchorElement, state: boolean) { + (anchor.parentElement as IgcBreadcrumbComponent).current = state; +} + +function breadcrumbsStoryState() { + const anchors = document.querySelectorAll('a'); + + const handler = (event: Event) => { + const current = event.target as HTMLAnchorElement; + event.preventDefault(); + + for (const anchor of anchors) { + setBreadCrumpCurrentState(anchor, false); + } + setBreadCrumpCurrentState(current, true); + }; + + for (const anchor of anchors) { + anchor.addEventListener('click', handler); + } +} + +export const Default: Story = { + play: breadcrumbsStoryState, + render: () => html` +
+

Default look

+ + + Grandparent + + + Parent + + + Child + + +
+ +
+

Custom separators

+ + + First + 👉 + + + Second + 👉 + + + Third + 👉 + + +
+
+

Custom separators & slotted items in breadcrumbs

+ + + 💖 + First + / + + + Second + / + + + ⚠️ + ⚠️ + Third + / + + +
+ `, +};