From c99efc9bbbc454bdc5ab3239fcbe154cab04bd87 Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Thu, 30 May 2024 10:17:09 -0700 Subject: [PATCH 01/21] init --- packages/web-components/src/drawer/README.md | 149 ++++ packages/web-components/src/drawer/define.ts | 4 + .../src/drawer/drawer.definition.ts | 17 + .../src/drawer/drawer.options.ts | 60 ++ .../web-components/src/drawer/drawer.spec.ts | 351 ++++++++ .../src/drawer/drawer.stories.ts | 843 ++++++++++++++++++ .../src/drawer/drawer.styles.ts | 203 +++++ .../src/drawer/drawer.template.ts | 32 + packages/web-components/src/drawer/drawer.ts | 279 ++++++ packages/web-components/src/drawer/index.ts | 5 + packages/web-components/src/index.ts | 10 + 11 files changed, 1953 insertions(+) create mode 100644 packages/web-components/src/drawer/README.md create mode 100644 packages/web-components/src/drawer/define.ts create mode 100644 packages/web-components/src/drawer/drawer.definition.ts create mode 100644 packages/web-components/src/drawer/drawer.options.ts create mode 100644 packages/web-components/src/drawer/drawer.spec.ts create mode 100644 packages/web-components/src/drawer/drawer.stories.ts create mode 100644 packages/web-components/src/drawer/drawer.styles.ts create mode 100644 packages/web-components/src/drawer/drawer.template.ts create mode 100644 packages/web-components/src/drawer/drawer.ts create mode 100644 packages/web-components/src/drawer/index.ts diff --git a/packages/web-components/src/drawer/README.md b/packages/web-components/src/drawer/README.md new file mode 100644 index 00000000000000..ead6929a45f2b0 --- /dev/null +++ b/packages/web-components/src/drawer/README.md @@ -0,0 +1,149 @@ +# Drawer + +The `Drawer` component represents a drawer that can be opened and closed, typically used for navigation or additional content. + +## Design Spec + +Link to Drawer Design Spec in Figma: [Link]() + +## Engineering Spec + +The Fluent WC3 Drawer extends `FASTElement` + +### Class `Drawer` + +### Template + +```html + +
+
+ +
+
+ +
+ +
+
+``` + +### **Variables** + +| Name | Type | Description | +| ----------------- | ------------------------------- | ---------------------- | +| `DrawerPosition` | `start` `end` | Positions for Drawer | +| `DrawerSize` | `small` `medium` `large` `full` | Sizes for Drawer | +| `DrawerModalType` | `modal` `non-modal` `alert` | Modal types for Drawer | +| `DrawerType` | `overlay` `inline` | Types for Drawer | + +### **Attributes** + +| Name | Type | Default | Description | +| ------------------ | ----------------------------------- | ------------------- | -------------------------------------------------------------------------------- | +| `modal-type` | `modal` `non-modal` `alert` | `modal` | Determines whether the drawer should be displayed as modal, non-modal, or alert. | +| `type` | `overlay` `inline` | `false` | Determines whether the drawer should be displayed inline or as an overlay | +| `position` | `DrawerPosition \| undefined` | `start` | Sets the position of the drawer (left/right). | +| `size` | `DrawerSize \| number \| undefined` | `DrawerSize.medium` | Sets the control size of the drawer. | +| `open` | `boolean` | `false` | Sets the drawer to be open. | +| `aria-labelledby` | `string \| undefined` | `undefined` | Sets the aria-labelledby attribute of the drawer. | +| `aria-describedby` | `string \| undefined` | `undefined` | Sets the aria-describedby attribute of the drawer. | +| `aria-label` | `string \| undefined` | `undefined` | Sets the aria-label attribute of the drawer. | + +### **Events** + +| Name | Type | Description | +| -------------- | ------------- | ------------------------------------------ | +| `onOpenChange` | `CustomEvent` | Fires when the drawer is opened or closed. | +| `cancel` | `CustomEvent` | Fires when the drawer is dismissed. | + +### **Methods** + +| Name | Privacy | Description | +| ------- | ------- | ----------------- | +| `show` | public | Shows the drawer. | +| `close` | public | Hides the drawer. | + +### **Slots** + +| Name | Description | +| -------- | ------------------------------------- | +| `header` | The slot for header | +| | The default slot for the main content | +| `footer` | The slot for the footer | + +### **CSS Variables** + +| Name | Description | +| -------------------------- | ------------------------------------- | +| `--drawer-overflow-border` | Used to set the overflow border color | +| `--drawer-width` | Used to set the width of the drawer | + +## **Accessiblity** + +### **WAI-ARIA Roles, States, and Properties** + +- `role="complementary"` + + - The drawer component should have a role of "complementary" by default to indicate its supplementary content role. + +- `role="dialog"` + + - The drawer component should have a role of "dialog" when rendered as a modal. + +- `aria-disabled` + + - The `aria-disabled` attribute should be set to indicate whether the drawer is disabled or enabled. When the drawer is disabled, `aria-disabled="true"`, and when enabled, `aria-disabled="false"`. + +- `tabindex` + + - The `tabindex` attribute should be set to control the tab order of the drawer component. When the drawer is open, `tabindex="0"` should be set to make the component focusable, and when closed, `tabindex="-1"` should be set to remove it from the tab order. + +- `aria-modal` + + - The `aria-modal` attribute should be set to indicate whether the drawer is modal or non-modal. When the drawer is modal, `aria-modal="true"`, and when it is non-modal, `aria-modal="false"`. + +- `aria-label` + + - The `aria-label` attribute should be used to label the drawer. + +- `aria-describedby` + + - The `aria-describedby` attribute should be used to associate the drawer with an element that provides a description of its purpose or content. + +- `aria-labelledby` + + - The `aria-labelledby` attribute should be used to associate the drawer with an element that serves as its accessible label or title. + +### **Fluent Web Component v3 v.s Fluent React 9** + +
+ +**Component and Slot Mapping** + +| Fluent UI React 9 | Fluent Web Components 3 | +| ------------------------------- | ----------------------- | +| `` | `type="overlay"` | +| `` | `type="inline"` | +| `` | `slot="title"` | +| `` | `slot="action"` | +| ` ` | `default slot` | +| `` | `slot="navigation"` | +| `` | `slot="footer"` | diff --git a/packages/web-components/src/drawer/define.ts b/packages/web-components/src/drawer/define.ts new file mode 100644 index 00000000000000..d94d257122ee8b --- /dev/null +++ b/packages/web-components/src/drawer/define.ts @@ -0,0 +1,4 @@ +import { FluentDesignSystem } from '../fluent-design-system.js'; +import { definition } from './drawer.definition.js'; + +definition.define(FluentDesignSystem.registry); diff --git a/packages/web-components/src/drawer/drawer.definition.ts b/packages/web-components/src/drawer/drawer.definition.ts new file mode 100644 index 00000000000000..8c855559a706e7 --- /dev/null +++ b/packages/web-components/src/drawer/drawer.definition.ts @@ -0,0 +1,17 @@ +import { FluentDesignSystem } from '../fluent-design-system.js'; +import { Drawer } from './drawer.js'; +import { styles } from './drawer.styles.js'; +import { template } from './drawer.template.js'; + +/** + * + * @public + * @remarks + * HTML Element: + */ + +export const definition = Drawer.compose({ + name: `${FluentDesignSystem.prefix}-drawer`, + template, + styles, +}); diff --git a/packages/web-components/src/drawer/drawer.options.ts b/packages/web-components/src/drawer/drawer.options.ts new file mode 100644 index 00000000000000..510bafee64edb4 --- /dev/null +++ b/packages/web-components/src/drawer/drawer.options.ts @@ -0,0 +1,60 @@ +import type { ValuesOf } from '../utils/index.js'; + +/** + * A drawer can be positioned on the left or right side of the viewport. + */ +export const DrawerPosition = { + start: 'start', + end: 'end', +} as const; + +/** + * The position of the drawer. + * @public + */ +export type DrawerPosition = ValuesOf; + +/** + * A drawer can be different sizes + */ +export const DrawerSize = { + small: 'small', + medium: 'medium', + large: 'large', + full: 'full', +} as const; + +/** + * The size of the drawer. + * @public + */ +export type DrawerSize = ValuesOf; + +/** + * A drawer can be different sizes + */ +export const DrawerModalType = { + alert: 'alert', + modal: 'modal', + nonModal: 'non-modal', +} as const; + +/** + * The size of the drawer. + * @public + */ +export type DrawerModalType = ValuesOf; + +/** + * A drawer can be different sizes + */ +export const DrawerType = { + overlay: 'overlay', + inline: 'inline', +} as const; + +/** + * The size of the drawer. + * @public + */ +export type DrawerType = ValuesOf; diff --git a/packages/web-components/src/drawer/drawer.spec.ts b/packages/web-components/src/drawer/drawer.spec.ts new file mode 100644 index 00000000000000..aaf016f61f7dae --- /dev/null +++ b/packages/web-components/src/drawer/drawer.spec.ts @@ -0,0 +1,351 @@ +import { expect, test } from '@playwright/test'; +import type { Locator, Page } from '@playwright/test'; +import { keyEscape } from '@microsoft/fast-web-utilities'; + +import { fixtureURL } from '../helpers.tests.js'; +import type { Drawer } from './drawer.js'; + +test.describe('Drawer', () => { + let page: Page; + let element: Locator; + let dialog: Locator; + let root: Locator; + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + root = page.locator('#root'); + element = page.locator('fluent-drawer'); + dialog = page.locator('dialog'); + + await page.goto(fixtureURL('components-drawer--drawer')); + }); + + test.afterAll(async () => { + await page.close(); + }); + + // eslint-disable-next-line playwright/no-focused-test + test('should reflect size attribute', async () => { + await root.evaluate(node => { + node.innerHTML = /* html */ ` + + `; + }); + + await expect(element).toHaveAttribute('size', 'medium'); + await expect(element).toHaveJSProperty('size', 'medium'); + + await element.evaluate((node: Drawer) => { + node.size = 'small'; + }); + + await expect(element).toHaveAttribute('size', 'small'); + await expect(element).toHaveJSProperty('size', 'small'); + + await element.evaluate((node: Drawer) => { + node.size = 'large'; + }); + await expect(element).toHaveAttribute('size', 'large'); + await expect(element).toHaveJSProperty('size', 'large'); + + await element.evaluate((node: Drawer) => { + node.size = 'full'; + }); + await expect(element).toHaveAttribute('size', 'full'); + await expect(element).toHaveJSProperty('size', 'full'); + }); + + test('should reflect position attribute', async () => { + await root.evaluate(node => { + node.innerHTML = /* html */ ` + + `; + }); + + await expect(element).toHaveAttribute('position', 'end'); + await expect(element).toHaveJSProperty('position', 'end'); + + await element.evaluate((node: Drawer) => { + node.position = 'start'; + }); + + await expect(element).toHaveAttribute('position', 'start'); + await expect(element).toHaveJSProperty('position', 'start'); + }); + + test('should reflect modal-type attribute', async () => { + await root.evaluate(node => { + node.innerHTML = /* html */ ` + + `; + }); + + await expect(element).toHaveAttribute('modal-type', 'modal'); + await expect(element).toHaveJSProperty('modalType', 'modal'); + + await element.evaluate((node: Drawer) => { + node.modalType = 'non-modal'; + }); + + await expect(element).toHaveAttribute('modal-type', 'non-modal'); + await expect(element).toHaveJSProperty('modalType', 'non-modal'); + + await element.evaluate((node: Drawer) => { + node.modalType = 'alert'; + }); + + await expect(element).toHaveAttribute('modal-type', 'alert'); + await expect(element).toHaveJSProperty('modalType', 'alert'); + }); + + test('should reflect open attribute', async () => { + await root.evaluate(node => { + node.innerHTML = /* html */ ` + + `; + }); + + await expect(element).toHaveAttribute('open', ''); + await expect(element).toHaveJSProperty('open', true); + + await element.evaluate((node: Drawer) => { + node.open = false; + }); + + await expect(element).not.toHaveAttribute('open', ''); + await expect(element).toHaveJSProperty('open', false); + }); + + test('should reflect aria-label attribute', async () => { + await root.evaluate(node => { + node.innerHTML = /* html */ ` + + `; + }); + + await expect(element).toHaveAttribute('aria-label', 'abc'); + await expect(element).toHaveJSProperty('ariaLabel', 'abc'); + + await element.evaluate((node: Drawer) => { + node.ariaLabel = 'def'; + }); + + await expect(element).toHaveAttribute('aria-label', 'def'); + await expect(element).toHaveJSProperty('ariaLabel', 'def'); + }); + + test('should reflect ariaLabelledby attribute', async () => { + await root.evaluate(node => { + node.innerHTML = /* html */ ` + + `; + }); + + await expect(element).toHaveAttribute('aria-labelledby', 'abc'); + await expect(element).toHaveJSProperty('ariaLabelledby', 'abc'); + + await element.evaluate((node: Drawer) => { + node.ariaLabelledby = 'def'; + }); + + await expect(element).toHaveAttribute('aria-labelledby', 'def'); + await expect(element).toHaveJSProperty('ariaLabelledby', 'def'); + }); + + test('should reflect ariaDescribedby attribute', async () => { + await root.evaluate(node => { + node.innerHTML = /* html */ ` + + `; + }); + + await expect(element).toHaveAttribute('aria-describedby', 'abc'); + await expect(element).toHaveJSProperty('ariaDescribedby', 'abc'); + + await element.evaluate((node: Drawer) => { + node.ariaDescribedby = 'def'; + }); + + await expect(element).toHaveAttribute('aria-describedby', 'def'); + await expect(element).toHaveJSProperty('ariaDescribedby', 'def'); + }); + + test('show and hide methods should toggle open state of drawer', async () => { + await root.evaluate(node => { + node.innerHTML = /* html */ ` + + `; + }); + + await expect(element).not.toHaveAttribute('open', ''); + await expect(element).toHaveJSProperty('open', false); + + await element.evaluate((node: Drawer) => { + node.show(); + }); + + await expect(element).toHaveAttribute('open', ''); + await expect(element).toHaveJSProperty('open', true); + + await element.evaluate((node: Drawer) => { + node.hide(); + }); + + await expect(element).not.toHaveAttribute('open', ''); + await expect(element).toHaveJSProperty('open', false); + }); + + test('close method should close drawer', async () => { + await root.evaluate(node => { + node.innerHTML = /* html */ ` + + `; + }); + + await expect(element).toHaveAttribute('open', ''); + await expect(element).toHaveJSProperty('open', true); + + await element.evaluate((node: Drawer) => { + node.hide(); + }); + + await expect(element).not.toHaveAttribute('open', ''); + await expect(element).toHaveJSProperty('open', false); + }); + + test('show method should open drawer', async () => { + await root.evaluate(node => { + node.innerHTML = /* html */ ` + + `; + }); + + await expect(element).not.toHaveAttribute('open', ''); + await expect(element).toHaveJSProperty('open', false); + + await element.evaluate((node: Drawer) => { + node.show(); + }); + + await expect(element).toHaveAttribute('open', ''); + await expect(element).toHaveJSProperty('open', true); + }); + + test('a drawer with an alert modal-type should include a role of `alertdialog` on the control', async () => { + await root.evaluate(node => { + node.innerHTML = /* html */ ` + + `; + }); + await expect(dialog).toHaveAttribute('role', 'alertdialog'); + }); + + test('should emit an event when open property changes', async () => { + await root.evaluate(node => { + node.innerHTML = /* html */ ` + + `; + }); + + const [wasOpened] = await Promise.all([ + element.evaluate( + node => + new Promise(resolve => { + node.addEventListener('onOpenChange', () => resolve(true)); + }), + ), + await element.evaluate((node: Drawer) => { + node.show(); + }), + ]); + + expect(wasOpened).toBe(true); + }); + + test('drawer should close on escape keypress', async () => { + const first: Locator = element.locator('button', { hasText: 'First' }); + + await root.evaluate(node => { + node.innerHTML = /* html */ ` + + + + `; + }); + + await expect(element).toHaveAttribute('open', ''); + await expect(element).toHaveJSProperty('open', true); + + await first.focus(); + + await expect(first).toBeFocused(); + + await first.press(keyEscape); + + await expect(element).not.toHaveAttribute('open', ''); + await expect(element).toHaveJSProperty('open', false); + }); + + test("should fire a 'dismiss' event when keydown is invoked on the document", async () => { + await root.evaluate(node => { + node.innerHTML = /* html */ ` + + `; + }); + + const [wasDismissed] = await Promise.all([ + element.evaluate( + node => + new Promise(resolve => { + node.addEventListener('cancel', () => resolve(true)); + }), + ), + element.evaluate(node => { + node.dispatchEvent( + new Event('cancel', { + key: 'Escape', + } as EventInit), + ); + }), + ]); + + expect(wasDismissed).toBe(true); + }); + + test('should trap focus in the drawer when arrow up/down keys', async () => { + const first: Locator = element.locator('button', { hasText: 'First' }); + const second: Locator = element.locator('button', { hasText: 'Second' }); + const third: Locator = page.locator('button', { hasText: 'Third' }); + await root.evaluate(node => { + node.innerHTML = /* html */ ` + + + + + + `; + }); + + await first.focus(); + + await expect(first).toBeFocused(); + await first.press('Tab'); + + await expect(second).toBeFocused(); + await second.press('Tab'); + + await expect(third).toBeFocused(); + await third.press('Tab'); + + await expect(first).toBeFocused(); + await first.press('Tab'); + + await expect(second).toBeFocused(); + await second.press('Shift+Tab'); + + await expect(first).toBeFocused(); + await first.press('Shift+Tab'); + + await expect(third).toBeFocused(); + }); +}); diff --git a/packages/web-components/src/drawer/drawer.stories.ts b/packages/web-components/src/drawer/drawer.stories.ts new file mode 100644 index 00000000000000..db4e3dda1cf421 --- /dev/null +++ b/packages/web-components/src/drawer/drawer.stories.ts @@ -0,0 +1,843 @@ +import { html } from '@microsoft/fast-element'; +import type { Args, Meta } from '@storybook/html'; +import { renderComponent } from '../helpers.stories.js'; +import { RadioGroup } from '../radio-group/radio-group.js'; +import type { Drawer as FluentDrawer } from './drawer.js'; +import { DrawerModalType, DrawerPosition, DrawerSize, DrawerType } from './drawer.options.js'; +import './define.js'; + +type DrawerStoryArgs = Args & FluentDrawer; +type DrawerStoryMeta = Meta; + +const dismissed20Regular = html``; + +const toggleDrawer = (drawerID: string, containerID?: string) => { + const drawer = document.getElementById(drawerID) as FluentDrawer; + if (containerID) { + const container = document.querySelector(`#${containerID}`); + const drawers = container!.querySelectorAll(`fluent-drawer:not(#${drawerID})`); + drawers.forEach(drawerElement => { + const drawer = drawerElement as FluentDrawer; + if (drawer.open) { + drawer.hide(); + } + }); + } + if (drawer.open) { + drawer.hide(); + } else { + drawer.show(); + } +}; + +const toggleSelectedDrawer = (radioGroupId: string) => { + const radioGroup = document.getElementById(radioGroupId) as RadioGroup; + const idSubString = radioGroup.value; + const drawerToOpen = document.querySelector(`#drawer-${idSubString}`) as FluentDrawer; + const drawersToClose = document.querySelectorAll(`fluent-drawer:not(#drawer-${idSubString})`); + if (drawerToOpen.open) { + drawerToOpen.hide(); + } else { + drawerToOpen.show(); + } + drawersToClose.forEach(d => { + const drawer = d as FluentDrawer; + if (drawer.open) { + drawer.hide(); + } + }); +}; + +const hideDrawer = (drawerID: string) => { + const drawer = document.getElementById(drawerID) as FluentDrawer; + if (drawer.open) { + drawer.hide(); + } +}; + +setTimeout(() => { + const input = document.getElementById('custom-size-input') as FluentDrawer; + const drawer = document.getElementById('drawer-width-custom') as FluentDrawer; + input.addEventListener('input', (e: any) => { + drawer.style.setProperty('--drawer-width', `${e.target.value}px`); + }); +}, 2000); + +const storyTemplate = html` +
+ +
+
+ +
+
+

Drawer Header

+ + + The drawer gives users a quick entry point to configuration and information. It should be used when + retaining context is beneficial to users. An overlay is optional depending on whether or not interacting + with the backgroun d content is beneficial to the user's context/scenario. An overlay makes the drawer + blocking and signifies that the users full attention is required when making configurations. + +
+ + Please select an option + Option 1 + Option 2 + Option 3 + +
+
+
+ Close + Do Something +
+
+
+
+
+

Drawer

+ + +

+ The Drawer gives users a quick entry point to configuration and information. It should be used when + retaining context is beneficial to users. +

+
+ + fluent-drawer + + + Position + Start + End + + + Toggle Drawer +
+
+ +
+
+

Drawer Header

+ + The drawer gives users a quick entry point to configuration and information. It should be used when + retaining context is beneficial to users. An overlay is optional depending on whether or not interacting + with the backgroun d content is beneficial to the user's context/scenario. An overlay makes the drawer + blocking and signifies that the users full attention is required when making configurations. + +
+ + Please select an option + Option 1 + Option 2 + Option 3 + +
+
+
+ Close + Do Something +
+
+
+
+
+
+`; + +export default { + title: 'Components/Drawer', + args: { + modalType: DrawerModalType.modal, + type: DrawerType.overlay, + size: DrawerSize.medium, + }, + argTypes: { + position: { + options: Object.values(DrawerPosition), + table: { + type: { + summary: 'Sets the position of drawer', + }, + defaultValue: { + summary: DrawerPosition.start, + }, + }, + }, + modalType: { + options: Object.values(DrawerModalType), + control: { + type: 'select', + }, + table: { + type: { + summary: 'Sets the modal type of the drawer', + }, + defaultValue: { + summary: DrawerModalType.modal, + }, + }, + }, + type: { + options: Object.values(DrawerType), + control: { + type: 'select', + }, + table: { + type: { + summary: 'Sets the modal type of the drawer', + }, + defaultValue: { + summary: DrawerType.overlay, + }, + }, + }, + size: { + options: Object.values(DrawerSize), + control: { + type: 'select', + }, + table: { + type: { + summary: 'Sets the width of drawer', + }, + defaultValue: { + summary: DrawerSize.medium, + }, + }, + }, + }, +} as DrawerStoryMeta; + +export const Drawer = renderComponent(storyTemplate).bind({}); + +export const Modal = renderComponent(html` +
+
+ +
+
+
+

Modal

+ + ${dismissed20Regular} + +
+ +

+ When a modal dialog is open, the rest of the page is dimmed out and cannot be interacted with. The tab + sequence is kept within the dialog and moving the focus outside the dialog will imply closing it. This + is the default type of the component. +

+
+ + modal-type="modal" + +
+
+
+
+
+

Modal

+ +

+ When a modal dialog is open, the rest of the page is dimmed out and cannot be interacted with. The tab + sequence is kept within the dialog and moving the focus outside the dialog will imply closing it. This is the + default type of the component. +

+
+ + modal-type="modal" + + Toggle Drawer +
+
+`); + +export const Alert = renderComponent(html` +
+
+ +
+
+

Alert

+ +

+ When an alert dialog is open it interrupts the user's workflow to communicate an important message or + ask for a decision. Unlike a typical modal dialog, the user must take an action through the options + given to dismiss the dialog, and it cannot be dismissed through the dimmed background. +

+
+ + modal-type="alert" + +
+
+ Do Something + hideDrawer('drawer-alert')}>Close +
+
+
+
+
+

Alert

+ +

+ When an alert dialog is open it interrupts the user's workflow to communicate an important message or ask for + a decision. Unlike a typical modal dialog, the user must take an action through the options given to dismiss + the dialog, and it cannot be dismissed through the dimmed background. +

+
+ + modal-type="alert" + + Toggle Drawer +
+
+`); + +export const NonModal = renderComponent(html` +
+
+ +
+
+
+

Non Modal

+ + ${dismissed20Regular} + +
+ +

+ When a non-modal dialog is open, the rest of the page is not dimmed out and users can interact with the + rest of the page. This also implies that the tab focus can move outside the dialog when it reaches the + last focusable element. +

+
+ + modal-type="non-modal" + +
+
+
+
+
+

Non Modal

+ +

+ When a non-modal dialog is open, the rest of the page is not dimmed out and users can interact with the rest + of the page. This also implies that the tab focus can move outside the dialog when it reaches the last + focusable element. +

+
+ + modal-type="non-modal" + + Toggle Drawer +
+
+`); + +export const Overlay = renderComponent(html` +
+
+ +
+
+
+

Overlay

+ + ${dismissed20Regular} + +
+ +

+ A Drawer rendered with an overlay contains supplementary content and are used for complex creation, + edit, or management experiences. For example, viewing details about an item in a list or editing + settings. By default, drawer is blocking and signifies that the users full attention is required when + making configurations. +

+
+ + type="overlay" + +
+
+
+
+
+

Overlay

+ +

+ A Drawer rendered with an overlay contains supplementary content and are used for complex creation, edit, or + management experiences. For example, viewing details about an item in a list or editing settings. By default, + drawer is blocking and signifies that the users full attention is required when making configurations. +

+
+ + type="overlay" + + Toggle Drawer +
+
+`); + +export const Inline = renderComponent(html` +
+ +
+
+
+

Drawer Inline

+ + ${dismissed20Regular} + +
+ +

+ An inline Drawer is often used for navigation that is not dismissible. As it is on the same level as the + main surface, users can still interact with other UI elements. This could be useful for swapping between + different items in the main surface. +

+
+ + type="inline" + +
+
+
+
+

Inline

+ +

+ An inline Drawer is often used for navigation that is not dismissible. As it is on the same level as the main + surface, users can still interact with other UI elements. This could be useful for swapping between different + items in the main surface. +

+
+ + type="inline" + + Select a Drawer Position + + Start + End + + Toggle Drawer +
+ +
+
+
+

Drawer Inline

+ + ${dismissed20Regular} + +
+ +

+ An inline Drawer is often used for navigation that is not dismissible. As it is on the same level as the + main surface, users can still interact with other UI elements. This could be useful for swapping between + different items in the main surface. +

+
+ + type="inline" + +
+
+
+
+`); + +export const Position = renderComponent(html` +
+
+ +
+
+
+

Drawer Position Start

+ + ${dismissed20Regular} + +
+ +

+ When a Drawer is invoked, it slides in from either the left or right side of the screen. This can be + specified by the position attribute. +

+
+ + default + +
+
+ Primary + Secondary +
+
+
+
+
+

Position

+ + +

+ When a Drawer is invoked, it slides in from either the left or right side of the screen. This can be specified + by the position attribute. +

+
+ + position="" + + Select a Drawer Position + + Start + End + + Toggle Drawer +
+
+ +
+
+
+

Drawer Position End

+ + ${dismissed20Regular} + +
+ +

+ The drawer component offers flexible positioning options to suit your layout needs. By using the + position attribute, you can easily place the drawer on either side of the screen. The attribute accepts + values of type DrawerPosition, which includes two options: 'start' and 'end'. The default position of + the Drawer is 'end'. +

+
+ + position="end" + +
+
+ Primary + Secondary +
+
+
+
+
+`); + +export const Size = renderComponent(html` +
+
+

Size

+ + +

The size attribute controls the width of the drawer. The default is medium.

+
+ + size="" + + Select a Drawer Size + + Small + Medium + Large + Full + + Toggle Drawer +
+ +
+
+
+

Drawer Small

+ + ${dismissed20Regular} + +
+ +

The size attribute controls the width of the drawer. The default is medium.

+
+ + size="small" + +
+
+
+ +
+
+
+

Drawer Medium

+ + ${dismissed20Regular} + +
+ +

The size attribute controls the width of the drawer. The default is medium.

+
+ + default + +
+
+
+ +
+
+ +
+

Drawer Large

+ + ${dismissed20Regular} + +
+

The size attribute controls the width of the drawer. The default is medium.

+
+ + size="large" + +
+
+
+ +
+
+
+

Drawer Full

+ + ${dismissed20Regular} + +
+ +

The size attribute controls the width of the drawer. The default is medium.

+
+ + size="full" + +
+
+
+
+`); + +export const CustomSize = renderComponent(html` +
+
+

Custom Size

+ + +

The Drawer can be sized to any custom width, by overriding the drawer-width CSS variable:

+
+ + var(--drawer-width) + + Set a Drawer Size + + Toggle Drawer +
+ +
+
+
+

Custom Size

+ + ${dismissed20Regular} + +
+ +

The Drawer can be sized to any custom width, by overriding the drawer-width CSS variable:

+
+ + var(--drawer-width) + +
+
+
+
+`); diff --git a/packages/web-components/src/drawer/drawer.styles.ts b/packages/web-components/src/drawer/drawer.styles.ts new file mode 100644 index 00000000000000..53c41e149831f0 --- /dev/null +++ b/packages/web-components/src/drawer/drawer.styles.ts @@ -0,0 +1,203 @@ +import { css } from '@microsoft/fast-element'; +import { display } from '../utils/index.js'; +import { + colorBackgroundOverlay, + colorNeutralBackground1, + colorNeutralForeground1, + colorStrokeFocus1, + colorStrokeFocus2, + colorTransparentStroke, + curveAccelerateMin, + curveDecelerateMid, + curveEasyEase, + durationNormal, + durationSlow, + fontFamilyBase, + fontSizeBase300, + fontWeightRegular, + lineHeightBase300, + shadow64, + strokeWidthThick, + strokeWidthThin, +} from '../theme/design-tokens.js'; + +/** Drawer styles + * @public + */ +export const styles = css` + ${display('block')} + + :host { + position: fixed; + height: 100%; + max-height: 100vh; + width: auto; + outline: none; + top: 0; + left: 0; + z-index: var(--drawer-elevation, 1000); + font-size: ${fontSizeBase300}; + line-height: ${lineHeightBase300}; + font-family: ${fontFamilyBase}; + font-weight: ${fontWeightRegular}; + color: ${colorNeutralForeground1}; + } + + :host([open][type='inline']), + :host([open][modal-type='non-modal']) { + width: 100%; + } + + :host([position='end']) { + inset-inline-start: unset; + inset-inline-end: 0; + } + + :host([type='inline']) dialog, + :host([type='inline']) { + position: relative; + width: 0; + } + + :host([type='inline']) dialog[open], + :host dialog[open] { + width: var(--drawer-width, 592px); + } + + :host([size='small']) dialog[open] { + width: 320px; + } + + :host([size='large']) dialog[open] { + width: 940px; + } + + :host([size='full']) dialog[open] { + width: 100%; + } + + :host([position='end']) dialog { + margin-inline-start: auto; + margin-inline-end: 0; + } + + :host([position='end']) dialog { + inset-inline-end: 0px; + } + + dialog { + background-color: transparent; + top: 0; + bottom: 0; + border-radius: 0; + height: 100%; + margin-inline-start: 0; + margin-inline-end: auto; + padding: 0; + max-width: 100%; + max-height: 100vh; + overflow: hidden; + width: var(--drawer-width, 592px); + box-shadow: ${shadow64}; + box-sizing: border-box; + display: flex; + flex-direction: column; + height: 100%; + border: ${strokeWidthThin} solid ${colorTransparentStroke}; + background: ${colorNeutralBackground1}; + position: absolute; + } + + :host([position='end']) dialog { + border-inline-end-color: ${colorTransparentStroke}; + border-inline-start-color: var(--drawer-separator, ${colorTransparentStroke}); + } + + :host([type='inline']) dialog { + box-shadow: none; + } + + :host([modal-type='non-modal']) dialog::backdrop { + display: none; + } + + dialog:focus-visible:after { + content: ''; + position: absolute; + inset: 0px; + cursor: pointer; + outline: none; + border: ${strokeWidthThick} solid ${colorStrokeFocus1}; + box-shadow: inset 0 0 0 1px ${colorStrokeFocus2}; + } + + dialog:focus-visible { + outline: none; + } + + dialog::backdrop { + background: ${colorBackgroundOverlay}; + inset: 0; + } + + :host([data-animating]) dialog::backdrop { + animation: drawer-fade; + animation-timing-function: ${curveEasyEase}; + animation-duration: ${durationSlow}; + } + + :host([data-animating][data-closing]) dialog::backdrop { + animation-direction: reverse; + } + + :host([data-animating]) dialog { + animation: drawer-slide-in-start; + animation-timing-function: ${curveDecelerateMid}; + animation-duration: ${durationNormal}; + } + + :host([data-animating][data-closing]) dialog { + animation-direction: reverse; + animation-timing-function: ${curveAccelerateMin}; + animation-duration: ${durationNormal}; + } + + :host([position='end'][data-animating]) dialog { + animation: drawer-slide-in-end; + animation-timing-function: ${curveDecelerateMid}; + animation-duration: ${durationNormal}; + } + + :host([position='end'][data-closing]) dialog { + animation-direction: reverse; + animation-timing-function: ${curveAccelerateMin}; + animation-duration: ${durationNormal}; + } + + @keyframes drawer-fade { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } + } + + @keyframes drawer-slide-in-start { + 0% { + transform: translate3D(-100%, 0, 0); + } + 100% { + transform: translate3D(0%, 0, 0); + } + } + + @keyframes drawer-slide-in-end { + 0% { + transform: translate3D(100%, 0, 0); + } + 100% { + transform: translate3D(0%, 0, 0); + } + } +`; diff --git a/packages/web-components/src/drawer/drawer.template.ts b/packages/web-components/src/drawer/drawer.template.ts new file mode 100644 index 00000000000000..6a88aa01d30fa4 --- /dev/null +++ b/packages/web-components/src/drawer/drawer.template.ts @@ -0,0 +1,32 @@ +import { ElementViewTemplate, html, ref } from '@microsoft/fast-element'; +import type { Drawer } from './drawer.js'; +import { DrawerModalType } from './drawer.options.js'; + +/** + * The template for the Drawer component. + * @public + */ +export function drawerTemplate(): ElementViewTemplate { + return html` + + + + `; +} + +export const template: ElementViewTemplate = drawerTemplate(); diff --git a/packages/web-components/src/drawer/drawer.ts b/packages/web-components/src/drawer/drawer.ts new file mode 100644 index 00000000000000..9d6db62f2840c6 --- /dev/null +++ b/packages/web-components/src/drawer/drawer.ts @@ -0,0 +1,279 @@ +import { attr, FASTElement, observable, Updates } from '@microsoft/fast-element'; +import { eventAnimationEnd, keyEscape } from '@microsoft/fast-web-utilities'; +import { DrawerModalType, DrawerPosition, DrawerSize, DrawerType } from './drawer.options.js'; + +/** + * Drawer + * + * A Drawer component for creating modal or non-modal drawers with various configurations. + * @extends FASTElement + * + * @attr {DrawerModalType} modal-type - Determines whether the drawer should be displayed as modal, non-modal, or alert. When in modal or alert mode, an overlay is applied over the rest of the view. + * @attr {string} aria-labelledby - The ID of the element that labels the drawer. + * @attr {string} aria-describedby - The ID of the element that describes the drawer. + * @attr {DrawerType} type - Sets the type of the drawer (overlay/inline). + * @attr {DrawerPosition} position - Sets the position of the drawer (start/end). + * @attr {DrawerSize} size - Sets the size of the drawer (small/medium/large). + * + * @csspart dialog - The dialog element that represents the drawer. + * + * @slot - Default slot for the drawer's content. + * + * @fires dismiss - Emitted when the drawer is dismissed. + * + * @method connectedCallback() - Called when the custom element is connected to the document's DOM. + * @method disconnectedCallback() - Called when the custom element is disconnected from the document's DOM. + * @method show() - Opens the drawer if it is currently hidden. + * @method hide() - Closes the drawer if it is currently open. + * @method clickHandler(event) - Handles click events on the drawer. + * @method keydownHandler(event) - Handles keydown events on the drawer. + * @method openChanged(oldValue, newValue) - Handles changes to the `open` property. + * @method typeChanged(oldValue, newValue) - Handles changes to the `type` attribute. + * + * @summary A flexible drawer component that can be used in various configurations such as modal, non-modal, alert, inline, overlay, with different sizes and positions. + * + * @tag fluent-drawer + */ + +export class Drawer extends FASTElement { + /** + * This method is called when the custom element becomes connected to the document's DOM. + * @public + */ + public connectedCallback(): void { + super.connectedCallback(); + /** + * By placing the syncDialogOpenState() inside Updates.enqueue(), we ensure that any actions to synchronize the component's state based on initial attributes are deferred until after all other pending DOM updates are processed. + * This ensures that the internal state of the dialog element aligns with the initial attributes specified on the , such as automatically opening the dialog when the 'open' attribute is present at the time of connection to the DOM. + */ + Updates.enqueue(() => { + this.syncDialogOpenState(); + }); + } + + /** + * This method is called when the custom element is disconnected from the document's DOM. + * @public + */ + public disconnectedCallback(): void { + super.disconnectedCallback(); + } + + /** + * The dialog element. + * @public + * + */ + @observable + public dialog!: HTMLDialogElement; + + /** + * Determines whether the drawer should be displayed as modal, non-modal, or alert. + * When in modal or alert mode, an overlay is applied over the rest of the view. + * @public + */ + @attr({ attribute: 'modal-type' }) + public modalType: DrawerModalType = DrawerModalType.modal; + + /** + * The ID of the element that labels the drawer. + * @public + */ + @attr({ attribute: 'aria-labelledby' }) + public ariaLabelledby?: string; + + /** + * The ID of the element that describes the drawer. + * @public + */ + @attr({ attribute: 'aria-describedby' }) + public ariaDescribedby?: string; + + /** + * Sets the type of the drawer (overlay/inline). + * @public + * @defaultValue overlay + */ + @attr + public type?: DrawerType; + + /** + * Sets the position of the drawer (start/end). + * @public + * @defaultValue start + */ + @attr + public position?: DrawerPosition; + + /** + * Sets the size of the drawer (small/medium/large). + * @public + * @defaultValue medium + */ + @attr({ attribute: 'size' }) + public size?: DrawerSize; + + /** + * Sets the open state of the drawer + * @public + * @defaultValue false + */ + @attr({ mode: 'boolean' }) + public open: boolean = false; + + /** + * Indicates whether the drawer is currently closing. + * @private + */ + private closing: boolean = false; + + /** + * Ensures the dialog's visibility aligns with the `open` property. + * @private + */ + private syncDialogOpenState(): void { + if (this.open) { + this.show(); + } else { + this.hide(); + } + } + + /** + * Emits a 'cancel' event. + * @internal + */ + public dismiss(): void { + this.$emit('dismiss'); + } + + /** + * Handles changes to the `open` property. + * @public + */ + public openChanged(oldValue: boolean, newValue: boolean): void { + if (this.$fastController.isConnected) { + if (newValue) { + this.show(); + } else { + this.hide(); + } + } + } + + /** + * @public + * Method called when the 'modalType' attribute changes + */ + public typeChanged(oldValue: DrawerType, newValue: DrawerType): void { + if (newValue !== oldValue) { + if (newValue == DrawerType.inline) { + this.modalType = DrawerModalType.nonModal; + } + } + } + + /** + * Opens the drawer if it is currently hidden. + * @public + */ + public show(): void { + if (!this.dialog.open) { + if (this.type === DrawerType.inline || this.modalType === DrawerModalType.nonModal) { + this.dialog.show(); + } else { + this.dialog.showModal(); + } + this.open = true; + this.closing = false; + this.triggerAnimation(); + } + } + + /** + * Closes the drawer if it is currently open. + * @public + */ + public hide(): void { + if (this.dialog.open) { + this.closing = true; + Updates.enqueue(() => { + this.triggerAnimation(); + }); + } + } + + /** + * Handles click events on the drawer. + * + * @param event - The click event + * @returns boolean - Always returns true + * @public + */ + public clickHandler(event: Event): boolean { + event.preventDefault(); + if (this.open && this.modalType !== DrawerModalType.alert && event.target === this.dialog) { + this.hide(); + this.dismiss(); + } + return true; + } + + /** + * @public + * Handles keydown events on the drawer + * @param e - The keydown event + * @returns boolean | void + */ + public keydownHandler = (event: KeyboardEvent): boolean | void => { + if (event.defaultPrevented) { + return; + } + switch (event.key) { + case keyEscape: + event.preventDefault(); + + if (this.modalType !== DrawerModalType.alert) { + this.hide(); + this.dismiss(); + } + break; + default: + return true; + } + }; + + /** + * A function that calls the animation end handler. + * @private + */ + private readonly animationEndHandlerFunction = (): void => this.animationEndHandler(); + + /** + * Triggers the opening or closing animation on the drawer. + * @private + */ + private triggerAnimation(): void { + this.setAttribute('data-animating', ''); + if (this.closing) { + this.setAttribute('data-closing', ''); + } + this.dialog.addEventListener(eventAnimationEnd, this.animationEndHandlerFunction); + } + + /** + * Handles the end of the animation. + * @private + * + */ + private animationEndHandler(): void { + this.dialog.removeEventListener(eventAnimationEnd, this.animationEndHandlerFunction); + this.removeAttribute('data-animating'); + if (this.closing) { + this.removeAttribute('data-closing'); + this.dialog.close(); + this.open = false; + this.closing = false; + } + } +} diff --git a/packages/web-components/src/drawer/index.ts b/packages/web-components/src/drawer/index.ts new file mode 100644 index 00000000000000..e6a070ceec4e2b --- /dev/null +++ b/packages/web-components/src/drawer/index.ts @@ -0,0 +1,5 @@ +export * from './drawer.js'; +export * from './drawer.options.js'; +export { definition as DrawerDefinition } from './drawer.definition.js'; +export { styles as DrawerStyles } from './drawer.styles.js'; +export { template as DrawerTemplate } from './drawer.template.js'; diff --git a/packages/web-components/src/index.ts b/packages/web-components/src/index.ts index 1bab9cc0685425..bc766ae6c2cd5e 100644 --- a/packages/web-components/src/index.ts +++ b/packages/web-components/src/index.ts @@ -98,6 +98,16 @@ export { DividerTemplate, DividerStyles, } from './divider/index.js'; +export { + Drawer, + DrawerDefinition, + DrawerPosition, + DrawerSize, + DrawerModalType, + DrawerType, + DrawerTemplate, + DrawerStyles, +} from './drawer/index.js'; export { Image, ImageFit, ImageShape, ImageDefinition, ImageTemplate, ImageStyles } from './image/index.js'; export { Label, LabelSize, LabelWeight, LabelDefinition, LabelStyles, LabelTemplate } from './label/index.js'; export { Menu, MenuTemplate, MenuStyles, MenuDefinition } from './menu/index.js'; From 21fc694465c76759d13bac4eda6067662899f03a Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Thu, 30 May 2024 10:22:30 -0700 Subject: [PATCH 02/21] adds drawer exports --- packages/web-components/package.json | 4 ++++ packages/web-components/src/index-rollup.ts | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/packages/web-components/package.json b/packages/web-components/package.json index 3241859b87f8c6..64c3f18d794a1a 100644 --- a/packages/web-components/package.json +++ b/packages/web-components/package.json @@ -67,6 +67,10 @@ "types": "./dist/dts/divider/define.d.ts", "default": "./dist/esm/divider/define.js" }, + "./drawer.js": { + "types": "./dist/dts/drawer/define.d.ts", + "default": "./dist/esm/drawer/define.js" + }, "./image.js": { "types": "./dist/dts/image/define.d.ts", "default": "./dist/esm/image/define.js" diff --git a/packages/web-components/src/index-rollup.ts b/packages/web-components/src/index-rollup.ts index 47c6b8175e001e..6e0d8a87bd0dfa 100644 --- a/packages/web-components/src/index-rollup.ts +++ b/packages/web-components/src/index-rollup.ts @@ -93,6 +93,16 @@ export { DividerTemplate, DividerStyles, } from './divider/index.js'; +export { + Drawer, + DrawerDefinition, + DrawerPosition, + DrawerSize, + DrawerModalType, + DrawerType, + DrawerTemplate, + DrawerStyles, +} from './drawer/index.js'; export { Image, ImageFit, ImageShape, ImageDefinition, ImageTemplate, ImageStyles } from './image/index.js'; export { Label, LabelSize, LabelWeight, LabelDefinition, LabelStyles, LabelTemplate } from './label/index.js'; export { Menu, MenuTemplate, MenuStyles, MenuDefinition } from './menu/index.js'; From 365627120e469248daa04be9ef8ef4d9c352aa9d Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Thu, 30 May 2024 10:26:47 -0700 Subject: [PATCH 03/21] yarn change --- ...eb-components-f0a2f34a-306f-475b-b230-e24dd267cab0.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-web-components-f0a2f34a-306f-475b-b230-e24dd267cab0.json diff --git a/change/@fluentui-web-components-f0a2f34a-306f-475b-b230-e24dd267cab0.json b/change/@fluentui-web-components-f0a2f34a-306f-475b-b230-e24dd267cab0.json new file mode 100644 index 00000000000000..6d52baa7a36e52 --- /dev/null +++ b/change/@fluentui-web-components-f0a2f34a-306f-475b-b230-e24dd267cab0.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "feat(drawer): add drawer web component", + "packageName": "@fluentui/web-components", + "email": "email not defined", + "dependentChangeType": "patch" +} From 601d3d061c7c667bb7ee6403dd8d484c0151dcf0 Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Fri, 31 May 2024 11:40:08 -0700 Subject: [PATCH 04/21] refactors drawer animation to css only solution --- .../src/drawer/drawer.stories.ts | 49 ++-- .../src/drawer/drawer.styles.ts | 236 +++++++++--------- .../src/drawer/drawer.template.ts | 6 +- packages/web-components/src/drawer/drawer.ts | 138 ++-------- 4 files changed, 166 insertions(+), 263 deletions(-) diff --git a/packages/web-components/src/drawer/drawer.stories.ts b/packages/web-components/src/drawer/drawer.stories.ts index db4e3dda1cf421..aa31c8a2aa3a85 100644 --- a/packages/web-components/src/drawer/drawer.stories.ts +++ b/packages/web-components/src/drawer/drawer.stories.ts @@ -30,12 +30,12 @@ const toggleDrawer = (drawerID: string, containerID?: string) => { const drawers = container!.querySelectorAll(`fluent-drawer:not(#${drawerID})`); drawers.forEach(drawerElement => { const drawer = drawerElement as FluentDrawer; - if (drawer.open) { + if (drawer.dialog.open) { drawer.hide(); } }); } - if (drawer.open) { + if (drawer.dialog.open) { drawer.hide(); } else { drawer.show(); @@ -47,14 +47,14 @@ const toggleSelectedDrawer = (radioGroupId: string) => { const idSubString = radioGroup.value; const drawerToOpen = document.querySelector(`#drawer-${idSubString}`) as FluentDrawer; const drawersToClose = document.querySelectorAll(`fluent-drawer:not(#drawer-${idSubString})`); - if (drawerToOpen.open) { + if (drawerToOpen.dialog.open) { drawerToOpen.hide(); } else { drawerToOpen.show(); } drawersToClose.forEach(d => { const drawer = d as FluentDrawer; - if (drawer.open) { + if (drawer.dialog.open) { drawer.hide(); } }); @@ -62,7 +62,7 @@ const toggleSelectedDrawer = (radioGroupId: string) => { const hideDrawer = (drawerID: string) => { const drawer = document.getElementById(drawerID) as FluentDrawer; - if (drawer.open) { + if (drawer.dialog.open) { drawer.hide(); } }; @@ -143,8 +143,8 @@ const storyTemplate = html` id="drawer-default-start" position="start" size="${x => x.size}" - modal-type="${x => x.modalType}" type="${x => x.type}" + ?inline="${x => x.inline}" >
@@ -201,8 +201,8 @@ const storyTemplate = html` id="drawer-default-end" position="end" size="${x => x.size}" - modal-type="${x => x.modalType}" type="${x => x.type}" + ?inline="${x => x.inline}" >
@@ -238,8 +238,8 @@ const storyTemplate = html` export default { title: 'Components/Drawer', args: { - modalType: DrawerModalType.modal, - type: DrawerType.overlay, + type: DrawerModalType.modal, + inline: false, size: DrawerSize.medium, }, argTypes: { @@ -254,7 +254,7 @@ export default { }, }, }, - modalType: { + type: { options: Object.values(DrawerModalType), control: { type: 'select', @@ -268,17 +268,16 @@ export default { }, }, }, - type: { - options: Object.values(DrawerType), + inline: { control: { - type: 'select', + type: 'boolean', }, table: { type: { - summary: 'Sets the modal type of the drawer', + summary: 'Determines if the drawer is an inline drawer', }, defaultValue: { - summary: DrawerType.overlay, + summary: false, }, }, }, @@ -327,7 +326,7 @@ export const Modal = renderComponent(html`

- modal-type="modal" + type="modal"
@@ -343,7 +342,7 @@ export const Modal = renderComponent(html`

- modal-type="modal" + type="modal" Toggle Drawer
@@ -353,7 +352,7 @@ export const Modal = renderComponent(html` export const Alert = renderComponent(html`
- +

Alert

@@ -365,7 +364,7 @@ export const Alert = renderComponent(html`

- modal-type="alert" + type="alert"
@@ -385,7 +384,7 @@ export const Alert = renderComponent(html`

- modal-type="alert" + type="alert" Toggle Drawer
@@ -395,7 +394,7 @@ export const Alert = renderComponent(html` export const NonModal = renderComponent(html`
- +
@@ -418,7 +417,7 @@ export const NonModal = renderComponent(html`

- modal-type="non-modal" + type="non-modal"
@@ -434,7 +433,7 @@ export const NonModal = renderComponent(html`

- modal-type="non-modal" + type="non-modal" Toggle Drawer` export const Inline = renderComponent(html`
- +
@@ -544,7 +543,7 @@ export const Inline = renderComponent(html` >Toggle Drawer
- +
diff --git a/packages/web-components/src/drawer/drawer.styles.ts b/packages/web-components/src/drawer/drawer.styles.ts index 53c41e149831f0..e9ac759a5acf72 100644 --- a/packages/web-components/src/drawer/drawer.styles.ts +++ b/packages/web-components/src/drawer/drawer.styles.ts @@ -7,11 +7,10 @@ import { colorStrokeFocus1, colorStrokeFocus2, colorTransparentStroke, - curveAccelerateMin, + curveAccelerateMid, curveDecelerateMid, - curveEasyEase, - durationNormal, - durationSlow, + curveLinear, + durationGentle, fontFamilyBase, fontSizeBase300, fontWeightRegular, @@ -28,97 +27,95 @@ export const styles = css` ${display('block')} :host { - position: fixed; - height: 100%; - max-height: 100vh; - width: auto; - outline: none; - top: 0; - left: 0; + --dialog-backdrop: ${colorBackgroundOverlay}; + width: fit-content; + } + + dialog { + display: none; z-index: var(--drawer-elevation, 1000); font-size: ${fontSizeBase300}; line-height: ${lineHeightBase300}; font-family: ${fontFamilyBase}; font-weight: ${fontWeightRegular}; color: ${colorNeutralForeground1}; + max-width: var(--drawer-width, 592px); + max-height: 100vh; + height: 100%; } - :host([open][type='inline']), - :host([open][modal-type='non-modal']) { - width: 100%; + dialog[open] { + position: fixed; + outline: none; + top: 0; + margin-inline-start: 0; + bottom: 0; + width: var(--drawer-width, 592px); + border-radius: 0; + padding: 0; + max-width: var(--drawer-width, 592px); + overflow: hidden; + box-shadow: ${shadow64}; + box-sizing: border-box; + display: flex; + flex-direction: column; + border: ${strokeWidthThin} solid ${colorTransparentStroke}; + background: ${colorNeutralBackground1}; } - - :host([position='end']) { - inset-inline-start: unset; - inset-inline-end: 0; + :host([position='end']) dialog[open] { + margin-inline-end: 0; } - :host([type='inline']) dialog, - :host([type='inline']) { + :host([inline]) { + width: fit-content; position: relative; - width: 0; } - :host([type='inline']) dialog[open], - :host dialog[open] { - width: var(--drawer-width, 592px); + :host([inline]) dialog { + box-shadow: none; } - :host([size='small']) dialog[open] { - width: 320px; + :host([inline]) dialog[open] { + position: relative; } - :host([size='large']) dialog[open] { - width: 940px; + :host([type='non-modal']) dialog::backdrop { + display: none; } - :host([size='full']) dialog[open] { - width: 100%; + :host([type='non-modal']:not([inline])) dialog[open] { + position: fixed; } - :host([position='end']) dialog { - margin-inline-start: auto; - margin-inline-end: 0; + :host([size='small']) dialog { + width: 320px; + max-width: 320px; } - :host([position='end']) dialog { - inset-inline-end: 0px; + :host([size='large']) dialog { + width: 940px; + max-width: 940px; } - dialog { - background-color: transparent; - top: 0; - bottom: 0; - border-radius: 0; - height: 100%; - margin-inline-start: 0; - margin-inline-end: auto; - padding: 0; + :host([size='full']) dialog { + width: 100%; max-width: 100%; - max-height: 100vh; - overflow: hidden; - width: var(--drawer-width, 592px); - box-shadow: ${shadow64}; - box-sizing: border-box; - display: flex; - flex-direction: column; - height: 100%; - border: ${strokeWidthThin} solid ${colorTransparentStroke}; - background: ${colorNeutralBackground1}; - position: absolute; } - :host([position='end']) dialog { + :host([position='start']) dialog { + margin-inline-start: 0; + margin-inline-end: auto; + inset-inline-start: 0px; border-inline-end-color: ${colorTransparentStroke}; border-inline-start-color: var(--drawer-separator, ${colorTransparentStroke}); } - :host([type='inline']) dialog { - box-shadow: none; - } - - :host([modal-type='non-modal']) dialog::backdrop { - display: none; + :host([position='end']) dialog { + margin-inline-start: auto; + margin-inline-end: 0; + inset-inline-end: 0px; + border-inline-end-color: ${colorTransparentStroke}; + border-inline-start-color: var(--drawer-separator, ${colorTransparentStroke}); } dialog:focus-visible:after { @@ -140,64 +137,67 @@ export const styles = css` inset: 0; } - :host([data-animating]) dialog::backdrop { - animation: drawer-fade; - animation-timing-function: ${curveEasyEase}; - animation-duration: ${durationSlow}; - } - - :host([data-animating][data-closing]) dialog::backdrop { - animation-direction: reverse; - } - - :host([data-animating]) dialog { - animation: drawer-slide-in-start; - animation-timing-function: ${curveDecelerateMid}; - animation-duration: ${durationNormal}; - } - - :host([data-animating][data-closing]) dialog { - animation-direction: reverse; - animation-timing-function: ${curveAccelerateMin}; - animation-duration: ${durationNormal}; - } - - :host([position='end'][data-animating]) dialog { - animation: drawer-slide-in-end; - animation-timing-function: ${curveDecelerateMid}; - animation-duration: ${durationNormal}; - } - - :host([position='end'][data-closing]) dialog { - animation-direction: reverse; - animation-timing-function: ${curveAccelerateMin}; - animation-duration: ${durationNormal}; - } - - @keyframes drawer-fade { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } - } - - @keyframes drawer-slide-in-start { - 0% { - transform: translate3D(-100%, 0, 0); + @layer animations { + /* Disable animations for reduced motion */ + @media (prefers-reduced-motion: no-preference) { + dialog { + transition: display allow-discrete, opacity, overlay allow-discrete, transform; + transition-duration: ${durationGentle}; + transition-timing-function: ${curveDecelerateMid}; + /* Move offscreen left when closed */ + transform: translateX(-100%); + } + + /* Exit styles for dialog */ + dialog:not([open]) { + transform: translateX(-100%); + transition-timing-function: ${curveAccelerateMid}; + } + :host([position='end']) dialog { + transform: translateX(100%); + } + + [open], + :host([position='end']) dialog[open] { + transform: translateX(0); + } + + /* Exit styles for dialog */ + :host([position='end']) dialog:not([open]) { + transform: translateX(100%); + } + + ::backdrop { + transition: display allow-discrete, opacity, overlay allow-discrete, scale; + transition-duration: ${durationGentle}; + transition-timing-function: ${curveDecelerateMid}; + background: var(--dialog-backdrop, ${colorBackgroundOverlay}); + /* Set opacity to 0 when closed */ + opacity: 0; + } + + /* Set opacity to 1 when open */ + [open]::backdrop { + opacity: 1; + } + + ::backdrop { + transition-timing-function: ${curveLinear}; + } } - 100% { - transform: translate3D(0%, 0, 0); - } - } - @keyframes drawer-slide-in-end { - 0% { - transform: translate3D(100%, 0, 0); - } - 100% { - transform: translate3D(0%, 0, 0); + @starting-style { + dialog, + [open] { + transform: translateX(-100%); + } + :host([position='end']) dialog[open], + :host([position='end']) dialog { + transform: translateX(100%); + } + [open]::backdrop { + opacity: 0; + } } } `; diff --git a/packages/web-components/src/drawer/drawer.template.ts b/packages/web-components/src/drawer/drawer.template.ts index 6a88aa01d30fa4..9dc21372ae9afd 100644 --- a/packages/web-components/src/drawer/drawer.template.ts +++ b/packages/web-components/src/drawer/drawer.template.ts @@ -11,15 +11,15 @@ export function drawerTemplate(): ElementViewTemplate { , such as automatically opening the dialog when the 'open' attribute is present at the time of connection to the DOM. - */ - Updates.enqueue(() => { - this.syncDialogOpenState(); - }); - } - - /** - * This method is called when the custom element is disconnected from the document's DOM. - * @public - */ - public disconnectedCallback(): void { - super.disconnectedCallback(); - } - /** * The dialog element. * @public @@ -72,8 +49,8 @@ export class Drawer extends FASTElement { * When in modal or alert mode, an overlay is applied over the rest of the view. * @public */ - @attr({ attribute: 'modal-type' }) - public modalType: DrawerModalType = DrawerModalType.modal; + @attr({ attribute: 'type' }) + public type: DrawerModalType = DrawerModalType.modal; /** * The ID of the element that labels the drawer. @@ -90,12 +67,12 @@ export class Drawer extends FASTElement { public ariaDescribedby?: string; /** - * Sets the type of the drawer (overlay/inline). + * Sets the drawer as inline * @public - * @defaultValue overlay + * @defaultValue false */ - @attr - public type?: DrawerType; + @attr({ mode: 'boolean' }) + public inline: boolean = false; /** * Sets the position of the drawer (start/end). @@ -114,61 +91,28 @@ export class Drawer extends FASTElement { public size?: DrawerSize; /** - * Sets the open state of the drawer - * @public - * @defaultValue false - */ - @attr({ mode: 'boolean' }) - public open: boolean = false; - - /** - * Indicates whether the drawer is currently closing. - * @private - */ - private closing: boolean = false; - - /** - * Ensures the dialog's visibility aligns with the `open` property. - * @private - */ - private syncDialogOpenState(): void { - if (this.open) { - this.show(); - } else { - this.hide(); - } - } - - /** - * Emits a 'cancel' event. - * @internal + * Method to emit an event when the dialog is dismissed */ public dismiss(): void { this.$emit('dismiss'); } /** - * Handles changes to the `open` property. + * Method to emit an event when the dialog's open state changes * @public */ - public openChanged(oldValue: boolean, newValue: boolean): void { - if (this.$fastController.isConnected) { - if (newValue) { - this.show(); - } else { - this.hide(); - } - } - } + public emitOpenChange = (): void => { + this.$emit('open', { open: this.dialog.open }); + }; /** * @public - * Method called when the 'modalType' attribute changes + * Method called when the 'type' attribute changes */ - public typeChanged(oldValue: DrawerType, newValue: DrawerType): void { - if (newValue !== oldValue) { - if (newValue == DrawerType.inline) { - this.modalType = DrawerModalType.nonModal; + public inlineChanged(oldValue: boolean, newValue: boolean): void { + if (this.$fastController.isConnected) { + if (newValue) { + this.type = DrawerModalType.nonModal; } } } @@ -179,14 +123,11 @@ export class Drawer extends FASTElement { */ public show(): void { if (!this.dialog.open) { - if (this.type === DrawerType.inline || this.modalType === DrawerModalType.nonModal) { + if (this.inline || this.type === DrawerModalType.nonModal) { this.dialog.show(); } else { this.dialog.showModal(); } - this.open = true; - this.closing = false; - this.triggerAnimation(); } } @@ -196,10 +137,7 @@ export class Drawer extends FASTElement { */ public hide(): void { if (this.dialog.open) { - this.closing = true; - Updates.enqueue(() => { - this.triggerAnimation(); - }); + this.dialog.close(); } } @@ -212,7 +150,7 @@ export class Drawer extends FASTElement { */ public clickHandler(event: Event): boolean { event.preventDefault(); - if (this.open && this.modalType !== DrawerModalType.alert && event.target === this.dialog) { + if (this.dialog.open && this.type !== DrawerModalType.alert && event.target === this.dialog) { this.hide(); this.dismiss(); } @@ -233,7 +171,7 @@ export class Drawer extends FASTElement { case keyEscape: event.preventDefault(); - if (this.modalType !== DrawerModalType.alert) { + if (this.type !== DrawerModalType.alert) { this.hide(); this.dismiss(); } @@ -242,38 +180,4 @@ export class Drawer extends FASTElement { return true; } }; - - /** - * A function that calls the animation end handler. - * @private - */ - private readonly animationEndHandlerFunction = (): void => this.animationEndHandler(); - - /** - * Triggers the opening or closing animation on the drawer. - * @private - */ - private triggerAnimation(): void { - this.setAttribute('data-animating', ''); - if (this.closing) { - this.setAttribute('data-closing', ''); - } - this.dialog.addEventListener(eventAnimationEnd, this.animationEndHandlerFunction); - } - - /** - * Handles the end of the animation. - * @private - * - */ - private animationEndHandler(): void { - this.dialog.removeEventListener(eventAnimationEnd, this.animationEndHandlerFunction); - this.removeAttribute('data-animating'); - if (this.closing) { - this.removeAttribute('data-closing'); - this.dialog.close(); - this.open = false; - this.closing = false; - } - } } From 5cda5a3e9042160aabeff94b751054bd59fb9710 Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Fri, 31 May 2024 13:19:22 -0700 Subject: [PATCH 05/21] adds drawer body --- .../web-components/src/drawer-body/README.md | 1 + .../web-components/src/drawer-body/define.ts | 4 + .../src/drawer-body/drawer-body.definition.ts | 17 + .../src/drawer-body/drawer-body.styles.ts | 37 ++ .../src/drawer-body/drawer-body.template.ts | 25 + .../src/drawer-body/drawer-body.ts | 3 + .../web-components/src/drawer-body/index.ts | 4 + .../src/drawer/drawer.options.ts | 14 - .../src/drawer/drawer.stories.ts | 601 +++++++++--------- .../src/drawer/drawer.styles.ts | 51 +- packages/web-components/src/drawer/drawer.ts | 10 +- packages/web-components/src/index-rollup.ts | 2 +- packages/web-components/src/index.ts | 2 +- 13 files changed, 417 insertions(+), 354 deletions(-) create mode 100644 packages/web-components/src/drawer-body/README.md create mode 100644 packages/web-components/src/drawer-body/define.ts create mode 100644 packages/web-components/src/drawer-body/drawer-body.definition.ts create mode 100644 packages/web-components/src/drawer-body/drawer-body.styles.ts create mode 100644 packages/web-components/src/drawer-body/drawer-body.template.ts create mode 100644 packages/web-components/src/drawer-body/drawer-body.ts create mode 100644 packages/web-components/src/drawer-body/index.ts diff --git a/packages/web-components/src/drawer-body/README.md b/packages/web-components/src/drawer-body/README.md new file mode 100644 index 00000000000000..47e45201c1c20c --- /dev/null +++ b/packages/web-components/src/drawer-body/README.md @@ -0,0 +1 @@ +# DrawerBody diff --git a/packages/web-components/src/drawer-body/define.ts b/packages/web-components/src/drawer-body/define.ts new file mode 100644 index 00000000000000..5972eb0e60cf15 --- /dev/null +++ b/packages/web-components/src/drawer-body/define.ts @@ -0,0 +1,4 @@ +import { FluentDesignSystem } from '../fluent-design-system.js'; +import { definition } from './drawer-body.definition.js'; + +definition.define(FluentDesignSystem.registry); diff --git a/packages/web-components/src/drawer-body/drawer-body.definition.ts b/packages/web-components/src/drawer-body/drawer-body.definition.ts new file mode 100644 index 00000000000000..562c0647ad36cb --- /dev/null +++ b/packages/web-components/src/drawer-body/drawer-body.definition.ts @@ -0,0 +1,17 @@ +import { FluentDesignSystem } from '../fluent-design-system.js'; +import { DrawerBody } from './drawer-body.js'; +import { styles } from './drawer-body.styles.js'; +import { template } from './drawer-body.template.js'; + +/** + * + * @public + * @remarks + * HTML Element: + */ + +export const definition = DrawerBody.compose({ + name: `${FluentDesignSystem.prefix}-drawer-body`, + template, + styles, +}); diff --git a/packages/web-components/src/drawer-body/drawer-body.styles.ts b/packages/web-components/src/drawer-body/drawer-body.styles.ts new file mode 100644 index 00000000000000..2e3eb64434c6a9 --- /dev/null +++ b/packages/web-components/src/drawer-body/drawer-body.styles.ts @@ -0,0 +1,37 @@ +import { css } from '@microsoft/fast-element'; +import { display } from '../utils/index.js'; +import { spacingHorizontalS, spacingVerticalL } from '../theme/design-tokens.js'; + +/** Drawer styles + * @public + */ +export const styles = css` + ${display('flex')} + :host { + height: 100%; + padding: var(--spacingHorizontalXL); + max-height: calc(100vh - (2 * var(--spacingVerticalXXL))); + } + .drawer-body { + display: grid; + grid-template-rows: min-content auto min-content; + gap: ${spacingVerticalL}; + overflow-y: auto; + max-height: calc(100vh - 2 * ${spacingVerticalL}); + } + .header { + display: flex; + justify-content: space-between; + align-items: center; + } + .title { + font-weight: var(--fontWeightSemibold); + font-size: var(--fontSizeBase500); + line-height: var(--lineHeightBase500); + } + .footer { + display: flex; + justify-content: flex-start; + gap: ${spacingHorizontalS}; + } +`; diff --git a/packages/web-components/src/drawer-body/drawer-body.template.ts b/packages/web-components/src/drawer-body/drawer-body.template.ts new file mode 100644 index 00000000000000..e23dca3c2466e1 --- /dev/null +++ b/packages/web-components/src/drawer-body/drawer-body.template.ts @@ -0,0 +1,25 @@ +import { ElementViewTemplate, html, ref } from '@microsoft/fast-element'; +import type { DrawerBody } from './drawer-body.js'; + +/** + * The template for the Drawer component. + * @public + */ +export function drawerBodyTemplate(): ElementViewTemplate { + return html` +
+
+ + +
+
+ +
+ +
+ `; +} + +export const template: ElementViewTemplate = drawerBodyTemplate(); diff --git a/packages/web-components/src/drawer-body/drawer-body.ts b/packages/web-components/src/drawer-body/drawer-body.ts new file mode 100644 index 00000000000000..2bd404d9ac0886 --- /dev/null +++ b/packages/web-components/src/drawer-body/drawer-body.ts @@ -0,0 +1,3 @@ +import { FASTElement } from '@microsoft/fast-element'; + +export class DrawerBody extends FASTElement {} diff --git a/packages/web-components/src/drawer-body/index.ts b/packages/web-components/src/drawer-body/index.ts new file mode 100644 index 00000000000000..bb88e71edf28c9 --- /dev/null +++ b/packages/web-components/src/drawer-body/index.ts @@ -0,0 +1,4 @@ +export * from './drawer-body.js'; +export { definition as DrawerBodyDefinition } from './drawer-body.definition.js'; +export { styles as DrawerBodyStyles } from './drawer-body.styles.js'; +export { template as DrawerBodyTemplate } from './drawer-body.template.js'; diff --git a/packages/web-components/src/drawer/drawer.options.ts b/packages/web-components/src/drawer/drawer.options.ts index 510bafee64edb4..74effac14947b5 100644 --- a/packages/web-components/src/drawer/drawer.options.ts +++ b/packages/web-components/src/drawer/drawer.options.ts @@ -44,17 +44,3 @@ export const DrawerModalType = { * @public */ export type DrawerModalType = ValuesOf; - -/** - * A drawer can be different sizes - */ -export const DrawerType = { - overlay: 'overlay', - inline: 'inline', -} as const; - -/** - * The size of the drawer. - * @public - */ -export type DrawerType = ValuesOf; diff --git a/packages/web-components/src/drawer/drawer.stories.ts b/packages/web-components/src/drawer/drawer.stories.ts index aa31c8a2aa3a85..aea32fffadee12 100644 --- a/packages/web-components/src/drawer/drawer.stories.ts +++ b/packages/web-components/src/drawer/drawer.stories.ts @@ -3,8 +3,9 @@ import type { Args, Meta } from '@storybook/html'; import { renderComponent } from '../helpers.stories.js'; import { RadioGroup } from '../radio-group/radio-group.js'; import type { Drawer as FluentDrawer } from './drawer.js'; -import { DrawerModalType, DrawerPosition, DrawerSize, DrawerType } from './drawer.options.js'; +import { DrawerModalType, DrawerPosition, DrawerSize } from './drawer.options.js'; import './define.js'; +import '../drawer-body/define.js'; type DrawerStoryArgs = Args & FluentDrawer; type DrawerStoryMeta = Meta; @@ -61,8 +62,11 @@ const toggleSelectedDrawer = (radioGroupId: string) => { }; const hideDrawer = (drawerID: string) => { + console.log('hide'); const drawer = document.getElementById(drawerID) as FluentDrawer; if (drawer.dialog.open) { + console.log('hidden'); + drawer.hide(); } }; @@ -128,14 +132,6 @@ const storyTemplate = html` .width-400 { width: 400px; } - .container { - padding: 22px; - display: flex; - justify-content: space-between; - flex-direction: column; - box-sizing: border-box; - height: 100%; - }
@@ -146,10 +142,11 @@ const storyTemplate = html` type="${x => x.type}" ?inline="${x => x.inline}" > -
-
-

Drawer Header

- + +

Drawer Header

+
The drawer gives users a quick entry point to configuration and information. It should be used when retaining context is beneficial to users. An overlay is optional depending on whether or not interacting @@ -165,13 +162,13 @@ const storyTemplate = html`
-
+
Close Do Something
-
+
@@ -204,9 +201,11 @@ const storyTemplate = html` type="${x => x.type}" ?inline="${x => x.inline}" > -
-
-

Drawer Header

+ +

Drawer Header

+
The drawer gives users a quick entry point to configuration and information. It should be used when retaining context is beneficial to users. An overlay is optional depending on whether or not interacting @@ -222,13 +221,13 @@ const storyTemplate = html`
-
+
Close Do Something
-
+
@@ -303,21 +302,21 @@ export const Drawer = renderComponent(storyTemplate).bind({}); export const Modal = renderComponent(html`
- -
-
-
-

Modal

- - ${dismissed20Regular} - -
+ + +

Drawer Header

+ + ${dismissed20Regular} + +

When a modal dialog is open, the rest of the page is dimmed out and cannot be interacted with. The tab @@ -329,19 +328,26 @@ export const Modal = renderComponent(html` type="modal"

-
+
+ Close + Do Something +
+

Modal

- +

When a modal dialog is open, the rest of the page is dimmed out and cannot be interacted with. The tab sequence is kept within the dialog and moving the focus outside the dialog will imply closing it. This is the default type of the component.

- +
+ type="modal" Toggle Drawer @@ -353,25 +359,24 @@ export const Alert = renderComponent(html`
-
-
-

Alert

- -

- When an alert dialog is open it interrupts the user's workflow to communicate an important message or - ask for a decision. Unlike a typical modal dialog, the user must take an action through the options - given to dismiss the dialog, and it cannot be dismissed through the dimmed background. -

-
- - type="alert" - -
+ +

Alert

+ +

+ When an alert dialog is open it interrupts the user's workflow to communicate an important message or ask + for a decision. Unlike a typical modal dialog, the user must take an action through the options given to + dismiss the dialog, and it cannot be dismissed through the dimmed background. +

+
+
+ + type="alert" +
Do Something hideDrawer('drawer-alert')}>Close
-
+
@@ -383,6 +388,7 @@ export const Alert = renderComponent(html` the dialog, and it cannot be dismissed through the dimmed background.

+
type="alert" @@ -395,32 +401,29 @@ export const NonModal = renderComponent(html`
-
-
-
-

Non Modal

- - ${dismissed20Regular} - -
- -

- When a non-modal dialog is open, the rest of the page is not dimmed out and users can interact with the - rest of the page. This also implies that the tab focus can move outside the dialog when it reaches the - last focusable element. -

-
- - type="non-modal" - -
-
+ +

Non Modal

+ + ${dismissed20Regular} + + +

+ When a non-modal dialog is open, the rest of the page is not dimmed out and users can interact with the + rest of the page. This also implies that the tab focus can move outside the dialog when it reaches the + last focusable element. +

+
+
+ + type="non-modal" + +
@@ -445,34 +448,31 @@ export const NonModal = renderComponent(html` export const Overlay = renderComponent(html`
- -
-
-
-

Overlay

- - ${dismissed20Regular} - -
- -

- A Drawer rendered with an overlay contains supplementary content and are used for complex creation, - edit, or management experiences. For example, viewing details about an item in a list or editing - settings. By default, drawer is blocking and signifies that the users full attention is required when - making configurations. -

-
- - type="overlay" - -
-
+ + +

Overlay

+ + ${dismissed20Regular} + + +

+ A Drawer rendered with an overlay contains supplementary content and are used for complex creation, edit, + or management experiences. For example, viewing details about an item in a list or editing settings. By + default, drawer is blocking and signifies that the users full attention is required when making + configurations. +

+
+
+ + default + +
@@ -484,6 +484,7 @@ export const Overlay = renderComponent(html` drawer is blocking and signifies that the users full attention is required when making configurations.

+
type="overlay" @@ -495,32 +496,33 @@ export const Overlay = renderComponent(html` export const Inline = renderComponent(html`
-
-
-
-

Drawer Inline

- - ${dismissed20Regular} - -
- -

- An inline Drawer is often used for navigation that is not dismissible. As it is on the same level as the - main surface, users can still interact with other UI elements. This could be useful for swapping between - different items in the main surface. -

-
- - type="inline" - -
-
+ +

Drawer Inline

+ + ${dismissed20Regular} + + +

+ An inline Drawer is often used for navigation that is not dismissible. As it is on the same level as the + main surface, users can still interact with other UI elements. This could be useful for swapping between + different items in the main surface. +

+
+
+ + + inline +
+ + type="non-modal" +
+

Inline

@@ -531,8 +533,13 @@ export const Inline = renderComponent(html` items in the main surface.

+
+ - type="inline" + inline +
+ + type="non-modal"
Select a Drawer Position @@ -544,32 +551,33 @@ export const Inline = renderComponent(html` >
-
-
-
-

Drawer Inline

- - ${dismissed20Regular} - -
- -

- An inline Drawer is often used for navigation that is not dismissible. As it is on the same level as the - main surface, users can still interact with other UI elements. This could be useful for swapping between - different items in the main surface. -

-
- - type="inline" - -
-
+ +

Drawer Inline

+ + ${dismissed20Regular} + + +

+ An inline Drawer is often used for navigation that is not dismissible. As it is on the same level as the + main surface, users can still interact with other UI elements. This could be useful for swapping between + different items in the main surface. +

+
+
+ + + inline +
+ + type="non-modal" +
+
`); @@ -577,36 +585,36 @@ export const Inline = renderComponent(html` export const Position = renderComponent(html`
- -
-
-
-

Drawer Position Start

- - ${dismissed20Regular} - -
- -

- When a Drawer is invoked, it slides in from either the left or right side of the screen. This can be - specified by the position attribute. -

-
- - default - -
+ + +

Drawer Position Start

+ + ${dismissed20Regular} + + +

+ When a Drawer is invoked, it slides in from either the left or right side of the screen. This can be + specified by the position attribute. +

+
+
+ + + default +
Primary Secondary
-
+
@@ -618,8 +626,9 @@ export const Position = renderComponent(html` by the position attribute.

+
- position="" + position="start" Select a Drawer Position @@ -631,38 +640,37 @@ export const Position = renderComponent(html` >
- -
-
-
-

Drawer Position End

- - ${dismissed20Regular} - -
- -

- The drawer component offers flexible positioning options to suit your layout needs. By using the - position attribute, you can easily place the drawer on either side of the screen. The attribute accepts - values of type DrawerPosition, which includes two options: 'start' and 'end'. The default position of - the Drawer is 'end'. -

-
- - position="end" - -
+ + +

Drawer Position End

+ + ${dismissed20Regular} + + +

+ The drawer component offers flexible positioning options to suit your layout needs. By using the position + attribute, you can easily place the drawer on either side of the screen. The attribute accepts values of + type DrawerPosition, which includes two options: 'start' and 'end'. The default position of the Drawer is + 'end'. +

+
+
+ + position="end" +
Primary Secondary
-
+
@@ -677,7 +685,7 @@ export const Size = renderComponent(html`

The size attribute controls the width of the drawer. The default is medium.

- size="" + size="small" Select a Drawer Size @@ -690,61 +698,50 @@ export const Size = renderComponent(html` >Toggle Drawer
- -
-
-
-

Drawer Small

- - ${dismissed20Regular} - -
- -

The size attribute controls the width of the drawer. The default is medium.

-
- - size="small" - -
-
+ + +

Drawer Small

+ + ${dismissed20Regular} + + +

The size attribute controls the width of the drawer. The default is medium.

+
+
+ + size="small" + +
- -
-
-
-

Drawer Medium

- - ${dismissed20Regular} - -
- -

The size attribute controls the width of the drawer. The default is medium.

-
- - default - -
-
+ + +

Drawer Medium

+ + ${dismissed20Regular} + + +

The size attribute controls the width of the drawer. The default is medium.

+
+
+ + default + +
- -
-
- -
+ +

Drawer Large

@@ -757,24 +754,24 @@ export const Size = renderComponent(html` > ${dismissed20Regular} -

The size attribute controls the width of the drawer. The default is medium.

+
+ size="large" -
-
+ +
- -
-
-
+ + +

Drawer Full

` > ${dismissed20Regular} -

The size attribute controls the width of the drawer. The default is medium.

+
size="full" -
-
+
`); @@ -812,31 +808,26 @@ export const CustomSize = renderComponent(html` >Toggle Drawer
- -
-
-
-

Custom Size

- - ${dismissed20Regular} - -
- -

The Drawer can be sized to any custom width, by overriding the drawer-width CSS variable:

-
- - var(--drawer-width) - -
-
+ + +

Custom Size

+ + ${dismissed20Regular} + + +

The Drawer can be sized to any custom width, by overriding the drawer-width CSS variable:

+
+
+ + var(--drawer-width) + +
`); diff --git a/packages/web-components/src/drawer/drawer.styles.ts b/packages/web-components/src/drawer/drawer.styles.ts index e9ac759a5acf72..25d7487472e1b6 100644 --- a/packages/web-components/src/drawer/drawer.styles.ts +++ b/packages/web-components/src/drawer/drawer.styles.ts @@ -28,10 +28,11 @@ export const styles = css` :host { --dialog-backdrop: ${colorBackgroundOverlay}; - width: fit-content; + overflow: hidden; } dialog { + position: fixed; display: none; z-index: var(--drawer-elevation, 1000); font-size: ${fontSizeBase300}; @@ -41,11 +42,12 @@ export const styles = css` color: ${colorNeutralForeground1}; max-width: var(--drawer-width, 592px); max-height: 100vh; + overflow: hidden; height: 100%; + transition: none; } dialog[open] { - position: fixed; outline: none; top: 0; margin-inline-start: 0; @@ -62,9 +64,6 @@ export const styles = css` border: ${strokeWidthThin} solid ${colorTransparentStroke}; background: ${colorNeutralBackground1}; } - :host([position='end']) dialog[open] { - margin-inline-end: 0; - } :host([inline]) { width: fit-content; @@ -79,14 +78,6 @@ export const styles = css` position: relative; } - :host([type='non-modal']) dialog::backdrop { - display: none; - } - - :host([type='non-modal']:not([inline])) dialog[open] { - position: fixed; - } - :host([size='small']) dialog { width: 320px; max-width: 320px; @@ -108,6 +99,7 @@ export const styles = css` inset-inline-start: 0px; border-inline-end-color: ${colorTransparentStroke}; border-inline-start-color: var(--drawer-separator, ${colorTransparentStroke}); + transform: translateX(-100%); } :host([position='end']) dialog { @@ -116,6 +108,7 @@ export const styles = css` inset-inline-end: 0px; border-inline-end-color: ${colorTransparentStroke}; border-inline-start-color: var(--drawer-separator, ${colorTransparentStroke}); + transform: translateX(100%); } dialog:focus-visible:after { @@ -141,36 +134,39 @@ export const styles = css` /* Disable animations for reduced motion */ @media (prefers-reduced-motion: no-preference) { dialog { - transition: display allow-discrete, opacity, overlay allow-discrete, transform; - transition-duration: ${durationGentle}; - transition-timing-function: ${curveDecelerateMid}; - /* Move offscreen left when closed */ - transform: translateX(-100%); + transition: transform ${durationGentle} ${curveDecelerateMid}; } - /* Exit styles for dialog */ - dialog:not([open]) { + :host([position='start']) dialog { + /* Move offscreen left when closed */ transform: translateX(-100%); - transition-timing-function: ${curveAccelerateMid}; } + :host([position='end']) dialog { + /* Move offscreen right when closed */ transform: translateX(100%); } - [open], - :host([position='end']) dialog[open] { + /* Move onscreen when open */ + dialog[open] { transform: translateX(0); } /* Exit styles for dialog */ + dialog:not([open]) { + transition-timing-function: ${curveAccelerateMid}; + } + + :host([position='start']) dialog:not([open]) { + transform: translateX(-100%); + } + :host([position='end']) dialog:not([open]) { transform: translateX(100%); } ::backdrop { - transition: display allow-discrete, opacity, overlay allow-discrete, scale; - transition-duration: ${durationGentle}; - transition-timing-function: ${curveDecelerateMid}; + transition: opacity ${durationGentle} ${curveDecelerateMid}; background: var(--dialog-backdrop, ${colorBackgroundOverlay}); /* Set opacity to 0 when closed */ opacity: 0; @@ -188,10 +184,9 @@ export const styles = css` @starting-style { dialog, - [open] { + :host([position='start']) dialog { transform: translateX(-100%); } - :host([position='end']) dialog[open], :host([position='end']) dialog { transform: translateX(100%); } diff --git a/packages/web-components/src/drawer/drawer.ts b/packages/web-components/src/drawer/drawer.ts index d0c07bf1806aaa..b2822fed6f5d36 100644 --- a/packages/web-components/src/drawer/drawer.ts +++ b/packages/web-components/src/drawer/drawer.ts @@ -1,6 +1,6 @@ -import { attr, FASTElement, observable, Updates } from '@microsoft/fast-element'; -import { eventAnimationEnd, keyEscape } from '@microsoft/fast-web-utilities'; -import { DrawerModalType, DrawerPosition, DrawerSize, DrawerType } from './drawer.options.js'; +import { attr, FASTElement, observable } from '@microsoft/fast-element'; +import { keyEscape } from '@microsoft/fast-web-utilities'; +import { DrawerModalType, DrawerPosition, DrawerSize } from './drawer.options.js'; /** * Drawer @@ -80,7 +80,7 @@ export class Drawer extends FASTElement { * @defaultValue start */ @attr - public position?: DrawerPosition; + public position: DrawerPosition = DrawerPosition.start; /** * Sets the size of the drawer (small/medium/large). @@ -123,7 +123,7 @@ export class Drawer extends FASTElement { */ public show(): void { if (!this.dialog.open) { - if (this.inline || this.type === DrawerModalType.nonModal) { + if (this.inline) { this.dialog.show(); } else { this.dialog.showModal(); diff --git a/packages/web-components/src/index-rollup.ts b/packages/web-components/src/index-rollup.ts index 6e0d8a87bd0dfa..a68fc0ad19a367 100644 --- a/packages/web-components/src/index-rollup.ts +++ b/packages/web-components/src/index-rollup.ts @@ -99,10 +99,10 @@ export { DrawerPosition, DrawerSize, DrawerModalType, - DrawerType, DrawerTemplate, DrawerStyles, } from './drawer/index.js'; +export { DrawerBody, DrawerBodyDefinition, DrawerBodyTemplate, DrawerBodyStyles } from './drawer-body/index.js'; export { Image, ImageFit, ImageShape, ImageDefinition, ImageTemplate, ImageStyles } from './image/index.js'; export { Label, LabelSize, LabelWeight, LabelDefinition, LabelStyles, LabelTemplate } from './label/index.js'; export { Menu, MenuTemplate, MenuStyles, MenuDefinition } from './menu/index.js'; diff --git a/packages/web-components/src/index.ts b/packages/web-components/src/index.ts index bc766ae6c2cd5e..88c61a25cf6b6c 100644 --- a/packages/web-components/src/index.ts +++ b/packages/web-components/src/index.ts @@ -104,10 +104,10 @@ export { DrawerPosition, DrawerSize, DrawerModalType, - DrawerType, DrawerTemplate, DrawerStyles, } from './drawer/index.js'; +export { DrawerBody, DrawerBodyDefinition, DrawerBodyTemplate, DrawerBodyStyles } from './drawer-body/index.js'; export { Image, ImageFit, ImageShape, ImageDefinition, ImageTemplate, ImageStyles } from './image/index.js'; export { Label, LabelSize, LabelWeight, LabelDefinition, LabelStyles, LabelTemplate } from './label/index.js'; export { Menu, MenuTemplate, MenuStyles, MenuDefinition } from './menu/index.js'; From 927183855d6328c99648c8852eb2653bd92a62f0 Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Tue, 4 Jun 2024 10:12:37 -0700 Subject: [PATCH 06/21] updates drawer animation styles --- .../src/drawer-body/drawer-body.styles.ts | 4 +- .../src/drawer/drawer.stories.ts | 80 ++++--------------- .../src/drawer/drawer.styles.ts | 62 +++++++------- packages/web-components/src/drawer/drawer.ts | 1 + 4 files changed, 47 insertions(+), 100 deletions(-) diff --git a/packages/web-components/src/drawer-body/drawer-body.styles.ts b/packages/web-components/src/drawer-body/drawer-body.styles.ts index 2e3eb64434c6a9..3f4c398c68dac9 100644 --- a/packages/web-components/src/drawer-body/drawer-body.styles.ts +++ b/packages/web-components/src/drawer-body/drawer-body.styles.ts @@ -1,6 +1,6 @@ import { css } from '@microsoft/fast-element'; import { display } from '../utils/index.js'; -import { spacingHorizontalS, spacingVerticalL } from '../theme/design-tokens.js'; +import { spacingHorizontalM, spacingVerticalL } from '../theme/design-tokens.js'; /** Drawer styles * @public @@ -32,6 +32,6 @@ export const styles = css` .footer { display: flex; justify-content: flex-start; - gap: ${spacingHorizontalS}; + gap: ${spacingHorizontalM}; } `; diff --git a/packages/web-components/src/drawer/drawer.stories.ts b/packages/web-components/src/drawer/drawer.stories.ts index aea32fffadee12..27a8ef67a97d5d 100644 --- a/packages/web-components/src/drawer/drawer.stories.ts +++ b/packages/web-components/src/drawer/drawer.stories.ts @@ -120,8 +120,8 @@ const storyTemplate = html` .justify--space-between { justify-content: space-between; } - .row-gap--16 { - row-gap: 16px; + .gap--16 { + gap: 16px; } .col-gap--8 { column-gap: 8px; @@ -162,7 +162,7 @@ const storyTemplate = html`
-
+
Close @@ -171,7 +171,7 @@ const storyTemplate = html`
-
+

Drawer

@@ -221,7 +221,7 @@ const storyTemplate = html`
-
+
Close @@ -328,7 +328,7 @@ export const Modal = renderComponent(html` type="modal"
-
+
Close @@ -337,7 +337,7 @@ export const Modal = renderComponent(html`
-
+

Modal

@@ -372,14 +372,14 @@ export const Alert = renderComponent(html` type="alert" -

+
Do Something hideDrawer('drawer-alert')}>Close
-
+

Alert

@@ -426,7 +426,7 @@ export const NonModal = renderComponent(html`

-
+

Non Modal

@@ -445,54 +445,6 @@ export const NonModal = renderComponent(html`

`); -export const Overlay = renderComponent(html` -
-
- - -

Overlay

- - ${dismissed20Regular} - - -

- A Drawer rendered with an overlay contains supplementary content and are used for complex creation, edit, - or management experiences. For example, viewing details about an item in a list or editing settings. By - default, drawer is blocking and signifies that the users full attention is required when making - configurations. -

-
-
- - default - -
-
-
-
-

Overlay

- -

- A Drawer rendered with an overlay contains supplementary content and are used for complex creation, edit, or - management experiences. For example, viewing details about an item in a list or editing settings. By default, - drawer is blocking and signifies that the users full attention is required when making configurations. -

-
-
- - type="overlay" - - Toggle Drawer -
-
-`); - export const Inline = renderComponent(html`
@@ -524,7 +476,7 @@ export const Inline = renderComponent(html` -
+

Inline

@@ -610,14 +562,14 @@ export const Position = renderComponent(html` default -

+
Primary Secondary
-
+

Position

@@ -666,7 +618,7 @@ export const Position = renderComponent(html` position="end" -
+
Primary Secondary
@@ -678,7 +630,7 @@ export const Position = renderComponent(html` export const Size = renderComponent(html`
-
+

Size

@@ -793,7 +745,7 @@ export const Size = renderComponent(html` export const CustomSize = renderComponent(html`
-
+

Custom Size

diff --git a/packages/web-components/src/drawer/drawer.styles.ts b/packages/web-components/src/drawer/drawer.styles.ts index 25d7487472e1b6..411f0ef24671f8 100644 --- a/packages/web-components/src/drawer/drawer.styles.ts +++ b/packages/web-components/src/drawer/drawer.styles.ts @@ -44,7 +44,6 @@ export const styles = css` max-height: 100vh; overflow: hidden; height: 100%; - transition: none; } dialog[open] { @@ -65,13 +64,19 @@ export const styles = css` background: ${colorNeutralBackground1}; } + :host([type='non-modal']) dialog[open]::backdrop { + display: none; + } + :host([inline]) { width: fit-content; position: relative; + height: 100%; } :host([inline]) dialog { box-shadow: none; + position: relative; } :host([inline]) dialog[open] { @@ -93,22 +98,21 @@ export const styles = css` max-width: 100%; } + :host([position='start']) dialog, + :host([position='end']) dialog { + inset-inline-end: 0px; + border-inline-end-color: ${colorTransparentStroke}; + border-inline-start-color: var(--drawer-separator, ${colorTransparentStroke}); + } + :host([position='start']) dialog { margin-inline-start: 0; margin-inline-end: auto; - inset-inline-start: 0px; - border-inline-end-color: ${colorTransparentStroke}; - border-inline-start-color: var(--drawer-separator, ${colorTransparentStroke}); - transform: translateX(-100%); } :host([position='end']) dialog { margin-inline-start: auto; margin-inline-end: 0; - inset-inline-end: 0px; - border-inline-end-color: ${colorTransparentStroke}; - border-inline-start-color: var(--drawer-separator, ${colorTransparentStroke}); - transform: translateX(100%); } dialog:focus-visible:after { @@ -134,39 +138,30 @@ export const styles = css` /* Disable animations for reduced motion */ @media (prefers-reduced-motion: no-preference) { dialog { - transition: transform ${durationGentle} ${curveDecelerateMid}; + transition: display allow-discrete, opacity, overlay allow-discrete, transform; + transition-duration: ${durationGentle}; + transition-timing-function: ${curveDecelerateMid}; } - :host([position='start']) dialog { - /* Move offscreen left when closed */ + /* Exit styles for dialog */ + :host dialog:not([open]) { transform: translateX(-100%); + transition-timing-function: ${curveAccelerateMid}; } - - :host([position='end']) dialog { - /* Move offscreen right when closed */ + :host([position='end']) dialog:not([open]) { transform: translateX(100%); - } - - /* Move onscreen when open */ - dialog[open] { - transform: translateX(0); - } - - /* Exit styles for dialog */ - dialog:not([open]) { transition-timing-function: ${curveAccelerateMid}; } - :host([position='start']) dialog:not([open]) { - transform: translateX(-100%); - } - - :host([position='end']) dialog:not([open]) { - transform: translateX(100%); + dialog[open], + :host([position='end']) dialog[open] { + transform: translateX(0); } ::backdrop { - transition: opacity ${durationGentle} ${curveDecelerateMid}; + transition: display allow-discrete, opacity, overlay allow-discrete, scale; + transition-duration: ${durationGentle}; + transition-timing-function: ${curveDecelerateMid}; background: var(--dialog-backdrop, ${colorBackgroundOverlay}); /* Set opacity to 0 when closed */ opacity: 0; @@ -183,11 +178,10 @@ export const styles = css` } @starting-style { - dialog, - :host([position='start']) dialog { + dialog[open] { transform: translateX(-100%); } - :host([position='end']) dialog { + :host([position='end']) dialog[open] { transform: translateX(100%); } [open]::backdrop { diff --git a/packages/web-components/src/drawer/drawer.ts b/packages/web-components/src/drawer/drawer.ts index b2822fed6f5d36..cf2400cd5cf477 100644 --- a/packages/web-components/src/drawer/drawer.ts +++ b/packages/web-components/src/drawer/drawer.ts @@ -128,6 +128,7 @@ export class Drawer extends FASTElement { } else { this.dialog.showModal(); } + this.emitOpenChange(); } } From 29a33d712cbe13742d60bc7596323b75f920beae Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Tue, 4 Jun 2024 10:14:55 -0700 Subject: [PATCH 07/21] fixes attributes --- packages/web-components/src/drawer/drawer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/web-components/src/drawer/drawer.ts b/packages/web-components/src/drawer/drawer.ts index cf2400cd5cf477..74e8c982d923bd 100644 --- a/packages/web-components/src/drawer/drawer.ts +++ b/packages/web-components/src/drawer/drawer.ts @@ -72,9 +72,9 @@ export class Drawer extends FASTElement { * @defaultValue false */ @attr({ mode: 'boolean' }) - public inline: boolean = false; + public inline?: boolean = false; - /** + /**"" * Sets the position of the drawer (start/end). * @public * @defaultValue start @@ -88,7 +88,7 @@ export class Drawer extends FASTElement { * @defaultValue medium */ @attr({ attribute: 'size' }) - public size?: DrawerSize; + public size: DrawerSize = DrawerSize.medium; /** * Method to emit an event when the dialog is dismissed From e58d3f066a67f7948d109222c7514a35253e60e7 Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Tue, 4 Jun 2024 10:17:35 -0700 Subject: [PATCH 08/21] adds drawer body to package json exports --- packages/web-components/package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/web-components/package.json b/packages/web-components/package.json index 64c3f18d794a1a..a256de7626fc85 100644 --- a/packages/web-components/package.json +++ b/packages/web-components/package.json @@ -71,6 +71,10 @@ "types": "./dist/dts/drawer/define.d.ts", "default": "./dist/esm/drawer/define.js" }, + "./drawer-body.js": { + "types": "./dist/dts/drawer-body/define.d.ts", + "default": "./dist/esm/drawer-body/define.js" + }, "./image.js": { "types": "./dist/dts/image/define.d.ts", "default": "./dist/esm/image/define.js" From 691743104916983af64994d88eb6aa554109abfe Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Tue, 4 Jun 2024 11:38:51 -0700 Subject: [PATCH 09/21] addresses PR feedback --- .../src/drawer-body/drawer-body.styles.ts | 20 +-- packages/web-components/src/drawer/README.md | 14 +- .../src/drawer/drawer.options.ts | 4 +- .../src/drawer/drawer.stories.ts | 58 ++++---- .../src/drawer/drawer.styles.ts | 1 - .../src/drawer/drawer.template.ts | 4 +- packages/web-components/src/drawer/drawer.ts | 130 +++++++++++------- packages/web-components/src/index-rollup.ts | 2 +- packages/web-components/src/index.ts | 2 +- 9 files changed, 133 insertions(+), 102 deletions(-) diff --git a/packages/web-components/src/drawer-body/drawer-body.styles.ts b/packages/web-components/src/drawer-body/drawer-body.styles.ts index 3f4c398c68dac9..03f4604fa3a4b4 100644 --- a/packages/web-components/src/drawer-body/drawer-body.styles.ts +++ b/packages/web-components/src/drawer-body/drawer-body.styles.ts @@ -1,6 +1,12 @@ import { css } from '@microsoft/fast-element'; import { display } from '../utils/index.js'; -import { spacingHorizontalM, spacingVerticalL } from '../theme/design-tokens.js'; +import { + spacingHorizontalM, + spacingHorizontalXL, + spacingVerticalL, + spacingVerticalXXL, +} from '../theme/design-tokens.js'; +import { typographySubtitle1Styles } from '../styles/partials/typography.partials.js'; /** Drawer styles * @public @@ -8,9 +14,10 @@ import { spacingHorizontalM, spacingVerticalL } from '../theme/design-tokens.js' export const styles = css` ${display('flex')} :host { + position: relative; height: 100%; - padding: var(--spacingHorizontalXL); - max-height: calc(100vh - (2 * var(--spacingVerticalXXL))); + padding: ${spacingHorizontalXL}; + max-height: calc(100vh - (2 * ${spacingVerticalXXL})); } .drawer-body { display: grid; @@ -23,12 +30,9 @@ export const styles = css` display: flex; justify-content: space-between; align-items: center; + ${typographySubtitle1Styles} } - .title { - font-weight: var(--fontWeightSemibold); - font-size: var(--fontSizeBase500); - line-height: var(--lineHeightBase500); - } + .footer { display: flex; justify-content: flex-start; diff --git a/packages/web-components/src/drawer/README.md b/packages/web-components/src/drawer/README.md index ead6929a45f2b0..0cde40138e175d 100644 --- a/packages/web-components/src/drawer/README.md +++ b/packages/web-components/src/drawer/README.md @@ -18,7 +18,7 @@ The Fluent WC3 Drawer extends `FASTElement` ; /** * A drawer can be different sizes */ -export const DrawerModalType = { +export const DrawerType = { alert: 'alert', modal: 'modal', nonModal: 'non-modal', @@ -43,4 +43,4 @@ export const DrawerModalType = { * The size of the drawer. * @public */ -export type DrawerModalType = ValuesOf; +export type DrawerType = ValuesOf; diff --git a/packages/web-components/src/drawer/drawer.stories.ts b/packages/web-components/src/drawer/drawer.stories.ts index 27a8ef67a97d5d..95b170864376c0 100644 --- a/packages/web-components/src/drawer/drawer.stories.ts +++ b/packages/web-components/src/drawer/drawer.stories.ts @@ -3,7 +3,7 @@ import type { Args, Meta } from '@storybook/html'; import { renderComponent } from '../helpers.stories.js'; import { RadioGroup } from '../radio-group/radio-group.js'; import type { Drawer as FluentDrawer } from './drawer.js'; -import { DrawerModalType, DrawerPosition, DrawerSize } from './drawer.options.js'; +import { DrawerType, DrawerPosition, DrawerSize } from './drawer.options.js'; import './define.js'; import '../drawer-body/define.js'; @@ -143,9 +143,7 @@ const storyTemplate = html` ?inline="${x => x.inline}" > -

Drawer Header

+ Drawer Header
The drawer gives users a quick entry point to configuration and information. It should be used when @@ -202,9 +200,8 @@ const storyTemplate = html` ?inline="${x => x.inline}" > -

Drawer Header

+ Drawer Header +
The drawer gives users a quick entry point to configuration and information. It should be used when @@ -237,7 +234,7 @@ const storyTemplate = html` export default { title: 'Components/Drawer', args: { - type: DrawerModalType.modal, + type: DrawerType.modal, inline: false, size: DrawerSize.medium, }, @@ -254,7 +251,7 @@ export default { }, }, type: { - options: Object.values(DrawerModalType), + options: Object.values(DrawerType), control: { type: 'select', }, @@ -263,7 +260,7 @@ export default { summary: 'Sets the modal type of the drawer', }, defaultValue: { - summary: DrawerModalType.modal, + summary: DrawerType.modal, }, }, }, @@ -304,9 +301,7 @@ export const Modal = renderComponent(html`
-

Drawer Header

+ Drawer Header `
-

Alert

+ Alert

When an alert dialog is open it interrupts the user's workflow to communicate an important message or ask @@ -402,7 +397,7 @@ export const NonModal = renderComponent(html`

-

Non Modal

+ Non modal `
-

Drawer Inline

+ Drawer Inline `
-

Drawer Inline

+ Drawer Inline ` export const Position = renderComponent(html`
- + -

Drawer Position Start

+ Drawer position start + ` >
- + -

Drawer Position End

+ Drawer position end `
-

Drawer Small

+ Drawer small `
-

Drawer Medium

+ Drawer medium `
-

Drawer Large

+ Drawer large + ` -

Drawer Full

+ Drawer full + -

Custom Size

+ Custom size (): ElementViewTemplate { { + this.validateConfiguration(); + }); + } /** + * @public * Determines whether the drawer should be displayed as modal, non-modal, or alert. * When in modal or alert mode, an overlay is applied over the rest of the view. - * @public */ @attr({ attribute: 'type' }) - public type: DrawerModalType = DrawerModalType.modal; + public type: DrawerType = DrawerType.modal; /** - * The ID of the element that labels the drawer. * @public + * The ID of the element that labels the drawer. */ @attr({ attribute: 'aria-labelledby' }) public ariaLabelledby?: string; /** - * The ID of the element that describes the drawer. * @public + * The ID of the element that describes the drawer. */ @attr({ attribute: 'aria-describedby' }) public ariaDescribedby?: string; /** - * Sets the drawer as inline * @public * @defaultValue false + * Sets the drawer as inline */ @attr({ mode: 'boolean' }) public inline?: boolean = false; /**"" - * Sets the position of the drawer (start/end). * @public * @defaultValue start + * Sets the position of the drawer (start/end). */ @attr public position: DrawerPosition = DrawerPosition.start; /** - * Sets the size of the drawer (small/medium/large). * @public * @defaultValue medium + * Sets the size of the drawer (small/medium/large). */ @attr({ attribute: 'size' }) public size: DrawerSize = DrawerSize.medium; /** - * Method to emit an event when the dialog is dismissed + * @public + * The dialog element. */ - public dismiss(): void { - this.$emit('dismiss'); - } + @observable + public dialog!: HTMLDialogElement; /** - * Method to emit an event when the dialog's open state changes * @public + * Method to emit an event after the dialog's open state changes + * HTML spec proposal: https://github.com/whatwg/html/issues/9733 */ - public emitOpenChange = (): void => { - this.$emit('open', { open: this.dialog.open }); + public emitToggle = (): void => { + this.$emit('toggle', { + oldState: this.dialog.open ? 'closed' : 'open', + newState: this.dialog.open ? 'open' : 'closed', + }); }; /** * @public - * Method called when the 'type' attribute changes + * Method to emit an event after the dialog's open state changes + * HTML spec proposal: https://github.com/whatwg/html/issues/9733 + */ + public emitBeforeToggle = (): void => { + this.$emit('beforetoggle', { + oldState: this.dialog.open ? 'open' : 'closed', + newState: this.dialog.open ? 'closed' : 'open', + }); + }; + + /** + * @public + * Method called when the 'inline' attribute changes */ public inlineChanged(oldValue: boolean, newValue: boolean): void { if (this.$fastController.isConnected) { if (newValue) { - this.type = DrawerModalType.nonModal; + this.validateConfiguration(); + } + } + } + + /** + * @public + * Method called when the 'type' attribute changes + */ + public typeChanged(oldValue: boolean, newValue: boolean): void { + if (this.$fastController.isConnected) { + if (newValue != oldValue) { + this.validateConfiguration(); } } } /** - * Opens the drawer if it is currently hidden. * @public + * Method to show the drawer */ public show(): void { - if (!this.dialog.open) { + Updates.enqueue(() => { + this.emitBeforeToggle(); if (this.inline) { this.dialog.show(); } else { this.dialog.showModal(); } - this.emitOpenChange(); - } + this.emitToggle(); + }); } /** - * Closes the drawer if it is currently open. * @public + * Method to hide the drawer */ public hide(): void { - if (this.dialog.open) { - this.dialog.close(); - } + this.emitBeforeToggle(); + this.dialog.close(); + this.emitToggle(); } /** - * Handles click events on the drawer. - * + * @public * @param event - The click event * @returns boolean - Always returns true - * @public + * Handles click events on the drawer. */ public clickHandler(event: Event): boolean { event.preventDefault(); - if (this.dialog.open && this.type !== DrawerModalType.alert && event.target === this.dialog) { + if (this.dialog.open && this.type !== DrawerType.alert && event.target === this.dialog) { this.hide(); - this.dismiss(); } return true; } /** * @public - * Handles keydown events on the drawer * @param e - The keydown event * @returns boolean | void + * Handles keydown events on the drawer */ public keydownHandler = (event: KeyboardEvent): boolean | void => { if (event.defaultPrevented) { @@ -172,13 +201,22 @@ export class Drawer extends FASTElement { case keyEscape: event.preventDefault(); - if (this.type !== DrawerModalType.alert) { + if (this.type !== DrawerType.alert) { this.hide(); - this.dismiss(); } break; default: return true; } }; + + /** + * Validates the configuration of the drawer. + * @throws {Error} Throws an error if the configuration is invalid. + */ + private validateConfiguration(): void { + if (this.inline && this.type !== DrawerType.nonModal) { + throw new Error('Invalid configuration: inline requires the type to be nonModal'); + } + } } diff --git a/packages/web-components/src/index-rollup.ts b/packages/web-components/src/index-rollup.ts index a68fc0ad19a367..3518a1288e9dad 100644 --- a/packages/web-components/src/index-rollup.ts +++ b/packages/web-components/src/index-rollup.ts @@ -98,7 +98,7 @@ export { DrawerDefinition, DrawerPosition, DrawerSize, - DrawerModalType, + DrawerType, DrawerTemplate, DrawerStyles, } from './drawer/index.js'; diff --git a/packages/web-components/src/index.ts b/packages/web-components/src/index.ts index 88c61a25cf6b6c..555776bd1e0d97 100644 --- a/packages/web-components/src/index.ts +++ b/packages/web-components/src/index.ts @@ -103,7 +103,7 @@ export { DrawerDefinition, DrawerPosition, DrawerSize, - DrawerModalType, + DrawerType, DrawerTemplate, DrawerStyles, } from './drawer/index.js'; From ff81c60e86910637ee97802a13f69011791e1731 Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Tue, 4 Jun 2024 16:33:49 -0700 Subject: [PATCH 10/21] drawer: addresses feedback --- .../web-components/src/drawer-body/README.md | 47 ++++++ .../src/drawer-body/drawer-body.styles.ts | 20 +-- .../src/drawer-body/drawer-body.template.ts | 20 ++- packages/web-components/src/drawer/README.md | 150 +++++++----------- .../src/drawer/drawer.options.ts | 3 +- .../src/drawer/drawer.stories.ts | 42 ----- .../src/drawer/drawer.styles.ts | 23 +-- .../src/drawer/drawer.template.ts | 5 +- packages/web-components/src/drawer/drawer.ts | 71 +++------ 9 files changed, 149 insertions(+), 232 deletions(-) diff --git a/packages/web-components/src/drawer-body/README.md b/packages/web-components/src/drawer-body/README.md index 47e45201c1c20c..301a9bc4e4dfdd 100644 --- a/packages/web-components/src/drawer-body/README.md +++ b/packages/web-components/src/drawer-body/README.md @@ -1 +1,48 @@ # DrawerBody + +## Overview + +The `DrawerBody` component is a part of the Fluent UI library and is intended to be used with the `fluent-drawer` component. It provides consumers with a structured template for the drawer's content, including areas for a header, content, and footer. + +## Attributes + +This component does not have specific attributes beyond standard HTML attributes. + +## Methods + +The `DrawerBody` component does not define custom methods beyond those inherited from `FASTElement`. + +## Events + +The `DrawerBody` component does not emit custom events beyond those standard to HTML elements. + +### **Slots** + +| Name | Description | +| -------- | ------------------------------------- | +| `header` | The slot for header | +| | The default slot for the main content | +| `footer` | The slot for the footer | + +## CSS Parts + +| Name | Description | +| --------- | ---------------------------------------------- | +| `header` | Styles the header section of the drawer. | +| `content` | Styles the main content section of the drawer. | +| `footer` | Styles the footer section of the drawer. | + +## Usage Examples + +### Basic Usage + +```html + + +
Drawer Title
+ +
Content goes here...
+
Footer content
+
+
+``` diff --git a/packages/web-components/src/drawer-body/drawer-body.styles.ts b/packages/web-components/src/drawer-body/drawer-body.styles.ts index 03f4604fa3a4b4..89c03f36899ccd 100644 --- a/packages/web-components/src/drawer-body/drawer-body.styles.ts +++ b/packages/web-components/src/drawer-body/drawer-body.styles.ts @@ -1,30 +1,20 @@ import { css } from '@microsoft/fast-element'; import { display } from '../utils/index.js'; -import { - spacingHorizontalM, - spacingHorizontalXL, - spacingVerticalL, - spacingVerticalXXL, -} from '../theme/design-tokens.js'; +import { spacingHorizontalM, spacingHorizontalXL } from '../theme/design-tokens.js'; import { typographySubtitle1Styles } from '../styles/partials/typography.partials.js'; /** Drawer styles * @public */ export const styles = css` - ${display('flex')} + ${display('grid')} :host { + box-sizing: boder-box; + grid-template-rows: min-content auto min-content; position: relative; height: 100%; padding: ${spacingHorizontalXL}; - max-height: calc(100vh - (2 * ${spacingVerticalXXL})); - } - .drawer-body { - display: grid; - grid-template-rows: min-content auto min-content; - gap: ${spacingVerticalL}; - overflow-y: auto; - max-height: calc(100vh - 2 * ${spacingVerticalL}); + max-height: 100svh; } .header { display: flex; diff --git a/packages/web-components/src/drawer-body/drawer-body.template.ts b/packages/web-components/src/drawer-body/drawer-body.template.ts index e23dca3c2466e1..822d90495ad189 100644 --- a/packages/web-components/src/drawer-body/drawer-body.template.ts +++ b/packages/web-components/src/drawer-body/drawer-body.template.ts @@ -7,17 +7,15 @@ import type { DrawerBody } from './drawer-body.js'; */ export function drawerBodyTemplate(): ElementViewTemplate { return html` -
-
- - -
-
- -
- +
+ + +
+
+ +
+ `; } diff --git a/packages/web-components/src/drawer/README.md b/packages/web-components/src/drawer/README.md index 0cde40138e175d..36e09e3df3c7a9 100644 --- a/packages/web-components/src/drawer/README.md +++ b/packages/web-components/src/drawer/README.md @@ -15,122 +15,98 @@ The Fluent WC3 Drawer extends `FASTElement` ### Template ```html - -
-
- -
-
- -
- -
+
``` ### **Variables** -| Name | Type | Description | -| ---------------- | ------------------------------- | ---------------------- | -| `DrawerPosition` | `start` `end` | Positions for Drawer | -| `DrawerSize` | `small` `medium` `large` `full` | Sizes for Drawer | -| `DrawerType` | `modal` `non-modal` `alert` | Modal types for Drawer | -| `DrawerType` | `overlay` `inline` | Types for Drawer | +| Name | Type | Description | +| ---------------- | ------------------------------- | ----------------------------------------- | +| `DrawerPosition` | `start` `end` | Positions for Drawer | +| `DrawerSize` | `small` `medium` `large` `full` | Sizes for Drawer | +| `DrawerType` | `modal` `non-modal` | Modal types for Drawer | +| `inline` | `boolean` | Determines if Drawer is an inline element | ### **Attributes** -| Name | Type | Default | Description | -| ------------------ | ----------------------------------- | ------------------- | -------------------------------------------------------------------------------- | -| `modal-type` | `modal` `non-modal` `alert` | `modal` | Determines whether the drawer should be displayed as modal, non-modal, or alert. | -| `type` | `overlay` `inline` | `false` | Determines whether the drawer should be displayed inline or as an overlay | -| `position` | `DrawerPosition \| undefined` | `start` | Sets the position of the drawer (left/right). | -| `size` | `DrawerSize \| number \| undefined` | `DrawerSize.medium` | Sets the control size of the drawer. | -| `open` | `boolean` | `false` | Sets the drawer to be open. | -| `aria-labelledby` | `string \| undefined` | `undefined` | Sets the aria-labelledby attribute of the drawer. | -| `aria-describedby` | `string \| undefined` | `undefined` | Sets the aria-describedby attribute of the drawer. | -| `aria-label` | `string \| undefined` | `undefined` | Sets the aria-label attribute of the drawer. | +| Name | Privacy | Type | Description | +| ----------------- | ------- | -------------- | ------------------------------------------------------------------------ | +| `type` | public | DrawerType | Determines whether the drawer should be displayed as modal or non-modal. | +| `ariaLabelledby` | public | string | The ID of the element that labels the drawer. | +| `ariaDescribedby` | public | string | The ID of the element that describes the drawer. | +| `inline` | public | boolean | Sets the drawer as inline. | +| `position` | public | DrawerPosition | Sets the position of the drawer (start/end). | +| `size` | public | DrawerSize | Sets the size of the drawer (small/medium/large). | ### **Events** -| Name | Type | Description | -| -------------- | ------------- | ------------------------------------------ | -| `onOpenChange` | `CustomEvent` | Fires when the drawer is opened or closed. | -| `cancel` | `CustomEvent` | Fires when the drawer is dismissed. | +| Name | Type | Description | +| -------------- | ------------- | -------------------------------------------- | +| `toggle` | `CustomEvent` | Fires after the dialog's open state changes | +| `beforetoggle` | `CustomEvent` | Fires before the dialog's open state changes | ### **Methods** -| Name | Privacy | Description | -| ------- | ------- | ----------------- | -| `show` | public | Shows the drawer. | -| `close` | public | Hides the drawer. | +| Name | Privacy | Description | +| ----------------------- | ------- | ------------------------------------------------------------------------------------------- | +| `show` | public | Shows the drawer. | +| `hide` | public | Hides the drawer. | +| `connectedCallback` | public | Called when the custom element is connected to the document's DOM. | +| `type` | public | Determines whether the drawer should be displayed as modal or non-modal. | +| `ariaLabelledby` | public | The ID of the element that labels the drawer. | +| `ariaDescribedby` | public | The ID of the element that describes the drawer. | +| `inline` | public | Sets the drawer as inline. | +| `position` | public | Sets the position of the drawer (start/end). | +| `size` | public | Sets the size of the drawer (small/medium/large). | +| `dialog` | public | The dialog element. | +| `emitToggle` | public | Method to emit an event after the dialog's open state changes. | +| `emitBeforeToggle` | public | Method to emit an event before the dialog's open state changes. | +| `inlineChanged` | public | Method called when the 'inline' attribute changes. | +| `typeChanged` | public | Method called when the 'type' attribute changes. | +| `clickHandler` | public | Handles click events on the drawer. | +| `keydownHandler` | public | Handles keydown events on the drawer. | +| `validateConfiguration` | private | Validates the configuration of the drawer. Throws an error if the configuration is invalid. | ### **Slots** -| Name | Description | -| -------- | ------------------------------------- | -| `header` | The slot for header | -| | The default slot for the main content | -| `footer` | The slot for the footer | +| Name | Description | +| ---- | ------------------------------------- | +| | The default slot for the main content | ### **CSS Variables** -| Name | Description | -| -------------------------- | ------------------------------------- | -| `--drawer-overflow-border` | Used to set the overflow border color | -| `--drawer-width` | Used to set the width of the drawer | +| Name | Description | +| ---------------- | ----------------------------------- | +| `--drawer-width` | Used to set the width of the drawer | ## **Accessiblity** ### **WAI-ARIA Roles, States, and Properties** -- `role="complementary"` - - - The drawer component should have a role of "complementary" by default to indicate its supplementary content role. - -- `role="dialog"` - - - The drawer component should have a role of "dialog" when rendered as a modal. - -- `aria-disabled` - - - The `aria-disabled` attribute should be set to indicate whether the drawer is disabled or enabled. When the drawer is disabled, `aria-disabled="true"`, and when enabled, `aria-disabled="false"`. - -- `tabindex` - - - The `tabindex` attribute should be set to control the tab order of the drawer component. When the drawer is open, `tabindex="0"` should be set to make the component focusable, and when closed, `tabindex="-1"` should be set to remove it from the tab order. - -- `aria-modal` - - - The `aria-modal` attribute should be set to indicate whether the drawer is modal or non-modal. When the drawer is modal, `aria-modal="true"`, and when it is non-modal, `aria-modal="false"`. - -- `aria-label` - - - The `aria-label` attribute should be used to label the drawer. - -- `aria-describedby` - - - The `aria-describedby` attribute should be used to associate the drawer with an element that provides a description of its purpose or content. - -- `aria-labelledby` - - - The `aria-labelledby` attribute should be used to associate the drawer with an element that serves as its accessible label or title. +| Name | Privacy | Type | Description | +| ------------------ | ------- | ------------------- | ------------------------------------------------------------------------------------------ | +| `role` | public | string | Sets the role of the dialog element, either 'region' or 'dialog' based on the drawer type. | +| `aria-modal` | public | string or undefined | Indicates if the drawer is modal. | +| `aria-describedby` | public | string | The ID of the element that describes the drawer. | +| `aria-labelledby` | public | string | The ID of the element that labels the drawer. | +| `aria-label` | public | string | Provides an accessible name for the drawer when aria-labelledby is not used. | ### **Fluent Web Component v3 v.s Fluent React 9** @@ -138,12 +114,8 @@ The Fluent WC3 Drawer extends `FASTElement` **Component and Slot Mapping** -| Fluent UI React 9 | Fluent Web Components 3 | -| ------------------------------- | ----------------------- | -| `` | `type="overlay"` | -| `` | `type="inline"` | -| `` | `slot="title"` | -| `` | `slot="action"` | -| ` ` | `default slot` | -| `` | `slot="navigation"` | -| `` | `slot="footer"` | +| Fluent UI React 9 | Fluent Web Components 3 | +| ----------------- | ----------------------- | +| `` | `type="modal"` | +| `` | `inline` | +| ` ` | `` | diff --git a/packages/web-components/src/drawer/drawer.options.ts b/packages/web-components/src/drawer/drawer.options.ts index bd70b6f92ceb6d..047cf685714efe 100644 --- a/packages/web-components/src/drawer/drawer.options.ts +++ b/packages/web-components/src/drawer/drawer.options.ts @@ -34,9 +34,8 @@ export type DrawerSize = ValuesOf; * A drawer can be different sizes */ export const DrawerType = { - alert: 'alert', - modal: 'modal', nonModal: 'non-modal', + modal: 'modal', } as const; /** diff --git a/packages/web-components/src/drawer/drawer.stories.ts b/packages/web-components/src/drawer/drawer.stories.ts index 95b170864376c0..411860bf5159c3 100644 --- a/packages/web-components/src/drawer/drawer.stories.ts +++ b/packages/web-components/src/drawer/drawer.stories.ts @@ -350,48 +350,6 @@ export const Modal = renderComponent(html`
`); -export const Alert = renderComponent(html` -
-
- - - Alert - -

- When an alert dialog is open it interrupts the user's workflow to communicate an important message or ask - for a decision. Unlike a typical modal dialog, the user must take an action through the options given to - dismiss the dialog, and it cannot be dismissed through the dimmed background. -

-
-
- - type="alert" - -
- Do Something - hideDrawer('drawer-alert')}>Close -
-
-
-
-
-

Alert

- -

- When an alert dialog is open it interrupts the user's workflow to communicate an important message or ask for - a decision. Unlike a typical modal dialog, the user must take an action through the options given to dismiss - the dialog, and it cannot be dismissed through the dimmed background. -

-
-
- - type="alert" - - Toggle Drawer -
-
-`); - export const NonModal = renderComponent(html`
diff --git a/packages/web-components/src/drawer/drawer.styles.ts b/packages/web-components/src/drawer/drawer.styles.ts index bc7465aebe0e9b..c7be7d37062243 100644 --- a/packages/web-components/src/drawer/drawer.styles.ts +++ b/packages/web-components/src/drawer/drawer.styles.ts @@ -4,8 +4,6 @@ import { colorBackgroundOverlay, colorNeutralBackground1, colorNeutralForeground1, - colorStrokeFocus1, - colorStrokeFocus2, colorTransparentStroke, curveAccelerateMid, curveDecelerateMid, @@ -16,7 +14,6 @@ import { fontWeightRegular, lineHeightBase300, shadow64, - strokeWidthThick, strokeWidthThin, } from '../theme/design-tokens.js'; @@ -28,7 +25,6 @@ export const styles = css` :host { --dialog-backdrop: ${colorBackgroundOverlay}; - overflow: hidden; } dialog { @@ -41,7 +37,6 @@ export const styles = css` color: ${colorNeutralForeground1}; max-width: var(--drawer-width, 592px); max-height: 100vh; - overflow: hidden; height: 100%; } @@ -54,7 +49,6 @@ export const styles = css` border-radius: 0; padding: 0; max-width: var(--drawer-width, 592px); - overflow: hidden; box-shadow: ${shadow64}; box-sizing: border-box; display: flex; @@ -114,23 +108,8 @@ export const styles = css` margin-inline-end: 0; } - dialog:focus-visible:after { - content: ''; - position: absolute; - inset: 0px; - cursor: pointer; - outline: none; - border: ${strokeWidthThick} solid ${colorStrokeFocus1}; - box-shadow: inset 0 0 0 1px ${colorStrokeFocus2}; - } - - dialog:focus-visible { - outline: none; - } - dialog::backdrop { - background: ${colorBackgroundOverlay}; - inset: 0; + background: var(--dialog-backdrop); } @layer animations { diff --git a/packages/web-components/src/drawer/drawer.template.ts b/packages/web-components/src/drawer/drawer.template.ts index 115227c869d779..18938b3a68ef0b 100644 --- a/packages/web-components/src/drawer/drawer.template.ts +++ b/packages/web-components/src/drawer/drawer.template.ts @@ -1,6 +1,5 @@ import { ElementViewTemplate, html, ref } from '@microsoft/fast-element'; import type { Drawer } from './drawer.js'; -import { DrawerType } from './drawer.options.js'; /** * The template for the Drawer component. @@ -11,7 +10,7 @@ export function drawerTemplate(): ElementViewTemplate { (): ElementViewTemplate { type="${x => x.type}" ?inline="${x => x.inline}" @click="${(x, c) => x.clickHandler(c.event as MouseEvent)}" - @keydown="${(x, c) => x.keydownHandler(c.event as KeyboardEvent)}" + @cancel="${(x, c) => x.hide()}" ${ref('dialog')} > diff --git a/packages/web-components/src/drawer/drawer.ts b/packages/web-components/src/drawer/drawer.ts index 3b6256e48d9dee..a7e71f4465b63c 100644 --- a/packages/web-components/src/drawer/drawer.ts +++ b/packages/web-components/src/drawer/drawer.ts @@ -1,36 +1,34 @@ import { attr, FASTElement, observable, Updates } from '@microsoft/fast-element'; -import { keyEscape } from '@microsoft/fast-web-utilities'; -import { DrawerType, DrawerPosition, DrawerSize } from './drawer.options.js'; +import { DrawerPosition, DrawerSize, DrawerType } from './drawer.options.js'; /** - * Drawer - * - * A Drawer component for creating modal or non-modal drawers with various configurations. + * A Drawer component that allows content to be displayed in a side panel. It can be rendered as modal or non-modal. * @extends FASTElement * - * @attr {DrawerType} type - Determines whether the drawer should be displayed as modal, non-modal, or alert. When in modal or alert mode, an overlay is applied over the rest of the view. + * @attr {DrawerType} type - Determines whether the drawer should be displayed as modal, non-modal, or alert. + * @attr {string} ariaLabelledby - The ID of the element that labels the drawer. + * @attr {string} ariaDescribedby - The ID of the element that describes the drawer. * @attr {boolean} inline - Sets the drawer as an inline element. * @attr {DrawerPosition} position - Sets the position of the drawer (start/end). * @attr {DrawerSize} size - Sets the size of the drawer (small/medium/large). - * @attr {string} aria-labelledby - The ID of the element that labels the drawer. - * @attr {string} aria-describedby - The ID of the element that describes the drawer. * - * @csspart dialog - The dialog element that represents the drawer. + * @csspart dialog - The dialog element of the drawer. * - * @slot - Default slot for the drawer's content. + * @slot - Default slot for the content of the drawer. * - * @fires toggle - Emitted after the drawer's open state changes - * @fires beforeToggle - Emitted before the drawer's open state changes + * @fires toggle - Event emitted after the dialog's open state changes. + * @fires beforetoggle - Event emitted before the dialog's open state changes. * - * @method connectedCallback() - Called when the custom element is connected to the document's DOM. - * @method show() - Opens the drawer if it is currently hidden. - * @method hide() - Closes the drawer if it is currently open. - * @method clickHandler(event) - Handles click events on the drawer. - * @method keydownHandler(event) - Handles keydown events on the drawer. - * @method typeChanged(oldValue, newValue) - Handles changes to the `type` attribute. - * @method inlineChanged(oldValue, newValue) - Handles changes to the `inline` attribute. + * @method show - Method to show the drawer. + * @method hide - Method to hide the drawer. + * @method clickHandler - Handles click events on the drawer. + * @method emitToggle - Emits an event after the dialog's open state changes. + * @method emitBeforeToggle - Emits an event before the dialog's open state changes. + * @method inlineChanged - Method called when the 'inline' attribute changes. + * @method typeChanged - Method called when the 'type' attribute changes. + * @method validateConfiguration - Validates the configuration of the drawer. * - * @summary A flexible drawer component that can be used in various configurations such as modal, non-modal, alert, inline, overlay, with different sizes and positions. + * @summary A component that provides a drawer for displaying content in a side panel. * * @tag fluent-drawer */ @@ -48,10 +46,10 @@ export class Drawer extends FASTElement { /** * @public - * Determines whether the drawer should be displayed as modal, non-modal, or alert. - * When in modal or alert mode, an overlay is applied over the rest of the view. + * Determines whether the drawer should be displayed as modal or non-modal + * When rendered as a modal, an overlay is applied over the rest of the view. */ - @attr({ attribute: 'type' }) + @attr public type: DrawerType = DrawerType.modal; /** @@ -113,7 +111,7 @@ export class Drawer extends FASTElement { /** * @public - * Method to emit an event after the dialog's open state changes + * Method to emit an event before the dialog's open state changes * HTML spec proposal: https://github.com/whatwg/html/issues/9733 */ public emitBeforeToggle = (): void => { @@ -181,35 +179,12 @@ export class Drawer extends FASTElement { */ public clickHandler(event: Event): boolean { event.preventDefault(); - if (this.dialog.open && this.type !== DrawerType.alert && event.target === this.dialog) { + if (this.dialog.open && event.target === this.dialog) { this.hide(); } return true; } - /** - * @public - * @param e - The keydown event - * @returns boolean | void - * Handles keydown events on the drawer - */ - public keydownHandler = (event: KeyboardEvent): boolean | void => { - if (event.defaultPrevented) { - return; - } - switch (event.key) { - case keyEscape: - event.preventDefault(); - - if (this.type !== DrawerType.alert) { - this.hide(); - } - break; - default: - return true; - } - }; - /** * Validates the configuration of the drawer. * @throws {Error} Throws an error if the configuration is invalid. From baee2f622bd8fd6f35928ae9e5eca3d84037dae0 Mon Sep 17 00:00:00 2001 From: BrdyBrn Date: Tue, 4 Jun 2024 16:37:04 -0700 Subject: [PATCH 11/21] Update packages/web-components/src/drawer/drawer.spec.ts Co-authored-by: Dave Rupert --- packages/web-components/src/drawer/drawer.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-components/src/drawer/drawer.spec.ts b/packages/web-components/src/drawer/drawer.spec.ts index aaf016f61f7dae..e341af10538525 100644 --- a/packages/web-components/src/drawer/drawer.spec.ts +++ b/packages/web-components/src/drawer/drawer.spec.ts @@ -312,7 +312,7 @@ test.describe('Drawer', () => { expect(wasDismissed).toBe(true); }); - test('should trap focus in the drawer when arrow up/down keys', async () => { + test('should trap focus in the drawer when tabbing', async () => { const first: Locator = element.locator('button', { hasText: 'First' }); const second: Locator = element.locator('button', { hasText: 'Second' }); const third: Locator = page.locator('button', { hasText: 'Third' }); From b36b6e0ab247a37d4f37abc1d57845b9ad66b9eb Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Tue, 4 Jun 2024 16:36:18 -0700 Subject: [PATCH 12/21] updates readme --- packages/web-components/src/drawer-body/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/web-components/src/drawer-body/README.md b/packages/web-components/src/drawer-body/README.md index 301a9bc4e4dfdd..3910ecf320797c 100644 --- a/packages/web-components/src/drawer-body/README.md +++ b/packages/web-components/src/drawer-body/README.md @@ -20,7 +20,8 @@ The `DrawerBody` component does not emit custom events beyond those standard to | Name | Description | | -------- | ------------------------------------- | -| `header` | The slot for header | +| `title` | The slot for title | +| `close` | The slot for the close button | | | The default slot for the main content | | `footer` | The slot for the footer | From a17bb47a80b44007a39c9b14481975e9d5b6e7e0 Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Tue, 4 Jun 2024 18:02:03 -0700 Subject: [PATCH 13/21] updates tests --- .../web-components/src/drawer/drawer.spec.ts | 114 ++++-------------- 1 file changed, 21 insertions(+), 93 deletions(-) diff --git a/packages/web-components/src/drawer/drawer.spec.ts b/packages/web-components/src/drawer/drawer.spec.ts index e341af10538525..07506396f6b7aa 100644 --- a/packages/web-components/src/drawer/drawer.spec.ts +++ b/packages/web-components/src/drawer/drawer.spec.ts @@ -8,15 +8,12 @@ import type { Drawer } from './drawer.js'; test.describe('Drawer', () => { let page: Page; let element: Locator; - let dialog: Locator; let root: Locator; test.beforeAll(async ({ browser }) => { page = await browser.newPage(); root = page.locator('#root'); element = page.locator('fluent-drawer'); - dialog = page.locator('dialog'); - await page.goto(fixtureURL('components-drawer--drawer')); }); @@ -26,12 +23,6 @@ test.describe('Drawer', () => { // eslint-disable-next-line playwright/no-focused-test test('should reflect size attribute', async () => { - await root.evaluate(node => { - node.innerHTML = /* html */ ` - - `; - }); - await expect(element).toHaveAttribute('size', 'medium'); await expect(element).toHaveJSProperty('size', 'medium'); @@ -73,50 +64,25 @@ test.describe('Drawer', () => { await expect(element).toHaveJSProperty('position', 'start'); }); - test('should reflect modal-type attribute', async () => { - await root.evaluate(node => { - node.innerHTML = /* html */ ` - - `; - }); - - await expect(element).toHaveAttribute('modal-type', 'modal'); - await expect(element).toHaveJSProperty('modalType', 'modal'); - - await element.evaluate((node: Drawer) => { - node.modalType = 'non-modal'; - }); - - await expect(element).toHaveAttribute('modal-type', 'non-modal'); - await expect(element).toHaveJSProperty('modalType', 'non-modal'); - - await element.evaluate((node: Drawer) => { - node.modalType = 'alert'; - }); - - await expect(element).toHaveAttribute('modal-type', 'alert'); - await expect(element).toHaveJSProperty('modalType', 'alert'); - }); - - test('should reflect open attribute', async () => { + test('should reflect type attribute', async () => { await root.evaluate(node => { node.innerHTML = /* html */ ` - + `; }); - await expect(element).toHaveAttribute('open', ''); - await expect(element).toHaveJSProperty('open', true); + await expect(element).toHaveAttribute('type', 'modal'); + await expect(element).toHaveJSProperty('Type', 'modal'); await element.evaluate((node: Drawer) => { - node.open = false; + node.type = 'non-modal'; }); - await expect(element).not.toHaveAttribute('open', ''); - await expect(element).toHaveJSProperty('open', false); + await expect(element).toHaveAttribute('type', 'non-modal'); + await expect(element).toHaveJSProperty('type', 'non-modal'); }); - test('should reflect aria-label attribute', async () => { + test('should reflect ariaLabel attribute', async () => { await root.evaluate(node => { node.innerHTML = /* html */ ` @@ -198,10 +164,14 @@ test.describe('Drawer', () => { test('close method should close drawer', async () => { await root.evaluate(node => { node.innerHTML = /* html */ ` - + `; }); + await element.evaluate((node: Drawer) => { + node.show(); + }); + await expect(element).toHaveAttribute('open', ''); await expect(element).toHaveJSProperty('open', true); @@ -231,15 +201,6 @@ test.describe('Drawer', () => { await expect(element).toHaveJSProperty('open', true); }); - test('a drawer with an alert modal-type should include a role of `alertdialog` on the control', async () => { - await root.evaluate(node => { - node.innerHTML = /* html */ ` - - `; - }); - await expect(dialog).toHaveAttribute('role', 'alertdialog'); - }); - test('should emit an event when open property changes', async () => { await root.evaluate(node => { node.innerHTML = /* html */ ` @@ -251,7 +212,7 @@ test.describe('Drawer', () => { element.evaluate( node => new Promise(resolve => { - node.addEventListener('onOpenChange', () => resolve(true)); + node.addEventListener('toggle', () => resolve(true)); }), ), await element.evaluate((node: Drawer) => { @@ -267,12 +228,16 @@ test.describe('Drawer', () => { await root.evaluate(node => { node.innerHTML = /* html */ ` - + `; }); + await element.evaluate((node: Drawer) => { + node.show(); + }); + await expect(element).toHaveAttribute('open', ''); await expect(element).toHaveJSProperty('open', true); @@ -286,10 +251,10 @@ test.describe('Drawer', () => { await expect(element).toHaveJSProperty('open', false); }); - test("should fire a 'dismiss' event when keydown is invoked on the document", async () => { + test("should fire a 'cancel' event when keydown is invoked on the document", async () => { await root.evaluate(node => { node.innerHTML = /* html */ ` - + `; }); @@ -311,41 +276,4 @@ test.describe('Drawer', () => { expect(wasDismissed).toBe(true); }); - - test('should trap focus in the drawer when tabbing', async () => { - const first: Locator = element.locator('button', { hasText: 'First' }); - const second: Locator = element.locator('button', { hasText: 'Second' }); - const third: Locator = page.locator('button', { hasText: 'Third' }); - await root.evaluate(node => { - node.innerHTML = /* html */ ` - - - - - - `; - }); - - await first.focus(); - - await expect(first).toBeFocused(); - await first.press('Tab'); - - await expect(second).toBeFocused(); - await second.press('Tab'); - - await expect(third).toBeFocused(); - await third.press('Tab'); - - await expect(first).toBeFocused(); - await first.press('Tab'); - - await expect(second).toBeFocused(); - await second.press('Shift+Tab'); - - await expect(first).toBeFocused(); - await first.press('Shift+Tab'); - - await expect(third).toBeFocused(); - }); }); From 3093bb381c5018ae77b7396ad3c3d5831d0a6e2d Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Wed, 5 Jun 2024 10:25:59 -0700 Subject: [PATCH 14/21] drawer: updates tests --- .../web-components/src/drawer/drawer.spec.ts | 240 +++++++----------- 1 file changed, 85 insertions(+), 155 deletions(-) diff --git a/packages/web-components/src/drawer/drawer.spec.ts b/packages/web-components/src/drawer/drawer.spec.ts index 07506396f6b7aa..50d06ec038ce03 100644 --- a/packages/web-components/src/drawer/drawer.spec.ts +++ b/packages/web-components/src/drawer/drawer.spec.ts @@ -6,88 +6,85 @@ import { fixtureURL } from '../helpers.tests.js'; import type { Drawer } from './drawer.js'; test.describe('Drawer', () => { - let page: Page; - let element: Locator; - let root: Locator; - - test.beforeAll(async ({ browser }) => { - page = await browser.newPage(); - root = page.locator('#root'); - element = page.locator('fluent-drawer'); + test.beforeEach(async ({ page }) => { await page.goto(fixtureURL('components-drawer--drawer')); - }); - test.afterAll(async () => { - await page.close(); + await page.waitForFunction(() => customElements.whenDefined('fluent-drawer')); }); - // eslint-disable-next-line playwright/no-focused-test - test('should reflect size attribute', async () => { - await expect(element).toHaveAttribute('size', 'medium'); - await expect(element).toHaveJSProperty('size', 'medium'); + test('should reflect type attribute', async ({ page }) => { + const element = page.locator('fluent-drawer'); + + await page.setContent(/* html */ ` + Drawer + `); + + await expect(element).toHaveAttribute('type', 'modal'); + await expect(element).toHaveJSProperty('type', 'modal'); await element.evaluate((node: Drawer) => { - node.size = 'small'; + node.type = 'non-modal'; }); + await expect(element).toHaveAttribute('type', 'non-modal'); + }); + + test('should reflect size attribute', async ({ page }) => { + const element = page.locator('fluent-drawer'); + + await page.setContent(/* html */ ` + Drawer + `); + await expect(element).toHaveAttribute('size', 'small'); await expect(element).toHaveJSProperty('size', 'small'); + await element.evaluate((node: Drawer) => { + node.size = 'medium'; + }); + + await expect(element).toHaveAttribute('size', 'medium'); + await expect(element).toHaveJSProperty('size', 'medium'); + await element.evaluate((node: Drawer) => { node.size = 'large'; }); + await expect(element).toHaveAttribute('size', 'large'); await expect(element).toHaveJSProperty('size', 'large'); await element.evaluate((node: Drawer) => { node.size = 'full'; }); + await expect(element).toHaveAttribute('size', 'full'); await expect(element).toHaveJSProperty('size', 'full'); }); - test('should reflect position attribute', async () => { - await root.evaluate(node => { - node.innerHTML = /* html */ ` - - `; - }); - - await expect(element).toHaveAttribute('position', 'end'); - await expect(element).toHaveJSProperty('position', 'end'); + test('should reflect position attribute', async ({ page }) => { + const element = page.locator('fluent-drawer'); - await element.evaluate((node: Drawer) => { - node.position = 'start'; - }); + await page.setContent(/* html */ ` + Drawer + `); await expect(element).toHaveAttribute('position', 'start'); await expect(element).toHaveJSProperty('position', 'start'); - }); - - test('should reflect type attribute', async () => { - await root.evaluate(node => { - node.innerHTML = /* html */ ` - - `; - }); - - await expect(element).toHaveAttribute('type', 'modal'); - await expect(element).toHaveJSProperty('Type', 'modal'); await element.evaluate((node: Drawer) => { - node.type = 'non-modal'; + node.position = 'end'; }); - await expect(element).toHaveAttribute('type', 'non-modal'); - await expect(element).toHaveJSProperty('type', 'non-modal'); + await expect(element).toHaveAttribute('position', 'end'); + await expect(element).toHaveJSProperty('position', 'end'); }); - test('should reflect ariaLabel attribute', async () => { - await root.evaluate(node => { - node.innerHTML = /* html */ ` - - `; - }); + test('should reflect aria-label attribute', async ({ page }) => { + const element = page.locator('fluent-drawer'); + + await page.setContent(/* html */ ` + Drawer + `); await expect(element).toHaveAttribute('aria-label', 'abc'); await expect(element).toHaveJSProperty('ariaLabel', 'abc'); @@ -100,12 +97,12 @@ test.describe('Drawer', () => { await expect(element).toHaveJSProperty('ariaLabel', 'def'); }); - test('should reflect ariaLabelledby attribute', async () => { - await root.evaluate(node => { - node.innerHTML = /* html */ ` - - `; - }); + test('should reflect aria-labelledby attribute', async ({ page }) => { + const element = page.locator('fluent-drawer'); + + await page.setContent(/* html */ ` + Drawer + `); await expect(element).toHaveAttribute('aria-labelledby', 'abc'); await expect(element).toHaveJSProperty('ariaLabelledby', 'abc'); @@ -118,12 +115,12 @@ test.describe('Drawer', () => { await expect(element).toHaveJSProperty('ariaLabelledby', 'def'); }); - test('should reflect ariaDescribedby attribute', async () => { - await root.evaluate(node => { - node.innerHTML = /* html */ ` - - `; - }); + test('should reflect aria-describedby attribute', async ({ page }) => { + const element = page.locator('fluent-drawer'); + + await page.setContent(/* html */ ` + Drawer + `); await expect(element).toHaveAttribute('aria-describedby', 'abc'); await expect(element).toHaveJSProperty('ariaDescribedby', 'abc'); @@ -136,83 +133,40 @@ test.describe('Drawer', () => { await expect(element).toHaveJSProperty('ariaDescribedby', 'def'); }); - test('show and hide methods should toggle open state of drawer', async () => { - await root.evaluate(node => { - node.innerHTML = /* html */ ` - - `; - }); - - await expect(element).not.toHaveAttribute('open', ''); - await expect(element).toHaveJSProperty('open', false); - - await element.evaluate((node: Drawer) => { - node.show(); - }); - - await expect(element).toHaveAttribute('open', ''); - await expect(element).toHaveJSProperty('open', true); - - await element.evaluate((node: Drawer) => { - node.hide(); - }); - - await expect(element).not.toHaveAttribute('open', ''); - await expect(element).toHaveJSProperty('open', false); - }); - - test('close method should close drawer', async () => { - await root.evaluate(node => { - node.innerHTML = /* html */ ` - - `; - }); + test('should emit an event when open property changes', async ({ page }) => { + const element = page.locator('fluent-drawer'); - await element.evaluate((node: Drawer) => { - node.show(); - }); + await page.setContent(/* html */ ` + Drawer + `); - await expect(element).toHaveAttribute('open', ''); - await expect(element).toHaveJSProperty('open', true); - - await element.evaluate((node: Drawer) => { - node.hide(); - }); + const [wasOpened] = await Promise.all([ + element.evaluate( + node => + new Promise(resolve => { + node.addEventListener('toggle', () => resolve(true)); + }), + ), + await element.evaluate((node: Drawer) => { + node.show(); + }), + ]); - await expect(element).not.toHaveAttribute('open', ''); - await expect(element).toHaveJSProperty('open', false); + expect(wasOpened).toBe(true); }); - test('show method should open drawer', async () => { - await root.evaluate(node => { - node.innerHTML = /* html */ ` - - `; - }); - - await expect(element).not.toHaveAttribute('open', ''); - await expect(element).toHaveJSProperty('open', false); + test('should emit an event before open property changes', async ({ page }) => { + const element = page.locator('fluent-drawer'); - await element.evaluate((node: Drawer) => { - node.show(); - }); - - await expect(element).toHaveAttribute('open', ''); - await expect(element).toHaveJSProperty('open', true); - }); - - test('should emit an event when open property changes', async () => { - await root.evaluate(node => { - node.innerHTML = /* html */ ` - - `; - }); + await page.setContent(/* html */ ` + Drawer + `); const [wasOpened] = await Promise.all([ element.evaluate( node => new Promise(resolve => { - node.addEventListener('toggle', () => resolve(true)); + node.addEventListener('beforetoggle', () => resolve(true)); }), ), await element.evaluate((node: Drawer) => { @@ -223,41 +177,17 @@ test.describe('Drawer', () => { expect(wasOpened).toBe(true); }); - test('drawer should close on escape keypress', async () => { - const first: Locator = element.locator('button', { hasText: 'First' }); + test('should fire a `cancel` event when keydown is invoked on the document', async ({ page }) => { + const element = page.locator('fluent-drawer'); - await root.evaluate(node => { - node.innerHTML = /* html */ ` - - - - `; - }); + await page.setContent(/* html */ ` + + `); await element.evaluate((node: Drawer) => { node.show(); }); - await expect(element).toHaveAttribute('open', ''); - await expect(element).toHaveJSProperty('open', true); - - await first.focus(); - - await expect(first).toBeFocused(); - - await first.press(keyEscape); - - await expect(element).not.toHaveAttribute('open', ''); - await expect(element).toHaveJSProperty('open', false); - }); - - test("should fire a 'cancel' event when keydown is invoked on the document", async () => { - await root.evaluate(node => { - node.innerHTML = /* html */ ` - - `; - }); - const [wasDismissed] = await Promise.all([ element.evaluate( node => From f0cc64e4f52218d6be3b148d344d787d7f96b9ba Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Wed, 5 Jun 2024 10:32:44 -0700 Subject: [PATCH 15/21] removes dead code --- packages/web-components/src/drawer/drawer.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/web-components/src/drawer/drawer.spec.ts b/packages/web-components/src/drawer/drawer.spec.ts index 50d06ec038ce03..2d167763e75940 100644 --- a/packages/web-components/src/drawer/drawer.spec.ts +++ b/packages/web-components/src/drawer/drawer.spec.ts @@ -1,6 +1,4 @@ import { expect, test } from '@playwright/test'; -import type { Locator, Page } from '@playwright/test'; -import { keyEscape } from '@microsoft/fast-web-utilities'; import { fixtureURL } from '../helpers.tests.js'; import type { Drawer } from './drawer.js'; From 0e04bc135119e08f7ce52e95556eab05d9ccf9fe Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Tue, 11 Jun 2024 13:03:19 -0700 Subject: [PATCH 16/21] drawer: addresses feedback --- .../src/drawer-body/drawer-body.template.ts | 2 +- packages/web-components/src/drawer/README.md | 80 ++++---- .../src/drawer/drawer.options.ts | 1 + .../src/drawer/drawer.stories.ts | 181 +++++++----------- .../src/drawer/drawer.styles.ts | 89 ++++----- .../src/drawer/drawer.template.ts | 5 +- packages/web-components/src/drawer/drawer.ts | 63 +----- 7 files changed, 149 insertions(+), 272 deletions(-) diff --git a/packages/web-components/src/drawer-body/drawer-body.template.ts b/packages/web-components/src/drawer-body/drawer-body.template.ts index 822d90495ad189..eb515759e7a761 100644 --- a/packages/web-components/src/drawer-body/drawer-body.template.ts +++ b/packages/web-components/src/drawer-body/drawer-body.template.ts @@ -1,4 +1,4 @@ -import { ElementViewTemplate, html, ref } from '@microsoft/fast-element'; +import { ElementViewTemplate, html } from '@microsoft/fast-element'; import type { DrawerBody } from './drawer-body.js'; /** diff --git a/packages/web-components/src/drawer/README.md b/packages/web-components/src/drawer/README.md index 36e09e3df3c7a9..3b65f3b9fac925 100644 --- a/packages/web-components/src/drawer/README.md +++ b/packages/web-components/src/drawer/README.md @@ -14,46 +14,47 @@ The Fluent WC3 Drawer extends `FASTElement` ### Template -```html +```ts +export function drawerTemplate(): ElementViewTemplate { + return html` + `; +} ``` ### **Variables** -| Name | Type | Description | -| ---------------- | ------------------------------- | ----------------------------------------- | -| `DrawerPosition` | `start` `end` | Positions for Drawer | -| `DrawerSize` | `small` `medium` `large` `full` | Sizes for Drawer | -| `DrawerType` | `modal` `non-modal` | Modal types for Drawer | -| `inline` | `boolean` | Determines if Drawer is an inline element | +| Name | Type | Description | +| ---------------- | ------------------------------- | ---------------------- | +| `DrawerPosition` | `start` `end` | Positions for Drawer | +| `DrawerSize` | `small` `medium` `large` `full` | Sizes for Drawer | +| `DrawerType` | `modal` `non-modal` `inline` | Modal types for Drawer | ### **Attributes** -| Name | Privacy | Type | Description | -| ----------------- | ------- | -------------- | ------------------------------------------------------------------------ | -| `type` | public | DrawerType | Determines whether the drawer should be displayed as modal or non-modal. | -| `ariaLabelledby` | public | string | The ID of the element that labels the drawer. | -| `ariaDescribedby` | public | string | The ID of the element that describes the drawer. | -| `inline` | public | boolean | Sets the drawer as inline. | -| `position` | public | DrawerPosition | Sets the position of the drawer (start/end). | -| `size` | public | DrawerSize | Sets the size of the drawer (small/medium/large). | +| Name | Privacy | Type | Description | +| ----------------- | ------- | -------------- | ----------------------------------------------------------------------------------------- | +| `type` | public | DrawerType | Determines whether the drawer should be displayed as a modal, non-modal or inline drawer. | +| `position` | public | DrawerPosition | Sets the position of the drawer (start/end). | +| `size` | public | DrawerSize | Sets the size of the drawer (small/medium/large). | +| `ariaDescribedby` | public | string | The ID of the element that describes the drawer. | +| `ariaLabelledby` | public | string | The ID of the element that labels the drawer. | ### **Events** @@ -64,25 +65,14 @@ The Fluent WC3 Drawer extends `FASTElement` ### **Methods** -| Name | Privacy | Description | -| ----------------------- | ------- | ------------------------------------------------------------------------------------------- | -| `show` | public | Shows the drawer. | -| `hide` | public | Hides the drawer. | -| `connectedCallback` | public | Called when the custom element is connected to the document's DOM. | -| `type` | public | Determines whether the drawer should be displayed as modal or non-modal. | -| `ariaLabelledby` | public | The ID of the element that labels the drawer. | -| `ariaDescribedby` | public | The ID of the element that describes the drawer. | -| `inline` | public | Sets the drawer as inline. | -| `position` | public | Sets the position of the drawer (start/end). | -| `size` | public | Sets the size of the drawer (small/medium/large). | -| `dialog` | public | The dialog element. | -| `emitToggle` | public | Method to emit an event after the dialog's open state changes. | -| `emitBeforeToggle` | public | Method to emit an event before the dialog's open state changes. | -| `inlineChanged` | public | Method called when the 'inline' attribute changes. | -| `typeChanged` | public | Method called when the 'type' attribute changes. | -| `clickHandler` | public | Handles click events on the drawer. | -| `keydownHandler` | public | Handles keydown events on the drawer. | -| `validateConfiguration` | private | Validates the configuration of the drawer. Throws an error if the configuration is invalid. | +| Name | Privacy | Description | +| ------------------ | ------- | --------------------------------------------------------------- | +| `show` | public | Shows the drawer. | +| `hide` | public | Hides the drawer. | +| `emitToggle` | public | Method to emit an event after the dialog's open state changes. | +| `emitBeforeToggle` | public | Method to emit an event before the dialog's open state changes. | +| `clickHandler` | public | Handles click events on the drawer. | +| `keydownHandler` | public | Handles keydown events on the drawer. | ### **Slots** @@ -100,13 +90,13 @@ The Fluent WC3 Drawer extends `FASTElement` ### **WAI-ARIA Roles, States, and Properties** -| Name | Privacy | Type | Description | -| ------------------ | ------- | ------------------- | ------------------------------------------------------------------------------------------ | -| `role` | public | string | Sets the role of the dialog element, either 'region' or 'dialog' based on the drawer type. | -| `aria-modal` | public | string or undefined | Indicates if the drawer is modal. | -| `aria-describedby` | public | string | The ID of the element that describes the drawer. | -| `aria-labelledby` | public | string | The ID of the element that labels the drawer. | -| `aria-label` | public | string | Provides an accessible name for the drawer when aria-labelledby is not used. | +| Name | Privacy | Type | Description | +| ------------------ | ------- | ------------------- | ---------------------------------------------------------------------------- | +| `role` | public | string | Sets the role of the dialog element to dialog when modal dialog is rendered. | +| `aria-modal` | public | string or undefined | Indicates if the drawer is modal. | +| `aria-describedby` | public | string | The ID of the element that describes the drawer. | +| `aria-labelledby` | public | string | The ID of the element that labels the drawer. | +| `aria-label` | public | string | Provides an accessible name for the drawer when aria-labelledby is not used. | ### **Fluent Web Component v3 v.s Fluent React 9** @@ -117,5 +107,5 @@ The Fluent WC3 Drawer extends `FASTElement` | Fluent UI React 9 | Fluent Web Components 3 | | ----------------- | ----------------------- | | `` | `type="modal"` | -| `` | `inline` | +| `` | `type="inline"` | | ` ` | `` | diff --git a/packages/web-components/src/drawer/drawer.options.ts b/packages/web-components/src/drawer/drawer.options.ts index 047cf685714efe..74e2a9c5f36327 100644 --- a/packages/web-components/src/drawer/drawer.options.ts +++ b/packages/web-components/src/drawer/drawer.options.ts @@ -36,6 +36,7 @@ export type DrawerSize = ValuesOf; export const DrawerType = { nonModal: 'non-modal', modal: 'modal', + inline: 'inline', } as const; /** diff --git a/packages/web-components/src/drawer/drawer.stories.ts b/packages/web-components/src/drawer/drawer.stories.ts index 411860bf5159c3..cdd78e6b535d90 100644 --- a/packages/web-components/src/drawer/drawer.stories.ts +++ b/packages/web-components/src/drawer/drawer.stories.ts @@ -3,7 +3,7 @@ import type { Args, Meta } from '@storybook/html'; import { renderComponent } from '../helpers.stories.js'; import { RadioGroup } from '../radio-group/radio-group.js'; import type { Drawer as FluentDrawer } from './drawer.js'; -import { DrawerType, DrawerPosition, DrawerSize } from './drawer.options.js'; +import { DrawerPosition, DrawerSize, DrawerType } from './drawer.options.js'; import './define.js'; import '../drawer-body/define.js'; @@ -62,11 +62,8 @@ const toggleSelectedDrawer = (radioGroupId: string) => { }; const hideDrawer = (drawerID: string) => { - console.log('hide'); const drawer = document.getElementById(drawerID) as FluentDrawer; if (drawer.dialog.open) { - console.log('hidden'); - drawer.hide(); } }; @@ -134,44 +131,35 @@ const storyTemplate = html` }
-
- - - Drawer Header + + + Drawer Header +
+ + The drawer gives users a quick entry point to configuration and information. It should be used when + retaining context is beneficial to users. An overlay is optional depending on whether or not interacting + with the backgroun d content is beneficial to the user's context/scenario. An overlay makes the drawer + blocking and signifies that the users full attention is required when making configurations. +
- - The drawer gives users a quick entry point to configuration and information. It should be used when - retaining context is beneficial to users. An overlay is optional depending on whether or not interacting - with the backgroun d content is beneficial to the user's context/scenario. An overlay makes the drawer - blocking and signifies that the users full attention is required when making configurations. - -
- - Please select an option - Option 1 - Option 2 - Option 3 - -
-
-
- Close - Do Something + + Please select an option + Option 1 + Option 2 + Option 3 +
- - -
+
+
+ Close + Do Something +
+ +

Drawer

-

The Drawer gives users a quick entry point to configuration and information. It should be used when @@ -186,47 +174,36 @@ const storyTemplate = html` Start End - Toggle Drawer

-
- - - Drawer Header + + + Drawer Header +
+ + The drawer gives users a quick entry point to configuration and information. It should be used when + retaining context is beneficial to users. An overlay is optional depending on whether or not interacting + with the backgroun d content is beneficial to the user's context/scenario. An overlay makes the drawer + blocking and signifies that the users full attention is required when making configurations. +
- - The drawer gives users a quick entry point to configuration and information. It should be used when - retaining context is beneficial to users. An overlay is optional depending on whether or not interacting - with the backgroun d content is beneficial to the user's context/scenario. An overlay makes the drawer - blocking and signifies that the users full attention is required when making configurations. - -
- - Please select an option - Option 1 - Option 2 - Option 3 - -
-
-
- Close - Do Something + + Please select an option + Option 1 + Option 2 + Option 3 +
- - -
+
+
+ Close + Do Something +
+ +
`; @@ -235,7 +212,6 @@ export default { title: 'Components/Drawer', args: { type: DrawerType.modal, - inline: false, size: DrawerSize.medium, }, argTypes: { @@ -264,19 +240,6 @@ export default { }, }, }, - inline: { - control: { - type: 'boolean', - }, - table: { - type: { - summary: 'Determines if the drawer is an inline drawer', - }, - defaultValue: { - summary: false, - }, - }, - }, size: { options: Object.values(DrawerSize), control: { @@ -342,6 +305,7 @@ export const Modal = renderComponent(html`


+
type="modal" @@ -373,6 +337,7 @@ export const NonModal = renderComponent(html`


+
type="non-modal" @@ -400,7 +365,7 @@ export const NonModal = renderComponent(html` export const Inline = renderComponent(html`
- + Drawer Inline `


- +
- inline -
- - type="non-modal" + type="inline"
@@ -439,12 +401,9 @@ export const Inline = renderComponent(html`


- +
- inline -
- - type="non-modal" + type="inline"
Select a Drawer Position @@ -455,7 +414,7 @@ export const Inline = renderComponent(html` >Toggle Drawer
- + Drawer Inline `


+
- inline -
- - type="non-modal" + type="inline"
@@ -490,7 +447,7 @@ export const Inline = renderComponent(html` export const Position = renderComponent(html`
- + Drawer position start @@ -544,7 +501,7 @@ export const Position = renderComponent(html` >
- + Drawer position end `


+
position="end" @@ -616,6 +574,7 @@ export const Size = renderComponent(html`

The size attribute controls the width of the drawer. The default is medium.


+
size="small" @@ -637,6 +596,7 @@ export const Size = renderComponent(html`

The size attribute controls the width of the drawer. The default is medium.


+
default @@ -645,9 +605,8 @@ export const Size = renderComponent(html` Drawer large - `

The size attribute controls the width of the drawer. The default is medium.

-
- +
+
size="large"
-
- Drawer full - `

The size attribute controls the width of the drawer. The default is medium.


+
size="full" @@ -699,6 +656,8 @@ export const CustomSize = renderComponent(html`

The Drawer can be sized to any custom width, by overriding the drawer-width CSS variable:

+
+
var(--drawer-width) diff --git a/packages/web-components/src/drawer/drawer.styles.ts b/packages/web-components/src/drawer/drawer.styles.ts index c7be7d37062243..dfbea798de4888 100644 --- a/packages/web-components/src/drawer/drawer.styles.ts +++ b/packages/web-components/src/drawer/drawer.styles.ts @@ -27,55 +27,20 @@ export const styles = css` --dialog-backdrop: ${colorBackgroundOverlay}; } - dialog { - display: none; - z-index: var(--drawer-elevation, 1000); - font-size: ${fontSizeBase300}; - line-height: ${lineHeightBase300}; - font-family: ${fontFamilyBase}; - font-weight: ${fontWeightRegular}; - color: ${colorNeutralForeground1}; - max-width: var(--drawer-width, 592px); - max-height: 100vh; - height: 100%; - } - - dialog[open] { - outline: none; - top: 0; - margin-inline-start: 0; - bottom: 0; - width: var(--drawer-width, 592px); - border-radius: 0; - padding: 0; - max-width: var(--drawer-width, 592px); - box-shadow: ${shadow64}; - box-sizing: border-box; - display: flex; - flex-direction: column; - border: ${strokeWidthThin} solid ${colorTransparentStroke}; - background: ${colorNeutralBackground1}; - } - :host([type='non-modal']) dialog[open]::backdrop { display: none; } - :host([inline]) { - width: fit-content; - position: relative; + :host([type='inline']) { height: 100%; + width: fit-content; } - :host([inline]) dialog { + :host([type='inline']) dialog { box-shadow: none; position: relative; } - :host([inline]) dialog[open] { - position: relative; - } - :host([size='small']) dialog { width: 320px; max-width: 320px; @@ -91,21 +56,42 @@ export const styles = css` max-width: 100%; } - :host([position='start']) dialog, :host([position='end']) dialog { - inset-inline-end: 0px; - border-inline-end-color: ${colorTransparentStroke}; - border-inline-start-color: var(--drawer-separator, ${colorTransparentStroke}); + margin-inline-start: auto; + margin-inline-end: 0; } - :host([position='start']) dialog { + dialog { + display: none; + z-index: var(--drawer-elevation, 1000); + font-size: ${fontSizeBase300}; + line-height: ${lineHeightBase300}; + font-family: ${fontFamilyBase}; + font-weight: ${fontWeightRegular}; + color: ${colorNeutralForeground1}; + max-width: var(--drawer-width, 592px); + max-height: 100vh; + height: 100%; margin-inline-start: 0; margin-inline-end: auto; + border-inline-end-color: ${colorTransparentStroke}; + border-inline-start-color: var(--drawer-separator, ${colorTransparentStroke}); } - :host([position='end']) dialog { - margin-inline-start: auto; - margin-inline-end: 0; + dialog[open] { + outline: none; + top: 0; + bottom: 0; + width: var(--drawer-width, 592px); + border-radius: 0; + padding: 0; + max-width: var(--drawer-width, 592px); + box-shadow: ${shadow64}; + box-sizing: border-box; + display: flex; + flex-direction: column; + border: ${strokeWidthThin} solid ${colorTransparentStroke}; + background: ${colorNeutralBackground1}; } dialog::backdrop { @@ -131,12 +117,11 @@ export const styles = css` transition-timing-function: ${curveAccelerateMid}; } - dialog[open], - :host([position='end']) dialog[open] { + dialog[open] { transform: translateX(0); } - ::backdrop { + dialog::backdrop { transition: display allow-discrete, opacity, overlay allow-discrete, scale; transition-duration: ${durationGentle}; transition-timing-function: ${curveDecelerateMid}; @@ -146,11 +131,11 @@ export const styles = css` } /* Set opacity to 1 when open */ - [open]::backdrop { + dialog[open]::backdrop { opacity: 1; } - ::backdrop { + dialog::backdrop { transition-timing-function: ${curveLinear}; } } @@ -162,7 +147,7 @@ export const styles = css` :host([position='end']) dialog[open] { transform: translateX(100%); } - [open]::backdrop { + dialog[open]::backdrop { opacity: 0; } } diff --git a/packages/web-components/src/drawer/drawer.template.ts b/packages/web-components/src/drawer/drawer.template.ts index 18938b3a68ef0b..e2754b2d43eb02 100644 --- a/packages/web-components/src/drawer/drawer.template.ts +++ b/packages/web-components/src/drawer/drawer.template.ts @@ -10,15 +10,14 @@ export function drawerTemplate(): ElementViewTemplate { { - this.validateConfiguration(); - }); - } - /** * @public * Determines whether the drawer should be displayed as modal or non-modal @@ -66,14 +51,6 @@ export class Drawer extends FASTElement { @attr({ attribute: 'aria-describedby' }) public ariaDescribedby?: string; - /** - * @public - * @defaultValue false - * Sets the drawer as inline - */ - @attr({ mode: 'boolean' }) - public inline?: boolean = false; - /**"" * @public * @defaultValue start @@ -121,30 +98,6 @@ export class Drawer extends FASTElement { }); }; - /** - * @public - * Method called when the 'inline' attribute changes - */ - public inlineChanged(oldValue: boolean, newValue: boolean): void { - if (this.$fastController.isConnected) { - if (newValue) { - this.validateConfiguration(); - } - } - } - - /** - * @public - * Method called when the 'type' attribute changes - */ - public typeChanged(oldValue: boolean, newValue: boolean): void { - if (this.$fastController.isConnected) { - if (newValue != oldValue) { - this.validateConfiguration(); - } - } - } - /** * @public * Method to show the drawer @@ -152,7 +105,7 @@ export class Drawer extends FASTElement { public show(): void { Updates.enqueue(() => { this.emitBeforeToggle(); - if (this.inline) { + if (this.type === DrawerType.inline) { this.dialog.show(); } else { this.dialog.showModal(); @@ -184,14 +137,4 @@ export class Drawer extends FASTElement { } return true; } - - /** - * Validates the configuration of the drawer. - * @throws {Error} Throws an error if the configuration is invalid. - */ - private validateConfiguration(): void { - if (this.inline && this.type !== DrawerType.nonModal) { - throw new Error('Invalid configuration: inline requires the type to be nonModal'); - } - } } From 6ac2483c8ad35865a84dbdda0980601f1da04d1d Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Tue, 11 Jun 2024 13:10:00 -0700 Subject: [PATCH 17/21] updates role attr --- packages/web-components/src/drawer/drawer.stories.ts | 4 ++-- packages/web-components/src/drawer/drawer.template.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/web-components/src/drawer/drawer.stories.ts b/packages/web-components/src/drawer/drawer.stories.ts index cdd78e6b535d90..bb6462f16a0e68 100644 --- a/packages/web-components/src/drawer/drawer.stories.ts +++ b/packages/web-components/src/drawer/drawer.stories.ts @@ -365,7 +365,7 @@ export const NonModal = renderComponent(html` export const Inline = renderComponent(html`
- + Drawer Inline ` >Toggle Drawer
- + Drawer Inline (): ElementViewTemplate { Date: Tue, 11 Jun 2024 14:18:29 -0700 Subject: [PATCH 18/21] sets nonModal drawer to open via dialog.show --- packages/web-components/src/drawer/drawer.styles.ts | 4 ++++ packages/web-components/src/drawer/drawer.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/web-components/src/drawer/drawer.styles.ts b/packages/web-components/src/drawer/drawer.styles.ts index dfbea798de4888..b84cd13fdc7c8e 100644 --- a/packages/web-components/src/drawer/drawer.styles.ts +++ b/packages/web-components/src/drawer/drawer.styles.ts @@ -31,6 +31,10 @@ export const styles = css` display: none; } + :host([type='non-modal']) dialog { + position: fixed; + } + :host([type='inline']) { height: 100%; width: fit-content; diff --git a/packages/web-components/src/drawer/drawer.ts b/packages/web-components/src/drawer/drawer.ts index 46d9575915433e..e6b0332ab9ce7a 100644 --- a/packages/web-components/src/drawer/drawer.ts +++ b/packages/web-components/src/drawer/drawer.ts @@ -105,7 +105,7 @@ export class Drawer extends FASTElement { public show(): void { Updates.enqueue(() => { this.emitBeforeToggle(); - if (this.type === DrawerType.inline) { + if (this.type === DrawerType.inline || this.type === DrawerType.nonModal) { this.dialog.show(); } else { this.dialog.showModal(); From 72fd34655470df5cfd43f9aed14de3d9b18d0c51 Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Tue, 11 Jun 2024 14:39:17 -0700 Subject: [PATCH 19/21] drawer: style fix --- packages/web-components/src/drawer/drawer.styles.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/web-components/src/drawer/drawer.styles.ts b/packages/web-components/src/drawer/drawer.styles.ts index b84cd13fdc7c8e..52ed09f7430e1d 100644 --- a/packages/web-components/src/drawer/drawer.styles.ts +++ b/packages/web-components/src/drawer/drawer.styles.ts @@ -33,6 +33,8 @@ export const styles = css` :host([type='non-modal']) dialog { position: fixed; + top: 0; + bottom: 0; } :host([type='inline']) { @@ -40,7 +42,7 @@ export const styles = css` width: fit-content; } - :host([type='inline']) dialog { + :host([type='inline']) dialog[open] { box-shadow: none; position: relative; } From 2f343f91055ca0b03b7b329f5a64471885c0722b Mon Sep 17 00:00:00 2001 From: Brian Brady Date: Tue, 11 Jun 2024 15:22:35 -0700 Subject: [PATCH 20/21] drawer: cleans up storybook, adjusts styles --- .../src/drawer-body/drawer-body.styles.ts | 3 +- .../src/drawer/drawer.stories.ts | 106 ++++++------------ .../src/drawer/drawer.styles.ts | 10 +- 3 files changed, 36 insertions(+), 83 deletions(-) diff --git a/packages/web-components/src/drawer-body/drawer-body.styles.ts b/packages/web-components/src/drawer-body/drawer-body.styles.ts index 89c03f36899ccd..e487cc683d0aee 100644 --- a/packages/web-components/src/drawer-body/drawer-body.styles.ts +++ b/packages/web-components/src/drawer-body/drawer-body.styles.ts @@ -9,7 +9,8 @@ import { typographySubtitle1Styles } from '../styles/partials/typography.partial export const styles = css` ${display('grid')} :host { - box-sizing: boder-box; + box-sizing: border-box; + display: grid; grid-template-rows: min-content auto min-content; position: relative; height: 100%; diff --git a/packages/web-components/src/drawer/drawer.stories.ts b/packages/web-components/src/drawer/drawer.stories.ts index bb6462f16a0e68..cb9eade68fe5ef 100644 --- a/packages/web-components/src/drawer/drawer.stories.ts +++ b/packages/web-components/src/drawer/drawer.stories.ts @@ -282,6 +282,8 @@ export const Modal = renderComponent(html` is the default type of the component.

+
+
type="modal" @@ -304,8 +306,6 @@ export const Modal = renderComponent(html` default type of the component.

-
-
type="modal" @@ -337,7 +337,6 @@ export const NonModal = renderComponent(html`


-
type="non-modal" @@ -364,33 +363,34 @@ export const NonModal = renderComponent(html` `); export const Inline = renderComponent(html` -
- - - Drawer Inline - - ${dismissed20Regular} - - -

- An inline Drawer is often used for navigation that is not dismissible. As it is on the same level as the - main surface, users can still interact with other UI elements. This could be useful for swapping between - different items in the main surface. -

-
-
-
- - type="inline" - -
-
+
+
+ + + Drawer Inline + + ${dismissed20Regular} + + +

+ An inline Drawer is often used for navigation that is not dismissible. As it is on the same level as the + main surface, users can still interact with other UI elements. This could be useful for swapping between + different items in the main surface. +

+
+
+ + type="inline" + +
+
+

Inline

@@ -400,47 +400,11 @@ export const Inline = renderComponent(html` items in the main surface.

-
-
type="inline" - Select a Drawer Position - - Start - End - - Toggle Drawer + Toggle Drawer
- - - Drawer Inline - - ${dismissed20Regular} - - -

- An inline Drawer is often used for navigation that is not dismissible. As it is on the same level as the - main surface, users can still interact with other UI elements. This could be useful for swapping between - different items in the main surface. -

-
-
-
- - - type="inline" - -
-
`); @@ -501,7 +465,7 @@ export const Position = renderComponent(html` >
- + Drawer position end `


-
position="end" @@ -574,7 +537,6 @@ export const Size = renderComponent(html`

The size attribute controls the width of the drawer. The default is medium.


-
size="small" @@ -596,7 +558,6 @@ export const Size = renderComponent(html`

The size attribute controls the width of the drawer. The default is medium.


-
default @@ -656,8 +617,6 @@ export const CustomSize = renderComponent(html`

The Drawer can be sized to any custom width, by overriding the drawer-width CSS variable:

-
-
var(--drawer-width) @@ -683,6 +642,7 @@ export const CustomSize = renderComponent(html`

The Drawer can be sized to any custom width, by overriding the drawer-width CSS variable:


+
var(--drawer-width) diff --git a/packages/web-components/src/drawer/drawer.styles.ts b/packages/web-components/src/drawer/drawer.styles.ts index 52ed09f7430e1d..3ee4275c68c382 100644 --- a/packages/web-components/src/drawer/drawer.styles.ts +++ b/packages/web-components/src/drawer/drawer.styles.ts @@ -68,7 +68,7 @@ export const styles = css` } dialog { - display: none; + box-sizing: border-box; z-index: var(--drawer-elevation, 1000); font-size: ${fontSizeBase300}; line-height: ${lineHeightBase300}; @@ -82,9 +82,6 @@ export const styles = css` margin-inline-end: auto; border-inline-end-color: ${colorTransparentStroke}; border-inline-start-color: var(--drawer-separator, ${colorTransparentStroke}); - } - - dialog[open] { outline: none; top: 0; bottom: 0; @@ -93,9 +90,6 @@ export const styles = css` padding: 0; max-width: var(--drawer-width, 592px); box-shadow: ${shadow64}; - box-sizing: border-box; - display: flex; - flex-direction: column; border: ${strokeWidthThin} solid ${colorTransparentStroke}; background: ${colorNeutralBackground1}; } @@ -132,11 +126,9 @@ export const styles = css` transition-duration: ${durationGentle}; transition-timing-function: ${curveDecelerateMid}; background: var(--dialog-backdrop, ${colorBackgroundOverlay}); - /* Set opacity to 0 when closed */ opacity: 0; } - /* Set opacity to 1 when open */ dialog[open]::backdrop { opacity: 1; } From 0cacc13aae710a781a16dd7d67ff4d12bf08efff Mon Sep 17 00:00:00 2001 From: BrdyBrn Date: Thu, 13 Jun 2024 15:18:02 -0700 Subject: [PATCH 21/21] Update packages/web-components/src/drawer-body/drawer-body.styles.ts Co-authored-by: Dave Rupert --- packages/web-components/src/drawer-body/drawer-body.styles.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/web-components/src/drawer-body/drawer-body.styles.ts b/packages/web-components/src/drawer-body/drawer-body.styles.ts index e487cc683d0aee..173aacab542f30 100644 --- a/packages/web-components/src/drawer-body/drawer-body.styles.ts +++ b/packages/web-components/src/drawer-body/drawer-body.styles.ts @@ -10,7 +10,6 @@ export const styles = css` ${display('grid')} :host { box-sizing: border-box; - display: grid; grid-template-rows: min-content auto min-content; position: relative; height: 100%;