diff --git a/packages/web/src/scss/components/Drawer/README.md b/packages/web/src/scss/components/Drawer/README.md new file mode 100644 index 0000000000..0d52d3e255 --- /dev/null +++ b/packages/web/src/scss/components/Drawer/README.md @@ -0,0 +1,87 @@ +# Drawer + +The Drawer component is a container that slides in from side of the screen. It can be used to display additional content or actions that are not part of the main view. + +The Drawer is a composition of several subcomponents: + +- [Drawer](#drawer) + - [DrawerCloseButton](#drawerclosebutton) + - [DrawerPanel](#drawerpanel) + +👉 The animation effect of this component is dependent on the +`prefers-reduced-motion` media query. + +## Drawer + +```html + + + +``` + +### Alignment + +The `Drawer` component allows aligning the content panel horizontally to the left or right side of the screen using `--left` or `--right` modifier. The default alignment of the drawer content panel is to the right. + +```html + + + +``` + +## DrawerCloseButton + +The `DrawerCloseButton` component is a button that closes the drawer when clicked. + +```html + +``` + +## DrawerPanel + +The `DrawerPanel` component is a container for the content that will be displayed in the drawer. + +```html +
+
+ +
+
+``` + +## Full Example + +```html + +
+
+ + +
+
+
+``` diff --git a/packages/web/src/scss/components/Drawer/_Drawer.scss b/packages/web/src/scss/components/Drawer/_Drawer.scss new file mode 100644 index 0000000000..0d48c6fab2 --- /dev/null +++ b/packages/web/src/scss/components/Drawer/_Drawer.scss @@ -0,0 +1,49 @@ +@use '../../tools/typography'; +@use '@tokens' as tokens; +@use 'theme'; + +.Drawer { + @include typography.generate(theme.$drawer-typography); + + --#{tokens.$css-variable-prefix}drawer-translate-x: 0%; + + all: unset; + position: fixed; + inset: 0; + z-index: 1; + display: flex; + width: 100%; + max-width: none; + height: 100%; + max-height: none; + border: none; + background-color: theme.$drawer-backdrop-background-color; + visibility: hidden; + + &::backdrop { + background-color: transparent; + } + + @media (prefers-reduced-motion: no-preference) { + transition-property: visibility, opacity; + transition-duration: theme.$drawer-transition-duration; + } +} + +.Drawer--left { + --#{tokens.$css-variable-prefix}drawer-translate-x: -100%; + + justify-content: start; +} + +.Drawer--right { + --#{tokens.$css-variable-prefix}drawer-translate-x: 100%; + + justify-content: end; +} + +.Drawer[open] { + --#{tokens.$css-variable-prefix}drawer-translate-x: 0%; + + visibility: visible; +} diff --git a/packages/web/src/scss/components/Drawer/_DrawerCloseButton.scss b/packages/web/src/scss/components/Drawer/_DrawerCloseButton.scss new file mode 100644 index 0000000000..247f29698d --- /dev/null +++ b/packages/web/src/scss/components/Drawer/_DrawerCloseButton.scss @@ -0,0 +1,11 @@ +@use '../../tools/accessibility'; +@use '../../tools/reset'; +@use 'theme'; + +.DrawerCloseButton { + @include accessibility.min-tap-target(theme.$drawer-close-button-size); + + align-self: flex-end; + margin-inline-end: theme.$drawer-close-button-padding-x; + margin-block-start: theme.$drawer-close-button-padding-y; +} diff --git a/packages/web/src/scss/components/Drawer/_DrawerPanel.scss b/packages/web/src/scss/components/Drawer/_DrawerPanel.scss new file mode 100644 index 0000000000..b2d62dc884 --- /dev/null +++ b/packages/web/src/scss/components/Drawer/_DrawerPanel.scss @@ -0,0 +1,29 @@ +@use '@tokens' as tokens; +@use 'theme'; + +.DrawerPanel { + display: flex; + flex-direction: column; + box-sizing: border-box; + width: var(--#{tokens.$css-variable-prefix}drawer-panel-width, #{theme.$drawer-panel-width}); + height: theme.$drawer-panel-height; + max-height: theme.$drawer-panel-max-height; + color: theme.$drawer-panel-text-color; + background-color: theme.$drawer-panel-background-color; + box-shadow: theme.$drawer-panel-shadow; + transform: translateX(var(--#{tokens.$css-variable-prefix}drawer-translate-x)); + + @media (prefers-reduced-motion: no-preference) { + transition-property: transform; + transition-duration: theme.$drawer-transition-duration; + transition-timing-function: theme.$drawer-transition-timing; + } +} + +.DrawerPanel__content { + display: flex; + flex-grow: 1; + flex-direction: column; + overflow-y: auto; + overscroll-behavior: contain; +} diff --git a/packages/web/src/scss/components/Drawer/_theme.scss b/packages/web/src/scss/components/Drawer/_theme.scss new file mode 100644 index 0000000000..25b2e80137 --- /dev/null +++ b/packages/web/src/scss/components/Drawer/_theme.scss @@ -0,0 +1,22 @@ +@use '@tokens' as tokens; +@use '../../settings/transitions'; + +$drawer-typography: tokens.$body-medium-semibold; + +// Transition +$drawer-transition-duration: transitions.$duration-200; +$drawer-transition-timing: transitions.$timing-eased-in-out; +$drawer-backdrop-background-color: tokens.$background-backdrop; + +// DrawerPanel +$drawer-panel-width: 288px; +$drawer-panel-height: auto; +$drawer-panel-max-height: none; +$drawer-panel-text-color: tokens.$text-primary; +$drawer-panel-background-color: tokens.$background-primary; +$drawer-panel-shadow: tokens.$shadow-400; + +// DrawerCloseButton +$drawer-close-button-size: tokens.$space-1200; +$drawer-close-button-padding-x: tokens.$space-900; +$drawer-close-button-padding-y: tokens.$space-700; diff --git a/packages/web/src/scss/components/Drawer/index.html b/packages/web/src/scss/components/Drawer/index.html new file mode 100644 index 0000000000..c6664537b3 --- /dev/null +++ b/packages/web/src/scss/components/Drawer/index.html @@ -0,0 +1,163 @@ +{{#> web/layout/default title="Drawer" parentPageName="Components" }} + + + + +
+ +
+

Drawer

+ +
+ +
+
+ Drawer alignment: + + +
+
+
+ + +
+ + +
+ Can contain HTML. +
+
+
+
+
+ + + + + +
+
+ +
This is a Drawer content.
+
+
+
+ + +
+
+
+ +{{/web/layout/default }} diff --git a/packages/web/src/scss/components/Drawer/index.scss b/packages/web/src/scss/components/Drawer/index.scss new file mode 100644 index 0000000000..bc5d315cb6 --- /dev/null +++ b/packages/web/src/scss/components/Drawer/index.scss @@ -0,0 +1,3 @@ +@forward 'Drawer'; +@forward 'DrawerPanel'; +@forward 'DrawerCloseButton'; diff --git a/packages/web/src/scss/components/index.scss b/packages/web/src/scss/components/index.scss index a12da9416a..495e9fc223 100644 --- a/packages/web/src/scss/components/index.scss +++ b/packages/web/src/scss/components/index.scss @@ -8,6 +8,7 @@ @forward 'Collapse'; @forward 'Container'; @forward 'Divider'; +@forward 'Drawer'; @forward 'Dropdown'; @forward 'FieldGroup'; @forward 'FileUploader'; diff --git a/tests/e2e/components/demo-drawer-compare.spec.ts b/tests/e2e/components/demo-drawer-compare.spec.ts new file mode 100644 index 0000000000..4a2c02ccc2 --- /dev/null +++ b/tests/e2e/components/demo-drawer-compare.spec.ts @@ -0,0 +1,90 @@ +/* eslint-disable no-console -- we want to log when test fails */ +import { test, Page, expect } from '@playwright/test'; +import { formatPackageName, getServerUrl, hideFromVisualTests, waitForPageLoad, takeScreenshot } from '../../helpers'; + +type TestConfig = { + componentsDir: string; + packageName: string; + componentName: string; +}; + +const runComponentCompareTests = ({ componentsDir, packageName, componentName }: TestConfig): void => { + if (!packageName) return; + + const formattedPackageName = formatPackageName(packageName); + + test.describe(`Test opened Drawer`, () => { + test(`Test ${componentName} component in ${formattedPackageName} package`, async ({ page }: { page: Page }) => { + try { + const url = getServerUrl(packageName); + await page.goto(`${url}${componentsDir}/${componentName}/`); + await waitForPageLoad(page); + await hideFromVisualTests(page); + await runDrawerTests(page, componentName); + } catch (error) { + console.error(`Test for demo ${formattedPackageName} component ${componentName} failed. ${error}`); + throw error; + } + }); + }); +}; + +const runDrawerTests = async (page: Page, componentName: string): Promise => { + // open drawer on the right side, close with backdrop click + await page.click('[data-testid="drawer-open-button"]'); + await takeScreenshot(page, `${componentName}-right-backdrop-click-`); + await page.locator('body').click({ position: { x: 0, y: 0 } }); + await expect(page.getByTestId('drawer-panel')).not.toBeVisible(); + + // open drawer on the left side, close with backdrop click + await page.click('[id="drawer-alignment-left"]'); + await page.click('[data-testid="drawer-open-button"]'); + await takeScreenshot(page, `${componentName}-left-backdrop-click`); + await page.locator('body').click({ position: { x: 300, y: 0 } }); + await expect(page.getByTestId('drawer-panel')).not.toBeVisible(); + + // open drawer on the right side, close with close button + await page.click('[id="drawer-alignment-right"]'); + await page.click('[data-testid="drawer-open-button"]'); + await expect(page.getByTestId('drawer-panel')).toBeVisible(); + await page.click('dialog[open] button'); + await expect(page.getByTestId('drawer-panel')).not.toBeVisible(); + + // open drawer on the left side, close with close button + await page.click('[id="drawer-alignment-left"]'); + await page.click('[data-testid="drawer-open-button"]'); + await expect(page.getByTestId('drawer-panel')).toBeVisible(); + await page.click('dialog[open] button'); + await expect(page.getByTestId('drawer-panel')).not.toBeVisible(); + + // open drawer on the right side, close with escape keys + await page.click('[id="drawer-alignment-right"]'); + await page.click('[data-testid="drawer-open-button"]'); + await expect(page.getByTestId('drawer-panel')).toBeVisible(); + await page.keyboard.press('Escape'); + await expect(page.getByTestId('drawer-panel')).not.toBeVisible(); + + // open drawer on the left side, close with escape keys + await page.click('[id="drawer-alignment-left"]'); + await page.click('[data-testid="drawer-open-button"]'); + await expect(page.getByTestId('drawer-panel')).toBeVisible(); + await page.keyboard.press('Escape'); + await expect(page.getByTestId('drawer-panel')).not.toBeVisible(); +}; + +const componentName = 'Drawer'; + +const testConfigs: TestConfig[] = [ + { + componentName, + componentsDir: '/src/scss/components', + packageName: 'web', + }, + { + componentName, + componentsDir: '/src/components', + packageName: 'web-react', + }, +]; + +testConfigs.forEach(runComponentCompareTests); diff --git a/tests/e2e/components/demo-drawer-compare.spec.ts-snapshots/drawer-left-backdrop-click-chromium-linux.png b/tests/e2e/components/demo-drawer-compare.spec.ts-snapshots/drawer-left-backdrop-click-chromium-linux.png new file mode 100644 index 0000000000..f114922776 Binary files /dev/null and b/tests/e2e/components/demo-drawer-compare.spec.ts-snapshots/drawer-left-backdrop-click-chromium-linux.png differ diff --git a/tests/e2e/components/demo-drawer-compare.spec.ts-snapshots/drawer-right-backdrop-click--chromium-linux.png b/tests/e2e/components/demo-drawer-compare.spec.ts-snapshots/drawer-right-backdrop-click--chromium-linux.png new file mode 100644 index 0000000000..97e2a0001e Binary files /dev/null and b/tests/e2e/components/demo-drawer-compare.spec.ts-snapshots/drawer-right-backdrop-click--chromium-linux.png differ diff --git a/tests/e2e/demo-components-compare.spec.ts-snapshots/drawer-chromium-linux.png b/tests/e2e/demo-components-compare.spec.ts-snapshots/drawer-chromium-linux.png new file mode 100644 index 0000000000..e089f47f97 Binary files /dev/null and b/tests/e2e/demo-components-compare.spec.ts-snapshots/drawer-chromium-linux.png differ diff --git a/tests/e2e/demo-homepages.spec.ts-snapshots/web-chromium-linux.png b/tests/e2e/demo-homepages.spec.ts-snapshots/web-chromium-linux.png index fc0ab9c319..c19da41995 100644 Binary files a/tests/e2e/demo-homepages.spec.ts-snapshots/web-chromium-linux.png and b/tests/e2e/demo-homepages.spec.ts-snapshots/web-chromium-linux.png differ diff --git a/tests/e2e/demo-homepages.spec.ts-snapshots/web-react-chromium-linux.png b/tests/e2e/demo-homepages.spec.ts-snapshots/web-react-chromium-linux.png index bc0ca1f25e..09d47d21a4 100644 Binary files a/tests/e2e/demo-homepages.spec.ts-snapshots/web-react-chromium-linux.png and b/tests/e2e/demo-homepages.spec.ts-snapshots/web-react-chromium-linux.png differ