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

Adds MessageBar component to FluentUI #31708

Merged
merged 40 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
f0db23b
Adds MessageBar component to FluentUI
procload Jun 13, 2024
f74c0ce
Merge branch 'master' into users/procload/messageBarComponent
brianchristopherbrady Jun 14, 2024
cc0a9df
Udpates slots for rendering status icons in MessageBar
procload Jun 14, 2024
147d1ca
More updates to the template
procload Jun 14, 2024
e93c76c
Adds ElementInternals for role on MessageBar
procload Jun 14, 2024
54fa025
remove role=status from template
procload Jun 14, 2024
94fde4b
Adds message-bar benchmark file
procload Jun 14, 2024
88eac78
Removes politeness attribute
procload Jun 14, 2024
8465577
Simplifies template DOM
procload Jun 14, 2024
766f28f
Fixes message bar
procload Jun 17, 2024
aabea29
Merge branch 'master' into users/procload/messageBarComponent
procload Jun 17, 2024
d24a6eb
Merge branch 'microsoft:master' into users/procload/messageBarComponent
procload Jun 19, 2024
91c8861
Removes inlined SVGs
procload Jun 19, 2024
f957735
Improves Storybook stories
procload Jun 19, 2024
9e851ad
Continues to simplify MessageBar template
procload Jun 19, 2024
57ea9b0
Updates MessageBar benchmark template
procload Jun 19, 2024
b4061b5
Updates states instead of attrs in CSS
procload Jun 19, 2024
b07f390
Adds info icon to final example
procload Jun 19, 2024
b2226e6
Begins to address PR feedback
procload Jun 21, 2024
bae9779
further PR feedback
procload Jun 21, 2024
b47a67f
Adds LabelledBy example Story
procload Jun 21, 2024
9881e9b
Add Story example of label with labelledby
procload Jun 21, 2024
f0461d2
Fixes overlapping actions in MessageBar
procload Jun 24, 2024
cee8a10
Adds contain and fixes CSS selector
procload Jun 24, 2024
604d0de
Changes close -> dismiss in MessageBar
procload Jun 24, 2024
a3958db
One more rename
procload Jun 24, 2024
f7a30cc
Another close -> dismiss
procload Jun 24, 2024
4a0d1cf
Adds CSS logical propertiy to support internationlization
procload Jun 25, 2024
31d8143
Merge branch 'master' into users/procload/messageBarComponent
procload Jun 25, 2024
49b8e48
Addresses benchmark comments
procload Jun 25, 2024
143d154
Revert "Addresses benchmark comments"
procload Jun 25, 2024
0fb8522
Fixes README template
procload Jun 25, 2024
cd24eaf
Attempts to fix message-bar.bench.ts
procload Jun 25, 2024
3e0dde5
Fixes capitalization
procload Jun 25, 2024
4a4ded0
Merge branch 'master' into users/procload/messageBarComponent
procload Jun 26, 2024
5eec5e0
Adjusts CSS to account for spacing
procload Jun 27, 2024
904738f
Fixes misalignment
procload Jun 27, 2024
76814c1
Applies style to only multiline messaage bars
procload Jun 27, 2024
d8f10c3
Merge branch 'master' into users/procload/messageBarComponent
procload Jun 28, 2024
97fc4cf
Runs Prettier on files
procload Jun 28, 2024
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 @@
{
procload marked this conversation as resolved.
Show resolved Hide resolved
"type": "prerelease",
"comment": "Adds MessageBar component to Fluent Web Components",
"packageName": "@fluentui/web-components",
"email": "[email protected]",
"dependentChangeType": "patch"
}
4 changes: 4 additions & 0 deletions packages/web-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@
"types": "./dist/dts/menu-item/define.d.ts",
"default": "./dist/esm/menu-item/define.js"
},
"./message-bar.js": {
"types": "./dist/dts/message-bar/define.d.ts",
"default": "./dist/esm/message-bar/define.js"
},
"./progress-bar.js": {
"types": "./dist/dts/progress-bar/define.d.ts",
"default": "./dist/esm/progress-bar/define.js"
Expand Down
1 change: 1 addition & 0 deletions packages/web-components/src/index-rollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import './menu-button/define.js';
import './menu-item/define.js';
import './menu-list/define.js';
import './menu/define.js';
import './message-bar/define.js';
import './progress-bar/define.js';
import './radio-group/define.js';
import './radio/define.js';
Expand Down
4 changes: 4 additions & 0 deletions packages/web-components/src/message-bar/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 './message-bar.definition.js';

definition.define(FluentDesignSystem.registry);
5 changes: 5 additions & 0 deletions packages/web-components/src/message-bar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { definition as MessageBarDefinition } from './message-bar.definition.js';
export { MessageBar } from './message-bar.js';
export { MessageBarIntent, MessageBarLayout, MessageBarPoliteness, MessageBarShape } from './message-bar.options.js';
export { styles as MessageBarStyles } from './message-bar.styles.js';
export { template as MessageBarTemplate } from './message-bar.template.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { FluentDesignSystem } from '../fluent-design-system.js';
import { MessageBar } from './message-bar.js';
import { styles } from './message-bar.styles.js';
import { template } from './message-bar.template.js';

/**
* The Fluent MessageBar Element definition.
*
* @public
* @remarks
* HTML Element: `<fluent-message-bar>`
*/
export const definition = MessageBar.compose({
name: `${FluentDesignSystem.prefix}-message-bar`,
template,
styles,
shadowOptions: {
mode: FluentDesignSystem.shadowRootMode,
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { expect, test } from '@playwright/test';
import type { Locator, Page } from '@playwright/test';

test.describe('MessageBar component', () => {
const componentID = 'components-messagebar--message-bar';
let page: Page;
let element: Locator;

test.beforeAll(async ({ browser }) => {
const context = await browser.newContext();
page = await context.newPage();
root = page.locator('#root');
element = page.locator('fluent-message-bar');
await page.goto(`iframe.html?id=${componentID}`);
});

test.afterAll(async () => {
await page.close();
});

test('should render with default attributes', async () => {
const role = await element.getAttribute('role');
const shape = await element.getAttribute('shape');
const layout = await element.getAttribute('layout');
const intent = await element.getAttribute('intent');
const politeness = await element.getAttribute('aria-live');

expect(role).toEqual('status');
expect(shape).toEqual('rounded');
expect(layout).toEqual('singleline');
expect(intent).toEqual('info');
expect(politeness).toEqual('polite');
});

test('should have correct `aria-labelledby` attribute', async () => {
const attribute = await element.getAttribute('aria-labelledby');
expect(attribute).toBeNull();
});

test('on dismissing the MessageBar, it should emit `dismiss` event', async () => {
const [dismissed] = await Promise.all([
element.evaluate(el => {
return new Promise(resolve => {
el.addEventListener('dismiss', () => resolve(true), {
once: true,
});
(el as any).dismissMessageBar();
});
}),
]);
expect(dismissed).toBeTruthy();
});

test('when the `layout` attribute is changed, it should reflect the new value', async () => {
await element.evaluate(el => el.setAttribute('layout', 'multiline'));
const layout = await element.getAttribute('layout');
expect(layout).toEqual('multiline');
});

test('when the `intent` attribute is changed, it should reflect the new value', async () => {
await element.evaluate(el => el.setAttribute('intent', 'warning'));
const intent = await element.getAttribute('intent');
expect(intent).toEqual('warning');
});

test('when the `shape` attribute is changed, it should reflect the new value', async () => {
await element.evaluate(el => el.setAttribute('shape', 'rectangular'));
const shape = await element.getAttribute('shape');
expect(shape).toEqual('rectangular');
});

test('when the `politeness` attribute is changed, it should reflect the new value', async () => {
await element.evaluate(el => el.setAttribute('politeness', 'polite'));
const politeness = await element.getAttribute('politeness');
expect(politeness).toEqual('polite');
});

test('when the `politeness` attribute is set, it should reflect the value in the aria-live attribute', async () => {
await element.evaluate(el => el.setAttribute('politeness', 'assertive'));
const ariaLive = await element.getAttribute('aria-live');
expect(ariaLive).toEqual('assertive');
});
});
47 changes: 47 additions & 0 deletions packages/web-components/src/message-bar/message-bar.options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { ValuesOf } from '../utils/typings.js';

/**
* @public
* The `layout` variations for the MessageBar component.
*/
export const MessageBarLayout = {
multiline: 'multiline',
singleline: 'singleline',
} as const;

export type MessageBarLayout = ValuesOf<typeof MessageBarLayout>;

/**
* @public
* The `shape` variations for the MessageBar component.
*/
export const MessageBarShape = {
rounded: 'rounded',
square: 'square',
} as const;

export type MessageBarShape = ValuesOf<typeof MessageBarShape>;

/**
* @public
* The `intent` variations for the MessageBar component.
*/
export const MessageBarIntent = {
success: 'success',
warning: 'warning',
error: 'error',
info: 'info',
} as const;

export type MessageBarIntent = ValuesOf<typeof MessageBarIntent>;

/**
* @public
* The `politeness` variations for the MessageBar component.
*/
export const MessageBarPoliteness = {
polite: 'polite',
assertive: 'assertive',
} as const;

export type MessageBarPoliteness = ValuesOf<typeof MessageBarPoliteness>;
158 changes: 158 additions & 0 deletions packages/web-components/src/message-bar/message-bar.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { html } from '@microsoft/fast-element';
import type { Args, Meta } from '@storybook/html';
import { renderComponent } from '../helpers.stories.js';
import { MessageBar as FluentMessageBar } from './message-bar.js';
import { MessageBarIntent, MessageBarLayout, MessageBarPoliteness, MessageBarShape } from './message-bar.options.js';
import './define';

type MessageBarStoryArgs = Args & FluentMessageBar;
type MessageBarStoryMeta = Meta<MessageBarStoryArgs>;

const dismissed20Regular = 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="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 storyTemplate = html<MessageBarStoryArgs>`
<fluent-message-bar
shape="${x => x.shape}"
layout="${x => x.layout}"
intent="${x => x.intent}"
politeness="${x => x.politeness}"
>
${x => x.content}
<div class="actions" slot="actions">
<fluent-button size="small">Action</fluent-button>
<fluent-button size="small">Action</fluent-button>
</div>
<fluent-button size="small" appearance="transparent" icon-only slot="close"> ${dismissed20Regular} </fluent-button>
</fluent-message-bar>
`;

export default {
title: 'Components/MessageBar',
args: {
content: 'This is a message bar that provides information to the user.',
shape: MessageBarShape.rounded,
layout: MessageBarLayout.singleline,
intent: MessageBarIntent.info,
politeness: MessageBarPoliteness.polite,
},
argTypes: {
content: {
description: 'MessageBar content',
control: { type: 'text' },
},
shape: {
description: 'MessageBar shape',
control: { type: 'select' },
options: Object.values(MessageBarShape),
},
layout: {
description: 'MessageBar layout',
control: { type: 'select' },
options: Object.values(MessageBarLayout),
},
intent: {
description: 'MessageBar intent',
control: { type: 'select' },
options: Object.values(MessageBarIntent),
},
politeness: {
description: 'MessageBar politeness',
control: { type: 'select' },
options: Object.values(MessageBarPoliteness),
},
},
} as MessageBarStoryMeta;

export const MessageBar = renderComponent(storyTemplate).bind({}) as any;

export const Shape = renderComponent(html<MessageBarStoryArgs>`
<fluent-message-bar shape="rounded">
rounded
<div class="actions" slot="actions">
<fluent-button size="small">Action</fluent-button>
<fluent-button size="small">Action</fluent-button>
</div>
<fluent-button size="small" appearance="transparent" icon-only slot="close"> ${dismissed20Regular} </fluent-button>
</fluent-message-bar>
<br />
<fluent-message-bar shape="square">
square
<div class="actions" slot="actions">
<fluent-button size="small">Action</fluent-button>
<fluent-button size="small">Action</fluent-button>
</div>
<fluent-button size="small" appearance="transparent" icon-only slot="close"> ${dismissed20Regular} </fluent-button>
</fluent-message-bar>
`);

export const Layout = renderComponent(html<MessageBarStoryArgs>`
<fluent-message-bar layout="singleline">
singleline
<div class="actions" slot="actions">
<fluent-button size="small">Action</fluent-button>
<fluent-button size="small">Action</fluent-button>
</div>
<fluent-button size="small" appearance="transparent" icon-only slot="close"> ${dismissed20Regular} </fluent-button>
</fluent-message-bar>
<br />
<fluent-message-bar layout="multiline">
multiline
<div class="actions" slot="actions">
<fluent-button size="small">Action</fluent-button>
<fluent-button size="small">Action</fluent-button>
</div>
<fluent-button size="small" appearance="transparent" icon-only slot="close"> ${dismissed20Regular} </fluent-button>
</fluent-message-bar>
`);

export const Intent = renderComponent(html<MessageBarStoryArgs>`
<fluent-message-bar intent="info">
info
<div class="actions" slot="actions">
<fluent-button size="small">Action</fluent-button>
<fluent-button size="small">Action</fluent-button>
</div>
<fluent-button size="small" appearance="transparent" icon-only slot="close"> ${dismissed20Regular} </fluent-button>
</fluent-message-bar>
<br />
<fluent-message-bar intent="warning">
warning
<div class="actions" slot="actions">
<fluent-button size="small">Action</fluent-button>
<fluent-button size="small">Action</fluent-button>
</div>
<fluent-button size="small" appearance="transparent" icon-only slot="close"> ${dismissed20Regular} </fluent-button>
</fluent-message-bar>
<br />
<fluent-message-bar intent="success">
success
<div class="actions" slot="actions">
<fluent-button size="small">Action</fluent-button>
<fluent-button size="small">Action</fluent-button>
</div>
<fluent-button size="small" appearance="transparent" icon-only slot="close"> ${dismissed20Regular} </fluent-button>
</fluent-message-bar>
<br />
<fluent-message-bar intent="error">
error
<div class="actions" slot="actions">
<fluent-button size="small">Action</fluent-button>
<fluent-button size="small">Action</fluent-button>
</div>
<fluent-button size="small" appearance="transparent" icon-only slot="close"> ${dismissed20Regular} </fluent-button>
</fluent-message-bar>
`);
Loading
Loading