diff --git a/.vscode/settings.json b/.vscode/settings.json index 4a79363fd..a442a591d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -36,7 +36,6 @@ }, "html.customData": ["./dist/bee-q/custom-elements.json"], "less.validate": false, - "prettier.prettierPath": "node_modules/prettier", "scss.validate": false, "stylelint.configFile": ".stylelintrc.json", "stylelint.validate": [ diff --git a/config/theme/default/light.ts b/config/theme/default/light.ts index b1cc22305..c79d73a68 100644 --- a/config/theme/default/light.ts +++ b/config/theme/default/light.ts @@ -120,8 +120,8 @@ export const DefaultLightTheme = { 'bq-text--secondary-active': 'var(--bq-grey-700)', 'bq-text--secondary-disabled': 'var(--bq-grey-400)', /** Inverse */ - 'bq-text--inverse': 'var(--bq-neutral-white)', - 'bq-text--inverse-disabled': 'var(--bq-grey-50)', + 'bq-text--inverse': 'var(--bq-grey-50)', + 'bq-text--inverse-disabled': 'var(--bq-grey-100)', /** Brand */ 'bq-text--brand': 'var(--bq-iris-600)', 'bq-text--brand-hover': 'var(--bq-iris-500)', diff --git a/packages/beeq/src/components.d.ts b/packages/beeq/src/components.d.ts index af32fcc8e..b5f200812 100644 --- a/packages/beeq/src/components.d.ts +++ b/packages/beeq/src/components.d.ts @@ -19,6 +19,8 @@ import { TSideMenuAppearance, TSideMenuSize } from "./components/side-menu/bq-si import { TSliderType } from "./components/slider/bq-slider.types"; import { TSpinnerSize, TSpinnerTextPosition } from "./components/spinner/bq-spinner.types"; import { TStatusType } from "./components/status/bq-status.types"; +import { TStepsSize, TStepsType } from "./components/steps/bq-steps.types"; +import { TStepItemStatus } from "./components/step-item/bq-step-item.types"; import { TSwitchInnerLabel, TSwitchJustifyContent } from "./components/switch/bq-swithc.types"; import { TTabSize } from "./components/tab/bq-tab.types"; import { TTextareaAutoCapitalize, TTextareaWrap } from "./components/textarea/bq-textarea.types"; @@ -37,6 +39,8 @@ export { TSideMenuAppearance, TSideMenuSize } from "./components/side-menu/bq-si export { TSliderType } from "./components/slider/bq-slider.types"; export { TSpinnerSize, TSpinnerTextPosition } from "./components/spinner/bq-spinner.types"; export { TStatusType } from "./components/status/bq-status.types"; +export { TStepsSize, TStepsType } from "./components/steps/bq-steps.types"; +export { TStepItemStatus } from "./components/step-item/bq-step-item.types"; export { TSwitchInnerLabel, TSwitchJustifyContent } from "./components/switch/bq-swithc.types"; export { TTabSize } from "./components/tab/bq-tab.types"; export { TTextareaAutoCapitalize, TTextareaWrap } from "./components/textarea/bq-textarea.types"; @@ -273,7 +277,7 @@ export namespace Components { */ "strokeDashWidth"?: number; /** - * Set the lineap of the divider's stroke. This is applicable when the stroke is dashed + * Set the line of the divider's stroke. This is applicable when the stroke is dashed */ "strokeLinecap"?: TDividerStrokeLinecap; /** @@ -769,6 +773,34 @@ export namespace Components { */ "type": TStatusType; } + interface BqStepItem { + /** + * It defines prefix size + */ + "size"?: TStepsSize; + /** + * It defines step item appearance based on its status + */ + "status"?: TStepItemStatus; + /** + * It defines the step item type used + */ + "type"?: TStepsType; + } + interface BqSteps { + /** + * The color of the line that connects the steps. It should be a valid declarative color token. + */ + "dividerColor": string; + /** + * The size of the steps + */ + "size": TStepsSize; + /** + * The type of prefix element to use on the step items + */ + "type": TStepsType; + } /** * Toggle switches are digital on/off switches. * They should provide immediate results, giving users the freedom to control their preferences as needed. @@ -1096,6 +1128,10 @@ export interface BqSliderCustomEvent extends CustomEvent { detail: T; target: HTMLBqSliderElement; } +export interface BqStepItemCustomEvent extends CustomEvent { + detail: T; + target: HTMLBqStepItemElement; +} export interface BqSwitchCustomEvent extends CustomEvent { detail: T; target: HTMLBqSwitchElement; @@ -1270,6 +1306,18 @@ declare global { prototype: HTMLBqStatusElement; new (): HTMLBqStatusElement; }; + interface HTMLBqStepItemElement extends Components.BqStepItem, HTMLStencilElement { + } + var HTMLBqStepItemElement: { + prototype: HTMLBqStepItemElement; + new (): HTMLBqStepItemElement; + }; + interface HTMLBqStepsElement extends Components.BqSteps, HTMLStencilElement { + } + var HTMLBqStepsElement: { + prototype: HTMLBqStepsElement; + new (): HTMLBqStepsElement; + }; /** * Toggle switches are digital on/off switches. * They should provide immediate results, giving users the freedom to control their preferences as needed. @@ -1335,6 +1383,8 @@ declare global { "bq-slider": HTMLBqSliderElement; "bq-spinner": HTMLBqSpinnerElement; "bq-status": HTMLBqStatusElement; + "bq-step-item": HTMLBqStepItemElement; + "bq-steps": HTMLBqStepsElement; "bq-switch": HTMLBqSwitchElement; "bq-tab": HTMLBqTabElement; "bq-tab-group": HTMLBqTabGroupElement; @@ -1619,7 +1669,7 @@ declare namespace LocalJSX { */ "strokeDashWidth"?: number; /** - * Set the lineap of the divider's stroke. This is applicable when the stroke is dashed + * Set the line of the divider's stroke. This is applicable when the stroke is dashed */ "strokeLinecap"?: TDividerStrokeLinecap; /** @@ -2211,6 +2261,35 @@ declare namespace LocalJSX { */ "type"?: TStatusType; } + interface BqStepItem { + "onBqClick"?: (event: BqStepItemCustomEvent<{ target: HTMLBqStepItemElement; value: string }>) => void; + /** + * It defines prefix size + */ + "size"?: TStepsSize; + /** + * It defines step item appearance based on its status + */ + "status"?: TStepItemStatus; + /** + * It defines the step item type used + */ + "type"?: TStepsType; + } + interface BqSteps { + /** + * The color of the line that connects the steps. It should be a valid declarative color token. + */ + "dividerColor"?: string; + /** + * The size of the steps + */ + "size"?: TStepsSize; + /** + * The type of prefix element to use on the step items + */ + "type"?: TStepsType; + } /** * Toggle switches are digital on/off switches. * They should provide immediate results, giving users the freedom to control their preferences as needed. @@ -2506,6 +2585,8 @@ declare namespace LocalJSX { "bq-slider": BqSlider; "bq-spinner": BqSpinner; "bq-status": BqStatus; + "bq-step-item": BqStepItem; + "bq-steps": BqSteps; "bq-switch": BqSwitch; "bq-tab": BqTab; "bq-tab-group": BqTabGroup; @@ -2551,6 +2632,8 @@ declare module "@stencil/core" { */ "bq-spinner": LocalJSX.BqSpinner & JSXBase.HTMLAttributes; "bq-status": LocalJSX.BqStatus & JSXBase.HTMLAttributes; + "bq-step-item": LocalJSX.BqStepItem & JSXBase.HTMLAttributes; + "bq-steps": LocalJSX.BqSteps & JSXBase.HTMLAttributes; /** * Toggle switches are digital on/off switches. * They should provide immediate results, giving users the freedom to control their preferences as needed. diff --git a/packages/beeq/src/components/divider/bq-divider.tsx b/packages/beeq/src/components/divider/bq-divider.tsx index 823ff819d..0a835d8cc 100644 --- a/packages/beeq/src/components/divider/bq-divider.tsx +++ b/packages/beeq/src/components/divider/bq-divider.tsx @@ -23,7 +23,7 @@ const strokeDrawPositions = { /** * @part base - The component's internal wrapper. * @part dash-start - The component's internal svg wrapper for the start line of the divider's stroke - * @part dash-end - The componet's internal svg wrapper for the end line of the divider's stroke + * @part dash-end - The component's internal svg wrapper for the end line of the divider's stroke * @part dash-start-line - The component's internal line component of the divider's stroke * @part dash-end-line - The component's internal line component of the divider's stroke */ @@ -76,7 +76,7 @@ export class BqDivider { /** Set the min width of the divider's stroke when text is not centered. Value expressed in px */ @Prop({ reflect: true }) strokeBasis?: number = 20; - /** Set the lineap of the divider's stroke. This is applicable when the stroke is dashed */ + /** Set the line of the divider's stroke. This is applicable when the stroke is dashed */ @Prop({ reflect: true }) strokeLinecap?: TDividerStrokeLinecap = 'butt'; // Prop lifecycle events diff --git a/packages/beeq/src/components/divider/readme.md b/packages/beeq/src/components/divider/readme.md index 7e8f860f9..93e71613d 100644 --- a/packages/beeq/src/components/divider/readme.md +++ b/packages/beeq/src/components/divider/readme.md @@ -13,7 +13,7 @@ | `strokeColor` | `stroke-color` | Set the stroke color of the divider. The value should be a valid value of the palette color | `string` | `'stroke--secondary'` | | `strokeDashGap` | `stroke-dash-gap` | Set the gap of the divider's stroke. This is applicable when the stroke is dashed | `number` | `7` | | `strokeDashWidth` | `stroke-dash-width` | Set the width of each dash of the divider's stroke. This is applicable when the stroke is dashed | `number` | `12` | -| `strokeLinecap` | `stroke-linecap` | Set the lineap of the divider's stroke. This is applicable when the stroke is dashed | `"butt" \| "round" \| "square"` | `'butt'` | +| `strokeLinecap` | `stroke-linecap` | Set the line of the divider's stroke. This is applicable when the stroke is dashed | `"butt" \| "round" \| "square"` | `'butt'` | | `strokeThickness` | `stroke-thickness` | Set the thickness of the divider's stroke. Value expressed in px | `number` | `2` | | `titleAlignment` | `title-alignment` | Set the alignment of the title on the main axis of the divider (horizontal / vertical) | `"end" \| "middle" \| "start"` | `'middle'` | @@ -23,12 +23,25 @@ | Part | Description | | ------------------- | ------------------------------------------------------------------------------- | | `"base"` | The component's internal wrapper. | -| `"dash-end"` | The componet's internal svg wrapper for the end line of the divider's stroke | +| `"dash-end"` | The component's internal svg wrapper for the end line of the divider's stroke | | `"dash-end-line"` | The component's internal line component of the divider's stroke | | `"dash-start"` | The component's internal svg wrapper for the start line of the divider's stroke | | `"dash-start-line"` | The component's internal line component of the divider's stroke | +## Dependencies + +### Used by + + - [bq-steps](../steps) + +### Graph +```mermaid +graph TD; + bq-steps --> bq-divider + style bq-divider fill:#f9f,stroke:#333,stroke-width:4px +``` + ---------------------------------------------- *Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/beeq/src/components/step-item/__tests__/bq-step-item.e2e.ts b/packages/beeq/src/components/step-item/__tests__/bq-step-item.e2e.ts new file mode 100644 index 000000000..d17686c54 --- /dev/null +++ b/packages/beeq/src/components/step-item/__tests__/bq-step-item.e2e.ts @@ -0,0 +1,85 @@ +import { newE2EPage } from '@stencil/core/testing'; + +describe('bq-step-item', () => { + it('should render', async () => { + const page = await newE2EPage({ + html: ``, + }); + + const element = await page.find('bq-step-item'); + expect(element).toHaveClass('hydrated'); + }); + + it('should have shadow root', async () => { + const page = await newE2EPage({ + html: ``, + }); + + const element = await page.find('bq-step-item'); + expect(element.shadowRoot).not.toBeNull(); + }); + + it('should display text title', async () => { + const title = 'Title'; + const description = 'Description for step item'; + + const page = await newE2EPage({ + html: ` + + ${title} + ${description} + + `, + }); + + const text = await page.$eval('bq-step-item', (element) => { + const slotElement = element.shadowRoot.querySelector('.bq-step-item__content--title').querySelector('slot'); + const assignedElements = (slotElement as HTMLSlotElement).assignedElements({ flatten: true })[0]; + + return assignedElements.textContent; + }); + expect(text).toEqualText(title); + }); + + it('should display description', async () => { + const title = 'Title'; + const description = 'Description for step item'; + + const page = await newE2EPage({ + html: ` + + ${title} + ${description} + + `, + }); + + const text = await page.$eval('bq-step-item', (element) => { + const slotElement = element.shadowRoot.querySelector('slot[name="description"]'); + const assignedElements = (slotElement as HTMLSlotElement).assignedElements({ flatten: true })[0]; + + return assignedElements.textContent; + }); + expect(text).toEqualText(description); + }); + + it('should display icon prefix', async () => { + const page = await newE2EPage({ + html: ` + + + Title + Description + + `, + }); + + const prefix = await page.$eval('bq-step-item', (element) => { + const slotElement = element.shadowRoot.querySelector('slot[name="prefix"]'); + const assignedElements = (slotElement as HTMLSlotElement).assignedElements({ flatten: true })[0]; + + return assignedElements.tagName; + }); + expect(prefix).toMatch(/bq-icon/i); + }); +}); diff --git a/packages/beeq/src/components/step-item/bq-step-item.tsx b/packages/beeq/src/components/step-item/bq-step-item.tsx new file mode 100644 index 000000000..8e99b088d --- /dev/null +++ b/packages/beeq/src/components/step-item/bq-step-item.tsx @@ -0,0 +1,144 @@ +import { Component, Element, Event, EventEmitter, h, Prop, Watch } from '@stencil/core'; + +import { STEP_ITEM_STATUS, TStepItemStatus } from './bq-step-item.types'; +import { isHTMLElement, validatePropValue } from '../../shared/utils'; +import { STEPS_SIZE, TStepsSize, TStepsType } from '../steps/bq-steps.types'; + +/** + * @part base - The component's base wrapper. + * @part title - The component's title. + * @part description - The component's description. + */ +@Component({ + tag: 'bq-step-item', + styleUrl: './scss/bq-step-item.scss', + shadow: true, +}) +export class BqStepItem { + // Own Properties + // ==================== + + // Reference to host HTML element + // =================================== + @Element() el!: HTMLBqStepItemElement; + + // State() variables + // Inlined decorator, alphabetical order + // ======================================= + + // Public Property API + // ======================== + + /** It defines prefix size */ + @Prop({ reflect: true }) size?: TStepsSize = 'medium'; + + /** It defines step item appearance based on its status */ + @Prop({ reflect: true }) status?: TStepItemStatus = 'default'; + + /** It defines the step item type used */ + @Prop({ reflect: true }) type?: TStepsType; + + // Prop lifecycle events + // ======================= + + @Watch('size') + @Watch('status') + checkPropValues() { + validatePropValue(STEPS_SIZE, 'medium', this.el, 'size'); + validatePropValue(STEP_ITEM_STATUS, 'default', this.el, 'status'); + + this.handleIconPrefix(); + } + + // Events section + // Requires JSDocs for public API documentation + // ============================================== + @Event() bqClick: EventEmitter<{ target: HTMLBqStepItemElement; value: string }>; + + // Component lifecycle events + // Ordered by their natural call order + // ===================================== + + componentWillLoad() { + this.checkPropValues(); + } + + componentDidLoad() { + this.checkPropValues(); + } + + // 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 get isDisabled(): boolean { + return this.status === 'disabled'; + } + + private get isCurrent(): boolean { + return this.status === 'current'; + } + + private handleIconPrefix = () => { + const iconElem = this.el.querySelector('[slot="prefix"]'); + if (!iconElem || !isHTMLElement(iconElem, 'bq-icon')) return; + + iconElem.size = this.size === 'small' ? 24 : 32; + iconElem.weight = this.isCurrent ? 'fill' : 'regular'; + }; + + // render() function + // Always the last one in the class. + // =================================== + + render() { + return ( +
+
+ +
+
+ {/* TITLE */} +
+ +
+ {/* DESCRIPTION */} +
+ +
+
+
+ ); + } +} diff --git a/packages/beeq/src/components/step-item/bq-step-item.types.ts b/packages/beeq/src/components/step-item/bq-step-item.types.ts new file mode 100644 index 000000000..42112de94 --- /dev/null +++ b/packages/beeq/src/components/step-item/bq-step-item.types.ts @@ -0,0 +1,2 @@ +export const STEP_ITEM_STATUS = ['default', 'current', 'completed', 'error', 'disabled'] as const; +export type TStepItemStatus = (typeof STEP_ITEM_STATUS)[number]; diff --git a/packages/beeq/src/components/step-item/readme.md b/packages/beeq/src/components/step-item/readme.md new file mode 100644 index 000000000..bc6a21815 --- /dev/null +++ b/packages/beeq/src/components/step-item/readme.md @@ -0,0 +1,35 @@ +# bq-step-item + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| -------- | --------- | --------------------------------------------------- | ---------------------------------------------------------------- | ----------- | +| `size` | `size` | It defines prefix size | `"medium" \| "small"` | `'medium'` | +| `status` | `status` | It defines step item appearance based on its status | `"completed" \| "current" \| "default" \| "disabled" \| "error"` | `'default'` | +| `type` | `type` | It defines the step item type used | `"dot" \| "icon" \| "numeric"` | `undefined` | + + +## Events + +| Event | Description | Type | +| --------- | ----------- | ---------------------------------------------------------------- | +| `bqClick` | | `CustomEvent<{ target: HTMLBqStepItemElement; value: string; }>` | + + +## Shadow Parts + +| Part | Description | +| --------------- | ----------------------------- | +| `"base"` | The component's base wrapper. | +| `"description"` | The component's description. | +| `"title"` | The component's title. | + + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/beeq/src/components/step-item/scss/bq-step-item.scss b/packages/beeq/src/components/step-item/scss/bq-step-item.scss new file mode 100644 index 000000000..26a61b4ff --- /dev/null +++ b/packages/beeq/src/components/step-item/scss/bq-step-item.scss @@ -0,0 +1,73 @@ +/* -------------------------------------------------------------------------- */ +/* Step item styles */ +/* -------------------------------------------------------------------------- */ + +@import './bq-step-item.variables'; + +:host { + --bq-icon--color: theme('colors.icon.primary'); + @apply bg-bg-primary; +} + +.bq-step-item__prefix.dot, +.bq-step-item__prefix.icon { + ::slotted(bq-icon) { + --bq-icon--color: var(--bq-step-item--prefix-color); + } + + &.current { + ::slotted(bq-icon) { + --bq-icon--color: var(--bq-step-item--prefix-color-current); + } + } + + &.completed { + ::slotted(bq-icon) { + --bq-icon--color: var(--bq-step-item--prefix-color-completed); + } + } + + &.error { + ::slotted(bq-icon) { + --bq-icon--color: var(--bq-step-item--prefix-color-error); + } + } + + &.disabled { + ::slotted(bq-icon) { + --bq-icon--color: var(--bq-step-item--prefix-color-disabled); + } + } +} + +.bq-step-item__prefix.numeric { + @apply flex items-center justify-center rounded-full; + @apply h-[--bq-step-item--prefix-num-size] w-[--bq-step-item--prefix-num-size] bg-[--bq-step-item--prefix-num-bg-color]; + @apply font-outfit text-m leading-regular font-semibold; + + &.small { + @apply [--bq-step-item--prefix-num-size:--bq-spacing-l] text-s; + } + + // Status + + &.current { + @apply bg-[var(--bq-step-item--prefix-color-current)] text-text-primary-alt; + } + + &.completed { + @apply bg-ui-success-alt text-text-success; + } + + &.error { + @apply bg-ui-danger-alt text-text-danger; + } + + &.disabled { + @apply bg-ui-secondary-disabled text-text-primary-disabled; + } +} + +.bq-step-item__content--description::slotted(*) { + @apply text-s leading-regular text-text-primary-disabled; +} diff --git a/packages/beeq/src/components/step-item/scss/bq-step-item.variables.scss b/packages/beeq/src/components/step-item/scss/bq-step-item.variables.scss new file mode 100644 index 000000000..70f9ee8b1 --- /dev/null +++ b/packages/beeq/src/components/step-item/scss/bq-step-item.variables.scss @@ -0,0 +1,23 @@ +/* -------------------------------------------------------------------------- */ +/* Step item custom properties */ +/* -------------------------------------------------------------------------- */ + +:host { + /** + * @prop --bq-step-item--prefix-color - Color of the prefix icon + * @prop --bq-step-item--prefix-color-current - Color of the prefix icon when current + * @prop --bq-step-item--prefix-color-completed - Color of the prefix icon when completed + * @prop --bq-step-item--prefix-color-error - Color of the prefix icon when error + * @prop --bq-step-item--prefix-color-disabled - Color of the prefix icon when disabled + * @prop --bq-step-item--prefix-num-size - Size of the prefix number + * @prop --bq-step-item--prefix-num-bg-color - Background color of the prefix number + */ + --bq-step-item--prefix-color: theme('colors.icon.secondary'); + --bq-step-item--prefix-color-current: theme('colors.icon.brand'); + --bq-step-item--prefix-color-completed: theme('colors.icon.success'); + --bq-step-item--prefix-color-error: theme('colors.icon.danger'); + --bq-step-item--prefix-color-disabled: theme('colors.icon.secondary-disabled'); + + --bq-step-item--prefix-num-size: theme('spacing.xl'); + --bq-step-item--prefix-num-bg-color: theme('colors.ui.secondary'); +} diff --git a/packages/beeq/src/components/steps/__tests__/bq-steps.e2e.ts b/packages/beeq/src/components/steps/__tests__/bq-steps.e2e.ts new file mode 100644 index 000000000..86aa1699b --- /dev/null +++ b/packages/beeq/src/components/steps/__tests__/bq-steps.e2e.ts @@ -0,0 +1,49 @@ +import { newE2EPage } from '@stencil/core/testing'; + +describe('bq-steps', () => { + it('should render', async () => { + const page = await newE2EPage({ + html: ` + + + + Title + Description + + + `, + }); + + const element = await page.find('bq-steps'); + expect(element).toHaveClass('hydrated'); + }); + + it('should have shadow root', async () => { + const page = await newE2EPage({ + html: ``, + }); + + const element = await page.find('bq-steps'); + expect(element.shadowRoot).not.toBeNull(); + }); + + it('should render the correct number of steps', async () => { + const page = await newE2EPage({ + html: ` + + + 1 + Title + + + 2 + Title + + + `, + }); + + const steps = await page.findAll('bq-step-item'); + expect(steps).toHaveLength(2); + }); +}); diff --git a/packages/beeq/src/components/steps/_storybook/bq-steps.mdx b/packages/beeq/src/components/steps/_storybook/bq-steps.mdx new file mode 100644 index 000000000..1d5f16acd --- /dev/null +++ b/packages/beeq/src/components/steps/_storybook/bq-steps.mdx @@ -0,0 +1,32 @@ +import { ArgTypes, Title, Subtitle } from '@storybook/addon-docs'; + +
+
+ Steps + + The Steps Component is a UI element used to display a series of steps or stages in a process or task. Steps are commonly used in wizards, onboarding flows, or multi-step forms, and are a way to help users understand the progression of a task or process and their current status. + It typically consists of a visually appealing representation of the current step, along with optional labels, icons, or additional information associated with each step. + + Usage + + - Displaying the steps in a wizard, onboarding flow, or multi-step form, so that users can understand the progression of the task and their current status. + - Providing an overview of the steps in a process or task, such as outlining the steps in a recipe or the stages of a project. + - Representing the progress of a task or process, such as indicating the current step, completed steps, and future steps. + + 👍 When to use + + 1. Multi-step processes: Use stepper items when there is a need to guide users through a multi-step process, such as signing up, completing a form, or placing an order. + 2. Wizard-like interfaces: Stepper items work well for implementing wizard-like interfaces where users need to progress through a series of steps to complete a task. + 3. Progress tracking: Stepper items can be used to visually track the progress of a user's actions, such as completing a tutorial or a course with multiple lessons. + + Properties + + bq-steps + + + + bq-step-item + + +
+
diff --git a/packages/beeq/src/components/steps/_storybook/bq-steps.stories.tsx b/packages/beeq/src/components/steps/_storybook/bq-steps.stories.tsx new file mode 100644 index 000000000..b9d1ad2c0 --- /dev/null +++ b/packages/beeq/src/components/steps/_storybook/bq-steps.stories.tsx @@ -0,0 +1,133 @@ +import { Args, Meta, StoryObj } from '@storybook/web-components'; +import { html, nothing } from 'lit-html'; +import { ifDefined } from 'lit-html/directives/if-defined.js'; + +import mdx from './bq-steps.mdx'; +import { STEPS_SIZE, STEPS_TYPE } from '../bq-steps.types'; + +const meta: Meta = { + title: 'Components/Steps', + component: 'bq-steps', + parameters: { + docs: { + page: mdx, + }, + }, + argTypes: { + 'divider-color': { control: 'text' }, + type: { control: 'select', options: [...STEPS_TYPE] }, + size: { control: 'select', options: [...STEPS_SIZE] }, + // Not part of the public API + children: { control: 'text', table: { disable: true } }, + }, + args: { + 'divider-color': 'ui--secondary', + size: 'medium', + }, +}; +export default meta; + +const Template = (args: Args) => { + return html` + + ${ifDefined(args.children) ? args.children : nothing} + + `; +}; + +export const Dots: StoryObj = { + render: Template, + args: { + type: 'dot', + children: html` + + + Title + Description + + + + Title + Description + + + + Title + Description + + + + Title + Description + + + + Title + Description + + `, + }, +}; + +export const Icons: StoryObj = { + render: Template, + args: { + type: 'icon', + children: html` + + + Flight + Reserve your flight + + + + Accommodation + Reserve your accommodation + + + + Rent a car + There was an error with your reservation + + + + Enjoy your holidays! + You're ready for your vacations + + `, + }, +}; + +export const Numbers: StoryObj = { + render: Template, + args: { + type: 'numeric', + children: html` + + 1 + Title + Description + + + 2 + Title + Description + + + 3 + Title + Description + + + 4 + Title + Description + + + 4 + Title + Description + + `, + }, +}; diff --git a/packages/beeq/src/components/steps/bq-steps.tsx b/packages/beeq/src/components/steps/bq-steps.tsx new file mode 100644 index 000000000..9a768b3f7 --- /dev/null +++ b/packages/beeq/src/components/steps/bq-steps.tsx @@ -0,0 +1,124 @@ +import { Component, Element, h, Prop, Watch } from '@stencil/core'; + +import { STEPS_SIZE, STEPS_TYPE, TStepsSize, TStepsType } from './bq-steps.types'; +import { validatePropValue } from '../../shared/utils'; + +/** + * @part container - The container wrapper of the Steps component + * @part divider-base - The base wrapper of the divider component + * @part divider-dash-start - The dash start wrapper of the divider component + * @part divider-dash-end - The dash end wrapper of the divider component + */ +@Component({ + tag: 'bq-steps', + styleUrl: './scss/bq-steps.scss', + shadow: true, +}) +export class BqSteps { + // Own Properties + // ==================== + + private stepElem: HTMLElement; + + // Reference to host HTML element + // =================================== + + @Element() el!: HTMLBqStepsElement; + + // State() variables + // Inlined decorator, alphabetical order + // ======================================= + + // Public Property API + // ======================== + + /** The color of the line that connects the steps. It should be a valid declarative color token. */ + @Prop({ reflect: true }) dividerColor: string = 'ui--secondary'; + + /** The size of the steps */ + @Prop({ reflect: true }) size: TStepsSize = 'medium'; + + /** The type of prefix element to use on the step items */ + @Prop({ reflect: true }) type: TStepsType; + + // Prop lifecycle events + // ======================= + + @Watch('type') + @Watch('size') + checkPropValues() { + validatePropValue(STEPS_SIZE, 'medium', this.el, 'size'); + validatePropValue(STEPS_TYPE, 'numeric', this.el, 'type'); + + this.setStepItemProps(); + } + // Events section + // Requires JSDocs for public API documentation + // ============================================== + + // Component lifecycle events + // Ordered by their natural call order + // ===================================== + + componentDidLoad() { + this.setStepItemProps(); + } + + // 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 get bqSteps(): HTMLBqStepItemElement[] { + if (!this.stepElem) return []; + + const slot = this.stepElem.querySelector('slot'); + return [...slot.assignedElements({ flatten: true })].filter( + (el: HTMLBqSideMenuItemElement) => el.tagName.toLowerCase() === 'bq-step-item', + ) as [HTMLBqSideMenuItemElement]; + } + + private setStepItemProps = () => { + this.bqSteps.forEach((bqStepElem: HTMLBqStepItemElement) => { + bqStepElem.size = this.size; + bqStepElem.type = this.type; + }); + }; + + // private handleChange = (event) => { + // this.bqChange.emit(event); + // } + + // render() function + // Always the last one in the class. + // =================================== + + render() { + const dividerPaddingTop = this.size === 'small' ? 'pt-s' : 'pt-m'; + + return ( +
(this.stepElem = div)} + part="container" + > + + +
+ ); + } +} diff --git a/packages/beeq/src/components/steps/bq-steps.types.ts b/packages/beeq/src/components/steps/bq-steps.types.ts new file mode 100644 index 000000000..0944bd776 --- /dev/null +++ b/packages/beeq/src/components/steps/bq-steps.types.ts @@ -0,0 +1,5 @@ +export const STEPS_TYPE = ['numeric', 'icon', 'dot'] as const; +export type TStepsType = (typeof STEPS_TYPE)[number]; + +export const STEPS_SIZE = ['medium', 'small'] as const; +export type TStepsSize = (typeof STEPS_SIZE)[number]; diff --git a/packages/beeq/src/components/steps/readme.md b/packages/beeq/src/components/steps/readme.md new file mode 100644 index 000000000..37b332e0e --- /dev/null +++ b/packages/beeq/src/components/steps/readme.md @@ -0,0 +1,42 @@ +# bq-steps + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| -------------- | --------------- | -------------------------------------------------------------------------------------------- | ------------------------------ | ----------------- | +| `dividerColor` | `divider-color` | The color of the line that connects the steps. It should be a valid declarative color token. | `string` | `'ui--secondary'` | +| `size` | `size` | The size of the steps | `"medium" \| "small"` | `'medium'` | +| `type` | `type` | The type of prefix element to use on the step items | `"dot" \| "icon" \| "numeric"` | `undefined` | + + +## Shadow Parts + +| Part | Description | +| ---------------------- | ----------------------------------------------- | +| `"container"` | The container wrapper of the Steps component | +| `"divider-base"` | The base wrapper of the divider component | +| `"divider-dash-end"` | The dash end wrapper of the divider component | +| `"divider-dash-start"` | The dash start wrapper of the divider component | + + +## Dependencies + +### Depends on + +- [bq-divider](../divider) + +### Graph +```mermaid +graph TD; + bq-steps --> bq-divider + style bq-steps fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/beeq/src/components/steps/scss/bq-steps.scss b/packages/beeq/src/components/steps/scss/bq-steps.scss new file mode 100644 index 000000000..b6652a6a9 --- /dev/null +++ b/packages/beeq/src/components/steps/scss/bq-steps.scss @@ -0,0 +1,17 @@ +/* -------------------------------------------------------------------------- */ +/* Steps styles */ +/* -------------------------------------------------------------------------- */ + +@import './bq-steps.variables'; + +:host { + @apply block; +} + +::slotted(bq-step-item:not(:first-child)) { + @apply ps-[--bq-steps--gap]; +} + +::slotted(bq-step-item:not(:last-child)) { + @apply pe-[--bq-steps--gap]; +} diff --git a/packages/beeq/src/components/steps/scss/bq-steps.variables.scss b/packages/beeq/src/components/steps/scss/bq-steps.variables.scss new file mode 100644 index 000000000..ad9a861b0 --- /dev/null +++ b/packages/beeq/src/components/steps/scss/bq-steps.variables.scss @@ -0,0 +1,12 @@ +/* -------------------------------------------------------------------------- */ +/* Steps custom properties */ +/* -------------------------------------------------------------------------- */ + +:host { + /** + * @prop --bq-steps--divider-color - Divider color + * @prop --bq-steps--gap - Gap between steps + */ + --bq-steps--divider-color: theme('colors.ui.secondary'); + --bq-steps--gap: theme('spacing.m'); +} diff --git a/packages/beeq/src/shared/utils/props.ts b/packages/beeq/src/shared/utils/props.ts index fc23628a6..a3d357e37 100644 --- a/packages/beeq/src/shared/utils/props.ts +++ b/packages/beeq/src/shared/utils/props.ts @@ -5,7 +5,7 @@ export type TValidProperty = TExtractProp<{ [K in keyof E]: E[K] extends T * Validate the element property value, if is one of the accepted values * * @param {readonly} ACCEPTED_VALUES - The list of the accepted values to check against. - * @param {unknow} fallbackValue - The default value to assign + * @param {unknown} fallbackValue - The default value to assign * @param {Element} element - The component reference * @param {string} propertyName - The property name (will be used in the console notification) * @returns {void} @@ -17,7 +17,7 @@ export const validatePropValue = ( propertyName: TValidProperty, ): void => { const propertyValue = element[propertyName as string]; - // Early return if the property value is one of the accetped values + // Early return if the property value is one of the accepted values if (ACCEPTED_VALUES.includes(propertyValue)) return; // Override property with fallback value element[propertyName as string] = fallbackValue;