Skip to content

Commit

Permalink
feat(breadcrumb): add new bq-breadcrumb component (#266)
Browse files Browse the repository at this point in the history
  • Loading branch information
HamudeHomsi authored and dgonzalezr committed Jul 18, 2023
1 parent c015bf0 commit 07d4da5
Show file tree
Hide file tree
Showing 12 changed files with 803 additions and 0 deletions.
106 changes: 106 additions & 0 deletions packages/bee-q/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,34 @@ export namespace Components {
*/
"textColor"?: string;
}
interface BqBreadcrumb {
/**
* The `aria-label` attribute to describe the type of navigation
*/
"ariaLabel": string;
}
interface BqBreadcrumbItem {
/**
* The aria-label that corresponds to the full title of the destination page. This won't be shown in the page, but it will be used by screen readers and other assistive devices.
*/
"ariaLabel": string;
/**
* If set, the breadcrumb item will be rendered as an `<a>` with this `href`, otherwise, a `<button>` will be rendered.
*/
"href": string;
/**
* If true, the item is the last element inside breadcrumb
*/
"isLastItem": boolean;
/**
* Where to display the link in the browser context. Relevant only if `href` is set.
*/
"rel": string;
/**
* Where to display the link in the browser context. Relevant only if `href` is set.
*/
"target": '_blank' | '_parent' | '_self' | '_top';
}
/**
* Buttons are designed for users to take action on a page or a screen.
*/
Expand Down Expand Up @@ -660,6 +688,14 @@ export namespace Components {
"visible"?: boolean;
}
}
export interface BqBreadcrumbCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLBqBreadcrumbElement;
}
export interface BqBreadcrumbItemCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLBqBreadcrumbItemElement;
}
export interface BqButtonCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLBqButtonElement;
Expand Down Expand Up @@ -732,6 +768,18 @@ declare global {
prototype: HTMLBqBadgeElement;
new (): HTMLBqBadgeElement;
};
interface HTMLBqBreadcrumbElement extends Components.BqBreadcrumb, HTMLStencilElement {
}
var HTMLBqBreadcrumbElement: {
prototype: HTMLBqBreadcrumbElement;
new (): HTMLBqBreadcrumbElement;
};
interface HTMLBqBreadcrumbItemElement extends Components.BqBreadcrumbItem, HTMLStencilElement {
}
var HTMLBqBreadcrumbItemElement: {
prototype: HTMLBqBreadcrumbItemElement;
new (): HTMLBqBreadcrumbItemElement;
};
/**
* Buttons are designed for users to take action on a page or a screen.
*/
Expand Down Expand Up @@ -856,6 +904,8 @@ declare global {
interface HTMLElementTagNameMap {
"bq-avatar": HTMLBqAvatarElement;
"bq-badge": HTMLBqBadgeElement;
"bq-breadcrumb": HTMLBqBreadcrumbElement;
"bq-breadcrumb-item": HTMLBqBreadcrumbItemElement;
"bq-button": HTMLBqButtonElement;
"bq-checkbox": HTMLBqCheckboxElement;
"bq-dialog": HTMLBqDialogElement;
Expand Down Expand Up @@ -920,6 +970,58 @@ declare namespace LocalJSX {
*/
"textColor"?: string;
}
interface BqBreadcrumb {
/**
* The `aria-label` attribute to describe the type of navigation
*/
"ariaLabel"?: string;
/**
* Handler to be called when `bq-breadcrumb-item` item loses focus.
*/
"onBqBreadcrumbBlur"?: (event: BqBreadcrumbCustomEvent<HTMLBqBreadcrumbItemElement>) => void;
/**
* Handler to be called when `bq-breadcrumb-item` is selected (on click/enter press).
*/
"onBqBreadcrumbClick"?: (event: BqBreadcrumbCustomEvent<HTMLBqBreadcrumbItemElement>) => void;
/**
* Handler to be called when `bq-breadcrumb-item` item gets focus.
*/
"onBqBreadcrumbFocus"?: (event: BqBreadcrumbCustomEvent<HTMLBqBreadcrumbItemElement>) => void;
}
interface BqBreadcrumbItem {
/**
* The aria-label that corresponds to the full title of the destination page. This won't be shown in the page, but it will be used by screen readers and other assistive devices.
*/
"ariaLabel"?: string;
/**
* If set, the breadcrumb item will be rendered as an `<a>` with this `href`, otherwise, a `<button>` will be rendered.
*/
"href"?: string;
/**
* If true, the item is the last element inside breadcrumb
*/
"isLastItem"?: boolean;
/**
* Handler to be called when item loses focus
*/
"onBqBlur"?: (event: BqBreadcrumbItemCustomEvent<HTMLBqBreadcrumbItemElement>) => void;
/**
* Handler to be called when item is clicked
*/
"onBqClick"?: (event: BqBreadcrumbItemCustomEvent<HTMLBqBreadcrumbItemElement>) => void;
/**
* Handler to be called when item is focused
*/
"onBqFocus"?: (event: BqBreadcrumbItemCustomEvent<HTMLBqBreadcrumbItemElement>) => void;
/**
* Where to display the link in the browser context. Relevant only if `href` is set.
*/
"rel"?: string;
/**
* Where to display the link in the browser context. Relevant only if `href` is set.
*/
"target"?: '_blank' | '_parent' | '_self' | '_top';
}
/**
* Buttons are designed for users to take action on a page or a screen.
*/
Expand Down Expand Up @@ -1549,6 +1651,8 @@ declare namespace LocalJSX {
interface IntrinsicElements {
"bq-avatar": BqAvatar;
"bq-badge": BqBadge;
"bq-breadcrumb": BqBreadcrumb;
"bq-breadcrumb-item": BqBreadcrumbItem;
"bq-button": BqButton;
"bq-checkbox": BqCheckbox;
"bq-dialog": BqDialog;
Expand Down Expand Up @@ -1578,6 +1682,8 @@ declare module "@stencil/core" {
*/
"bq-avatar": LocalJSX.BqAvatar & JSXBase.HTMLAttributes<HTMLBqAvatarElement>;
"bq-badge": LocalJSX.BqBadge & JSXBase.HTMLAttributes<HTMLBqBadgeElement>;
"bq-breadcrumb": LocalJSX.BqBreadcrumb & JSXBase.HTMLAttributes<HTMLBqBreadcrumbElement>;
"bq-breadcrumb-item": LocalJSX.BqBreadcrumbItem & JSXBase.HTMLAttributes<HTMLBqBreadcrumbItemElement>;
/**
* Buttons are designed for users to take action on a page or a screen.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { newE2EPage } from '@stencil/core/testing';

describe('bq-breadcrumb-item', () => {
it('should render', async () => {
const page = await newE2EPage();
await page.setContent('<bq-breadcrumb-item></bq-breadcrumb-item>');

const element = await page.find('bq-breadcrumb-item');

expect(element).toHaveClass('hydrated');
});

it('should have shadow root', async () => {
const page = await newE2EPage();
await page.setContent('<bq-breadcrumb-item></bq-breadcrumb-item>');

const element = await page.find('bq-breadcrumb-item');

expect(element.shadowRoot).not.toBeNull();
});

it('should display text', async () => {
const page = await newE2EPage();
await page.setContent('<bq-breadcrumb-item>Home</bq-breadcrumb-item>');

const element = await page.find('bq-breadcrumb-item');

expect(element).toEqualText('Home');
});

it('should render `button` tag', async () => {
const page = await newE2EPage();
await page.setContent('<bq-breadcrumb-item>Home</bq-breadcrumb-item>');

const element = await page.find('bq-breadcrumb-item >>> .breadcrumb-item');

expect(element.tagName.toLocaleLowerCase()).toBe('button');
});

it('should render `a` tag', async () => {
const page = await newE2EPage();
await page.setContent('<bq-breadcrumb-item href="https://example.com/">Home</bq-breadcrumb-item>');

const element = await page.find('bq-breadcrumb-item >>> .breadcrumb-item');

expect(element.tagName.toLocaleLowerCase()).toBe('a');
});
});
139 changes: 139 additions & 0 deletions packages/bee-q/src/components/breadcrumb-item/bq-breadcrumb-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { h, Component, Prop, Element, Event, EventEmitter } from '@stencil/core';

import { isDefined } from '../../shared/utils';

/**
* @part base - The component wrapper container
* @part base - The breadcrumb item wrapper (`button` or `a`)
* @part content - The `span` tag that loads the content item
* @part separator - The `span` tag that loads the separator
*/
@Component({
tag: 'bq-breadcrumb-item',
styleUrl: './scss/bq-breadcrumb-item.scss',
shadow: true,
})
export class BqBreadcrumbItem {
// Own Properties
// ====================

// Reference to host HTML element
// ===================================

@Element() el!: HTMLBqBreadcrumbItemElement;

// State() variables
// Inlined decorator, alphabetical order
// =======================================

// Public Property API
// ========================

/**
* The aria-label that corresponds to the full title of the destination page.
* This won't be shown in the page, but it will be used by screen readers and other assistive devices.
*/
@Prop() ariaLabel: string;

/** If true, the item is the last element inside breadcrumb */
@Prop() isLastItem: boolean = false;

/** If set, the breadcrumb item will be rendered as an `<a>` with this `href`, otherwise, a `<button>` will be rendered. */
@Prop({ reflect: true }) href: string;

/** Where to display the link in the browser context. Relevant only if `href` is set. */
@Prop({ reflect: true }) target: '_blank' | '_parent' | '_self' | '_top';

/** Where to display the link in the browser context. Relevant only if `href` is set. */
@Prop({ reflect: true }) rel: string = 'noreferrer noopener';

// Prop lifecycle events
// =======================

// Events section
// Requires JSDocs for public API documentation
// ==============================================

/** Handler to be called when item loses focus */
@Event() bqBlur: EventEmitter<HTMLBqBreadcrumbItemElement>;

/** Handler to be called when item is focused */
@Event() bqFocus: EventEmitter<HTMLBqBreadcrumbItemElement>;

/** Handler to be called when item is clicked */
@Event() bqClick: EventEmitter<HTMLBqBreadcrumbItemElement>;

// Component lifecycle events
// Ordered by their natural call order
// =====================================

// Listeners
// ==============

// Public methods API
// These methods are exposed on the host element.
// Always use two lines.
// Public Methods must be async.
// Requires JSDocs for public API documentation.
// ===============================================

// Local methods
// Internal business logic.
// These methods cannot be called from the host element.
// =======================================================

private onBlur = () => {
this.bqBlur.emit(this.el);
};

private onFocus = () => {
this.bqFocus.emit(this.el);
};

private onClick = () => {
this.bqClick.emit(this.el);
};

// render() function
// Always the last one in the class.
// ===================================

render() {
const isLink = isDefined(this.href);
const TagElem = isLink ? 'a' : 'button';

return (
<div class="flex items-center" aria-label={this.ariaLabel} part="base">
<TagElem
class="breadcrumb-item"
href={isLink ? this.href : undefined}
rel={isLink && this.target ? 'noreferrer noopener' : undefined}
target={isLink ? this.target : undefined}
onBlur={this.onBlur}
onFocus={this.onFocus}
onClick={this.onClick}
aria-current={isLink && this.isLastItem ? 'location' : undefined} // indicates the last link as the current page
part="item"
>
<span
class={{
'flex items-center gap-xs2': true,
'text-text-brand': this.isLastItem,
}}
part="content"
>
<slot></slot>
</span>
</TagElem>
<span
class={{
'breadcrumb-separator': true,
}}
part="separator"
>
<slot name="separator"></slot>
</span>
</div>
);
}
}
40 changes: 40 additions & 0 deletions packages/bee-q/src/components/breadcrumb-item/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# bq-breadcrumb-item



<!-- Auto Generated Below -->


## Properties

| Property | Attribute | Description | Type | Default |
| ------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------- | ----------------------- |
| `ariaLabel` | `aria-label` | The aria-label that corresponds to the full title of the destination page. This won't be shown in the page, but it will be used by screen readers and other assistive devices. | `string` | `undefined` |
| `href` | `href` | If set, the breadcrumb item will be rendered as an `<a>` with this `href`, otherwise, a `<button>` will be rendered. | `string` | `undefined` |
| `isLastItem` | `is-last-item` | If true, the item is the last element inside breadcrumb | `boolean` | `false` |
| `rel` | `rel` | Where to display the link in the browser context. Relevant only if `href` is set. | `string` | `'noreferrer noopener'` |
| `target` | `target` | Where to display the link in the browser context. Relevant only if `href` is set. | `"_blank" \| "_parent" \| "_self" \| "_top"` | `undefined` |


## Events

| Event | Description | Type |
| --------- | ------------------------------------------ | ------------------------------------------ |
| `bqBlur` | Handler to be called when item loses focus | `CustomEvent<HTMLBqBreadcrumbItemElement>` |
| `bqClick` | Handler to be called when item is clicked | `CustomEvent<HTMLBqBreadcrumbItemElement>` |
| `bqFocus` | Handler to be called when item is focused | `CustomEvent<HTMLBqBreadcrumbItemElement>` |


## Shadow Parts

| Part | Description |
| ------------- | ------------------------------------------ |
| `"base"` | The component wrapper container |
| `"content"` | The `span` tag that loads the content item |
| `"item"` | |
| `"separator"` | The `span` tag that loads the separator |


----------------------------------------------

*Built with [StencilJS](https://stenciljs.com/)*
Loading

0 comments on commit 07d4da5

Please sign in to comment.