Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(web-components): refactor fluent-dialog and add new fluent-dialog-body component #31512

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
chrisdholt marked this conversation as resolved.
Show resolved Hide resolved
"type": "prerelease",
"comment": "Refactor fluent-dialog and add fluent-dialog-body",
"packageName": "@fluentui/web-components",
"email": "rupertdavid@microsoft.com",
"dependentChangeType": "patch"
}
5 changes: 5 additions & 0 deletions packages/web-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
"types": "./dist/dts/dialog/define.d.ts",
"default": "./dist/esm/dialog/define.js"
},
"./dialog-body.js": {
"types": "./dist/dts/dialog-body/define.d.ts",
"default": "./dist/esm/dialog-body/define.js"
},
"./divider.js": {
"types": "./dist/dts/divider/define.d.ts",
"default": "./dist/esm/divider/define.js"
Expand Down Expand Up @@ -156,6 +160,7 @@
"./dist/esm/compound-button/define.js",
"./dist/esm/counter-badge/define.js",
"./dist/esm/dialog/define.js",
"./dist/esm/dialog-body/define.js",
"./dist/esm/divider/define.js",
"./dist/esm/image/define.js",
"./dist/esm/label/define.js",
Expand Down
8 changes: 8 additions & 0 deletions packages/web-components/src/dialog-body/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### **Slots**

| Name | Description |
| -------------- | ---------------------------------------------------------- |
| `title` | slot for title content |
| `title-action` | slot for close button |
| | default slot for content rendered between title and footer |
| `action` | slot for actions content |
4 changes: 4 additions & 0 deletions packages/web-components/src/dialog-body/define.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { FluentDesignSystem } from '../fluent-design-system.js';
import { definition } from './dialog-body.definition.js';

definition.define(FluentDesignSystem.registry);
davatron5000 marked this conversation as resolved.
Show resolved Hide resolved
14 changes: 14 additions & 0 deletions packages/web-components/src/dialog-body/dialog-body.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { FluentDesignSystem } from '../fluent-design-system.js';
import { definition } from './dialog-body.definition.js';

definition.define(FluentDesignSystem.registry);

const itemRenderer = () => {
const dialogBody = document.createElement('fluent-dialog-body');
dialogBody.appendChild(document.createTextNode('DialogBody'));

return dialogBody;
};

export default itemRenderer;
export { tests } from '../utils/benchmark-wrapper.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { FluentDesignSystem } from '../fluent-design-system.js';
import { DialogBody } from './dialog-body.js';
import { template } from './dialog-body.template.js';
import { styles } from './dialog-body.styles.js';

/**
* The Fluent Dialog Body Element
*
* @public
* @remarks
* HTML Element: \<fluent-dialog-body\>
*/
export const definition = DialogBody.compose({
name: `${FluentDesignSystem.prefix}-dialog-body`,
template,
styles,
});
68 changes: 68 additions & 0 deletions packages/web-components/src/dialog-body/dialog-body.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { expect, test } from '@playwright/test';
import { fixtureURL } from '../helpers.tests.js';
import type { Dialog } from '../dialog/dialog.js';
import type { DialogBody } from './dialog-body.js';

test.describe('Dialog Body', () => {
test.beforeEach(async ({ page }) => {
await page.goto(fixtureURL('components-dialog-dialog-body--default'));

await page.waitForFunction(() =>
Promise.all([
customElements.whenDefined('fluent-button'),
customElements.whenDefined('fluent-dialog'),
customElements.whenDefined('fluent-dialog-body'),
]),
);
});

test('should render a dialog body', async ({ page }) => {
const element = page.locator('fluent-dialog-body');
const closeButton = element.locator('.title-action');

await page.setContent(/* html */ `
<fluent-dialog-body>
<div id="content">content</div>
</fluent-dialog-body>
`);

await expect(element).toBeVisible();
await expect(closeButton).toBeHidden();
});

test('should add default close button for non-modal dialogs', async ({ page }) => {
const element = page.locator('fluent-dialog-body');
const closeButton = element.locator('.title-action');
const dialog = page.locator('fluent-dialog');
const content = element.locator('#content');

await page.setContent(/* html */ `
<fluent-dialog type="non-modal">
<fluent-dialog-body>
<div id="content">content</div>
</fluent-dialog-body>
</fluent-dialog>
`);

await test.step('should show the default close button in title for non-modal dialog', async () => {
await expect(content).toBeHidden();

dialog.evaluate((node: Dialog) => {
node.show();
});

await expect(content).toBeVisible();

// Shows the close button in title in non-modal dialog
await expect(closeButton).toBeVisible();
});

await test.step('should hide the close button when noTitleAction is set', async () => {
await element.evaluate((node: DialogBody) => {
node.noTitleAction = true;
});

await expect(closeButton).toBeHidden();
});
});
});
172 changes: 172 additions & 0 deletions packages/web-components/src/dialog-body/dialog-body.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { html } from '@microsoft/fast-element';
import type { Args } from '@storybook/html';
import { renderComponent } from '../helpers.stories.js';
import { Dialog as FluentDialog } from '../dialog/dialog.js';
import type { DialogBody as FluentDialogBody } from './dialog-body.js';
import './define.js';
import '../button/define.js';
import '../text/define.js';

type DialogStoryArgs = Args & FluentDialogBody;

const dismissed20Regular = html<DialogStoryArgs>`
<svg
fill="currentColor"
aria-hidden="true"
width="20"
height="20"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m4.09 4.22.06-.07a.5.5 0 0 1 .63-.06l.07.06L10 9.29l5.15-5.14a.5.5 0 0 1 .63-.06l.07.06c.18.17.2.44.06.63l-.06.07L10.71 10l5.14 5.15c.18.17.2.44.06.63l-.06.07a.5.5 0 0 1-.63.06l-.07-.06L10 10.71l-5.15 5.14a.5.5 0 0 1-.63.06l-.07-.06a.5.5 0 0 1-.06-.63l.06-.07L9.29 10 4.15 4.85a.5.5 0 0 1-.06-.63l.06-.07-.06.07Z"
fill="currentColor"
></path>
</svg>
`;

const dismissCircle20Regular = html`<svg
fill="currentColor"
aria-hidden="true"
width="20"
height="20"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 2a8 8 0 1 1 0 16 8 8 0 0 1 0-16Zm0 1a7 7 0 1 0 0 14 7 7 0 0 0 0-14ZM7.8 7.11l.08.06L10 9.3l2.12-2.12a.5.5 0 0 1 .64-.06l.07.06c.17.18.2.44.06.64l-.06.07L10.7 10l2.12 2.12c.17.17.2.44.06.64l-.06.07a.5.5 0 0 1-.64.06l-.07-.06L10 10.7l-2.12 2.12a.5.5 0 0 1-.64.06l-.07-.06a.5.5 0 0 1-.06-.64l.06-.07L9.3 10 7.17 7.88a.5.5 0 0 1-.06-.64l.06-.07a.5.5 0 0 1 .64-.06Z"
fill="currentColor"
></path>
</svg>`;

const dialogTemplate = html<DialogStoryArgs>`
<fluent-dialog-body>
<div slot="title">This is a Dialog title</div>
<fluent-text weight="regular" block>
<p>
The dialog component is a window overlaid on either the primary window or another dialog window. Windows under
a modal dialog are inert. That is, users cannot <a href="#">interact</a> with content outside an active dialog
window.
</p>
</fluent-text>
<br />
<fluent-text block><code>fluent-dialog</code></fluent-text>
<fluent-button
slot="actions"
id="dialog-default-close"
>
Close Dialog
</fluent-button>
</fluent-dialog>
`;

export default {
title: 'Components/Dialog/Dialog Body',
argTypes: {
noTitleAction: {
description:
'Used to opt out of rendering the default title action that is rendered when the dialog <code>type</code>is set to <code>non-modal</code>',
table: {
defaultValue: { summary: false },
},
control: {
type: 'boolean',
},
defaultValue: false,
},
titleAction: {
description:
'Slot for the title action elements (e.g. Close button). When the dialog <code>type</code> is set to <code>non-modal</code> and no title action is provided, a default title action button is rendered.',
},
defaultTitleAction: {
description: 'The default title action button',
},
},
};

export const Default = renderComponent(dialogTemplate).bind({});

export const Basic = renderComponent(html<DialogStoryArgs>`
<fluent-dialog-body>
<div slot="title">Basic</div>
<fluent-text block>
<p>
A dialog should have no more than <fluent-text weight="bold"><span>two</span></fluent-text>
actions.
</p>
</fluent-text>
<fluent-text block>
<p>However, if required, you can populate the action slot with any number of buttons as needed.</p>
</fluent-text>
<br />
<fluent-text block><code>slot="action"</code></fluent-text>
<fluent-button slot="action">Close Dialog</fluent-button>
<fluent-button appearance="primary" slot="action">Call to Action</fluent-button>
</fluent-dialog-body>
`);

export const Actions = renderComponent(html<DialogStoryArgs>`
<fluent-dialog-body id="dialog-fluidactions">
<div slot="title">Actions</div>
<fluent-button appearance="transparent" icon-only slot="title-action"> ${dismissed20Regular} </fluent-button>
<div>
<fluent-text block>
<p>
A dialog body should have no more than <strong>two</strong> footer actions. However, if required, you can
populate the action slot with any number of buttons as needed.
</p>
</fluent-text>
<fluent-text block><code>slot="action"</code></fluent-text>
</div>

<fluent-button size="small" slot="action">Something</fluent-button>
<fluent-button size="small" slot="action">Something Else</fluent-button>

<fluent-button slot="action" size="small" appearance="primary">Close Dialog</fluent-button>
<fluent-button size="small" slot="action">Something Else Entirely</fluent-button>
</fluent-dialog-body>
`);

export const NoTitleAction = renderComponent(html<DialogStoryArgs>`
<fluent-dialog-body no-title-action>
<div slot="title">No Title Action</div>
<fluent-text block>
<p>Removing the title action will prevent the default close button from being rendered in a non-modal dialog.</p>
</fluent-text>
<br />
<fluent-text block><code>no-title-action</code></fluent-text>
</fluent-dialog-body>
`);

export const CustomTitleAction = renderComponent(html<DialogStoryArgs>`
<fluent-dialog-body>
<div slot="title">Custom Title Action</div>
<fluent-button
slot="title-action"
appearance="transparent"
icon-only
@click="${(e: Event, c) => alert('This is a custom action')}"
>
${dismissCircle20Regular}
</fluent-button>
<fluent-text block>
<p>
A dialog should have no more than <fluent-text weight="bold"><span>two</span></fluent-text> actions.
</p>
</fluent-text>
<fluent-text block><code>slot="title-action"</code></fluent-text>
<fluent-button slot="action">Close Dialog</fluent-button>
</fluent-dialog-body>
`);

export const NoTitleAndNoAction = renderComponent(html<DialogStoryArgs>`
<fluent-dialog-body no-title-action>
<fluent-text block>
<p>
A dialog should have no more than <fluent-text weight="bold"><span>two</span></fluent-text> actions.
</p>
</fluent-text>
<br />
<fluent-text block><code>no-title-action</code></fluent-text>
</fluent-dialog-body>
`);
Loading
Loading