diff --git a/config/tailwind.primitve.colors.ts b/config/tailwind.primitve.colors.ts index 11f5e92c7..6c16305e3 100644 --- a/config/tailwind.primitve.colors.ts +++ b/config/tailwind.primitve.colors.ts @@ -64,16 +64,16 @@ export const PRIMITIVE_COLORS = { 1000: 'var(--bq-green-1000)', }, grey: { - 50: 'var(--bq-neutral-50)', - 100: 'var(--bq-neutral-100)', - 200: 'var(--bq-neutral-200)', - 300: 'var(--bq-neutral-300)', - 400: 'var(--bq-neutral-400)', - 500: 'var(--bq-neutral-500)', - 600: 'var(--bq-neutral-600)', - 700: 'var(--bq-neutral-700)', - 800: 'var(--bq-neutral-800)', - 900: 'var(--bq-neutral-900)', + 50: 'var(--bq-grey-50)', + 100: 'var(--bq-grey-100)', + 200: 'var(--bq-grey-200)', + 300: 'var(--bq-grey-300)', + 400: 'var(--bq-grey-400)', + 500: 'var(--bq-grey-500)', + 600: 'var(--bq-grey-600)', + 700: 'var(--bq-grey-700)', + 800: 'var(--bq-grey-800)', + 900: 'var(--bq-grey-900)', }, indigo: { 100: 'var(--bq-indigo-100)', diff --git a/packages/bee-q/src/components.d.ts b/packages/bee-q/src/components.d.ts index 1f18627ef..95b632cab 100644 --- a/packages/bee-q/src/components.d.ts +++ b/packages/bee-q/src/components.d.ts @@ -8,6 +8,7 @@ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; import { TAvatarShape, TAvatarSize } from "./components/avatar/bq-avatar.types"; import { TBadgeSize } from "./components/badge/bq-badge.types"; import { TButtonAppearance, TButtonSize, TButtonType, TButtonVariant } from "./components/button/bq-button.types"; +import { TDialogFooterAppearance, TDialogSize } from "./components/dialog/bq-dialog.types"; import { TDividerOrientation, TDividerStrokeLinecap, TDividerTitleAlignment } from "./components/divider/bq-divider.types"; import { TIconWeight } from "./components/icon/bq-icon.types"; import { TNotificationType } from "./components/notification/bq-notification.types"; @@ -22,6 +23,7 @@ import { FloatingUIPlacement } from "./services/interfaces"; export { TAvatarShape, TAvatarSize } from "./components/avatar/bq-avatar.types"; export { TBadgeSize } from "./components/badge/bq-badge.types"; export { TButtonAppearance, TButtonSize, TButtonType, TButtonVariant } from "./components/button/bq-button.types"; +export { TDialogFooterAppearance, TDialogSize } from "./components/dialog/bq-dialog.types"; export { TDividerOrientation, TDividerStrokeLinecap, TDividerTitleAlignment } from "./components/divider/bq-divider.types"; export { TIconWeight } from "./components/icon/bq-icon.types"; export { TNotificationType } from "./components/notification/bq-notification.types"; @@ -172,6 +174,44 @@ export namespace Components { */ "value": string; } + interface BqDialog { + /** + * Dismiss or cancel the dialog + */ + "cancel": () => Promise; + /** + * If true, the dialog will not close when clicking on the backdrop overlay + */ + "disableCloseClickOutside": boolean; + /** + * If true, the dialog will not close when the [Esc] key is press + */ + "disableCloseEscKeydown": boolean; + /** + * The appearance of footer + */ + "footerApperance": TDialogFooterAppearance; + /** + * Closes the dialog + */ + "hide": () => Promise; + /** + * If true, it hides the close button + */ + "hideCloseButton": boolean; + /** + * If true, the dialog will be shown as open + */ + "open": boolean; + /** + * Open the dialog + */ + "show": () => Promise; + /** + * The size of the dialog + */ + "size": TDialogSize; + } interface BqDivider { /** * If true, the divider has a dashed pattern @@ -592,6 +632,10 @@ export interface BqCheckboxCustomEvent extends CustomEvent { detail: T; target: HTMLBqCheckboxElement; } +export interface BqDialogCustomEvent extends CustomEvent { + detail: T; + target: HTMLBqDialogElement; +} export interface BqIconCustomEvent extends CustomEvent { detail: T; target: HTMLBqIconElement; @@ -663,6 +707,12 @@ declare global { prototype: HTMLBqCheckboxElement; new (): HTMLBqCheckboxElement; }; + interface HTMLBqDialogElement extends Components.BqDialog, HTMLStencilElement { + } + var HTMLBqDialogElement: { + prototype: HTMLBqDialogElement; + new (): HTMLBqDialogElement; + }; interface HTMLBqDividerElement extends Components.BqDivider, HTMLStencilElement { } var HTMLBqDividerElement: { @@ -762,6 +812,7 @@ declare global { "bq-badge": HTMLBqBadgeElement; "bq-button": HTMLBqButtonElement; "bq-checkbox": HTMLBqCheckboxElement; + "bq-dialog": HTMLBqDialogElement; "bq-divider": HTMLBqDividerElement; "bq-icon": HTMLBqIconElement; "bq-notification": HTMLBqNotificationElement; @@ -929,6 +980,44 @@ declare namespace LocalJSX { */ "value": string; } + interface BqDialog { + /** + * If true, the dialog will not close when clicking on the backdrop overlay + */ + "disableCloseClickOutside"?: boolean; + /** + * If true, the dialog will not close when the [Esc] key is press + */ + "disableCloseEscKeydown"?: boolean; + /** + * The appearance of footer + */ + "footerApperance"?: TDialogFooterAppearance; + /** + * If true, it hides the close button + */ + "hideCloseButton"?: boolean; + /** + * Callback handler emitted when the dialog has been canceled or dismissed + */ + "onBqCancel"?: (event: BqDialogCustomEvent) => void; + /** + * Callback handler emitted when the dialog will close + */ + "onBqClose"?: (event: BqDialogCustomEvent) => void; + /** + * Callback handler emitted when the dialog will open + */ + "onBqOpen"?: (event: BqDialogCustomEvent) => void; + /** + * If true, the dialog will be shown as open + */ + "open"?: boolean; + /** + * The size of the dialog + */ + "size"?: TDialogSize; + } interface BqDivider { /** * If true, the divider has a dashed pattern @@ -1377,6 +1466,7 @@ declare namespace LocalJSX { "bq-badge": BqBadge; "bq-button": BqButton; "bq-checkbox": BqCheckbox; + "bq-dialog": BqDialog; "bq-divider": BqDivider; "bq-icon": BqIcon; "bq-notification": BqNotification; @@ -1407,6 +1497,7 @@ declare module "@stencil/core" { */ "bq-button": LocalJSX.BqButton & JSXBase.HTMLAttributes; "bq-checkbox": LocalJSX.BqCheckbox & JSXBase.HTMLAttributes; + "bq-dialog": LocalJSX.BqDialog & JSXBase.HTMLAttributes; "bq-divider": LocalJSX.BqDivider & JSXBase.HTMLAttributes; /** * Icons are simplified images that graphically explain the meaning of an object on the screen. diff --git a/packages/bee-q/src/components/button/readme.md b/packages/bee-q/src/components/button/readme.md index 9b2497ff7..28bf945ac 100644 --- a/packages/bee-q/src/components/button/readme.md +++ b/packages/bee-q/src/components/button/readme.md @@ -49,6 +49,7 @@ Buttons are designed for users to take action on a page or a screen. ### Used by + - [bq-dialog](../dialog) - [bq-notification](../notification) ### Depends on @@ -59,6 +60,7 @@ Buttons are designed for users to take action on a page or a screen. ```mermaid graph TD; bq-button --> bq-icon + bq-dialog --> bq-button bq-notification --> bq-button style bq-button fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/packages/bee-q/src/components/dialog/__tests__/bq-dialog.e2e.ts b/packages/bee-q/src/components/dialog/__tests__/bq-dialog.e2e.ts new file mode 100644 index 000000000..6c1148ec4 --- /dev/null +++ b/packages/bee-q/src/components/dialog/__tests__/bq-dialog.e2e.ts @@ -0,0 +1,41 @@ +import { newE2EPage } from '@stencil/core/testing'; + +describe('bq-dialog', () => { + it('should render', async () => { + const page = await newE2EPage(); + await page.setContent(''); + + const element = await page.find('bq-dialog'); + + expect(element).toHaveClass('hydrated'); + }); + + it('should have shadow root', async () => { + const page = await newE2EPage(); + await page.setContent(''); + + const element = await page.find('bq-dialog'); + + expect(element.shadowRoot).not.toBeNull(); + }); + + it('should display title', async () => { + const page = await newE2EPage(); + await page.setContent('

Dialog Title

'); + + const element = await page.find('bq-dialog'); + + expect(element).toEqualText('Dialog Title'); + }); + + it('should display content', async () => { + const page = await newE2EPage(); + await page.setContent( + '

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

', + ); + + const element = await page.find('bq-dialog'); + + expect(element).toEqualText('Lorem Ipsum is simply dummy text of the printing and typesetting industry.'); + }); +}); diff --git a/packages/bee-q/src/components/dialog/_storybook/bq-dialog.mdx b/packages/bee-q/src/components/dialog/_storybook/bq-dialog.mdx new file mode 100644 index 000000000..85b524aed --- /dev/null +++ b/packages/bee-q/src/components/dialog/_storybook/bq-dialog.mdx @@ -0,0 +1,24 @@ +import { ArgTypes, Stories, Title, Subtitle } from '@storybook/addon-docs'; + +Dialog + +Description A Dialog is a UI component used to display additional content or prompt a user for action. +It provides a way to display additional information, options, or controls in a separate, non-obstructive interface element. + +Usage + +To ensure a seamless user experience and maximize the effectiveness of the Dialog Modal component, it is recommended to follow these best practices: + +- Keep it simple: Avoid overwhelming the user with excessive content within the Dialog Modal. Strive for simplicity and present only the essential information or options necessary to complete the intended action or convey the message. +- Use a clear and concise title: Provide a descriptive title that clearly communicates the purpose or content of the Dialog Modal. This helps users quickly understand the context and relevance of the dialog. +- Prioritize actions: If there are multiple actions within the Dialog Modal, make sure to present them in an order that helps users easily identify and choose the appropriate action. Consider using clear call-to-action buttons with descriptive labels to guide the user. + +👍 When to use + +- Displaying additional information, options, or controls that are relevant to a specific task or action. +- Prompting a user for confirmation or input, such as when performing a potentially destructive action or making changes to a critical setting. +- Providing a way for users to access related content or features without navigating away from the current interface. + +Properties + + diff --git a/packages/bee-q/src/components/dialog/_storybook/bq-dialog.stories.tsx b/packages/bee-q/src/components/dialog/_storybook/bq-dialog.stories.tsx new file mode 100644 index 000000000..2ac5071c0 --- /dev/null +++ b/packages/bee-q/src/components/dialog/_storybook/bq-dialog.stories.tsx @@ -0,0 +1,167 @@ +import type { Args, Meta, StoryObj } from '@storybook/web-components'; +import { html, nothing } from 'lit-html'; + +import mdx from './bq-dialog.mdx'; + +import { DIALOG_FOOTER_APPEARANCE, DIALOG_SIZE } from '../bq-dialog.types'; + +const meta: Meta = { + title: 'Components/Dialog', + component: 'bq-dialog', + parameters: { + docs: { + page: mdx, + }, + }, + argTypes: { + 'disable-close-click-outside': { control: 'boolean' }, + 'disable-close-esc-keydown': { control: 'boolean' }, + 'footer-apperance': { control: 'select', options: [...DIALOG_FOOTER_APPEARANCE] }, + 'hide-close-button': { control: 'boolean' }, + open: { control: 'boolean' }, + size: { control: 'select', options: [...DIALOG_SIZE] }, + // Events + bqCancel: { action: 'bqCancel' }, + bqClose: { action: 'bqClose' }, + bqOpen: { action: 'bqOpen' }, + // Not part of the public API + noContent: { control: 'boolean', table: { disable: true } }, + noFooter: { control: 'boolean', table: { disable: true } }, + }, + args: { + 'disable-close-click-outside': false, + 'disable-close-esc-keydown': false, + 'hide-close-button': false, + 'footer-apperance': 'standard', + open: false, + size: 'medium', + // Not part of the public API + noContent: false, + noFooter: false, + }, +}; + +export default meta; + +type Story = StoryObj; + +const Template = (args: Args) => { + const handleOpenDialog = async () => { + const dialogElem = document.querySelector('bq-dialog'); + await dialogElem.show(); + }; + + return html` + Open Dialog + +

+ + Title +

+ ${!args.noContent + ? html` +

+ Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the + industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and + scrambled it to make a type specimen book. +

+ ` + : nothing} + ${!args.noFooter + ? html` +
+ Button + Button + Button +
+ ` + : nothing} +
+ `; +}; + +export const Default: Story = { + render: Template, + args: { + open: true, + }, +}; + +export const HighlightFooter: Story = { + render: Template, + args: { + 'footer-apperance': 'highlight', + open: true, + }, +}; + +export const NoFooter: Story = { + render: Template, + args: { + noFooter: true, + open: true, + }, +}; + +const ConfirmTemplate = (args: Args) => { + const handleOpenDialog = async () => { + const dialogElem = document.querySelector('bq-dialog'); + await dialogElem.show(); + }; + + const handleDialogConfirm = async () => { + const dialogElem = document.querySelector('bq-dialog'); + await dialogElem.hide(); + alert('Account deactivated'); + }; + + const handleDialogCancel = async () => { + const dialogElem = document.querySelector('bq-dialog'); + await dialogElem.cancel(); + }; + + return html` + Deactivate account + +

+ + Deactivate account +

+

Are your sure you want to deactivate your account? All of your data will be permanently removed.

+ This action cannot be undone +
+ Cancel + Yes, deactive +
+
+ `; +}; + +export const DialogConfirm: Story = { + render: ConfirmTemplate, + args: { + 'disable-close-click-outside': true, + 'disable-close-esc-keydown': true, + 'hide-close-button': true, + }, +}; diff --git a/packages/bee-q/src/components/dialog/bq-dialog.tsx b/packages/bee-q/src/components/dialog/bq-dialog.tsx new file mode 100644 index 000000000..ed3496517 --- /dev/null +++ b/packages/bee-q/src/components/dialog/bq-dialog.tsx @@ -0,0 +1,251 @@ +import { Component, Element, Event, EventEmitter, h, Listen, Method, Prop, State, Watch } from '@stencil/core'; + +import { DIALOG_FOOTER_APPEARANCE, DIALOG_SIZE, TDialogFooterAppearance, TDialogSize } from './bq-dialog.types'; +import { hasSlotContent, validatePropValue } from '../../shared/utils'; + +/** + * @part body - The `
` that holds the dialog body content + * @part button-close - The button that close the dialog on click + * @part container - The `
` container that holds the dialog content + * @part dialog - The `` wrapper container inside the shadow DOM + * @part footer - The `