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

add menu button as new component #27396

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@
{
"type": "prerelease",
"comment": "feat(menu-button): add menu button as new component",
"packageName": "@fluentui/web-components",
"email": "chhol@microsoft.com",
"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 @@ -60,6 +60,10 @@
"types": "./dist/esm/text/define.d.ts",
"default": "./dist/esm/text/define.js"
},
"./menu-button": {
"types": "./dist/esm/menu-button/define.d.ts",
"default": "./dist/esm/menu-button/define.js"
},
"./progress-bar": {
"types": "./dist/esm/progress-bar/define.d.ts",
"default": "./dist/esm/progress-bar/define.js"
Expand Down
2 changes: 2 additions & 0 deletions packages/web-components/src/button/button.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,12 @@ export const styles = css`
fill: currentColor;
}

[slot='start'],
chrisdholt marked this conversation as resolved.
Show resolved Hide resolved
::slotted([slot='start']) {
margin-inline-end: var(--icon-spacing);
}

[slot='end'],
::slotted([slot='end']) {
margin-inline-start: var(--icon-spacing);
}
Expand Down
1 change: 1 addition & 0 deletions packages/web-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './button/index.js';
export * from './counter-badge/index.js';
export * from './divider/index.js';
export * from './image/index.js';
export * from './menu-button/index.js';
export * from './progress-bar/index.js';
export * from './slider/index.js';
export * from './spinner/index.js';
Expand Down
4 changes: 4 additions & 0 deletions packages/web-components/src/menu-button/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 './menu-button.definition.js';

definition.define(FluentDesignSystem.registry);
5 changes: 5 additions & 0 deletions packages/web-components/src/menu-button/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './menu-button.js';
export * from './menu-button.options.js';
export { template as MenuButtonTemplate } from './menu-button.template.js';
export { styles as MenuButtonStyles } from '../button/button.styles.js';
export { definition as MenuButtonDefinition } from './menu-button.definition.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { FluentDesignSystem } from '../fluent-design-system.js';
import { styles } from '../button/button.styles.js';
import { MenuButton } from './menu-button.js';
import { template } from './menu-button.template.js';

/**
* The Fluent Menu Button Element. Implements {@link @microsoft/fast-foundation#Button },
* {@link @microsoft/fast-foundation#buttonTemplate}
*
* @public
* @remarks
* HTML Element: \<fluent-button\>
*/
export const definition = MenuButton.compose({
name: `${FluentDesignSystem.prefix}-menu-button`,
template,
styles,
shadowOptions: {
delegatesFocus: true,
},
});
40 changes: 40 additions & 0 deletions packages/web-components/src/menu-button/menu-button.options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ButtonOptions, ValuesOf } from '@microsoft/fast-foundation';
import { ButtonAppearance, ButtonShape, ButtonSize } from '../button/button.options.js';

/**
* Menu Button Appearance constants
* @public
*/
export const MenuButtonAppearance = ButtonAppearance;

/**
* A Menu Button can be secondary, primary, outline, subtle, transparent
* @public
*/
export type MenuButtonAppearance = ValuesOf<typeof MenuButtonAppearance>;

/**
* A Menu Button can be square, circular or rounded.
* @public
*/
export const MenuButtonShape = ButtonShape;

/**
* A Menu Button can be square, circular or rounded
* @public
*/
export type MenuButtonShape = ValuesOf<typeof MenuButtonShape>;

/**
* A Menu Button can be a size of small, medium or large.
* @public
*/
export const MenuButtonSize = ButtonSize;

/**
* A Menu Button can be on of several preset sizes.
* @public
*/
export type MenuButtonSize = ValuesOf<typeof MenuButtonSize>;

export { ButtonOptions as MenuButtonOptions };
261 changes: 261 additions & 0 deletions packages/web-components/src/menu-button/menu-button.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
import { html } from '@microsoft/fast-element';
import type { Args, Meta } from '@storybook/html';
import { renderComponent } from '../helpers.stories.js';
import type { MenuButton as FluentMenuButton } from './menu-button.js';
import { MenuButtonAppearance, MenuButtonShape, MenuButtonSize } from './menu-button.options.js';
import './define.js';

type MenuButtonStoryArgs = Args & FluentMenuButton;
type MenuButtonStoryMeta = Meta<MenuButtonStoryArgs>;

const storyTemplate = html<MenuButtonStoryArgs>`
<fluent-menu-button
appearance="${x => x.appearance}"
shape="${x => x.shape}"
size="${x => x.size}"
?disabled="${x => x.disabled}"
?disabled-focusable="${x => x.disabledFocusable}"
?icon-only="${x => x.iconOnly}"
>
${x => x.content}
</fluent-menu-button>
`;

export default {
title: 'Components/Button/Menu Button',
args: {
content: 'Menu Button',
disabled: false,
disabledFocusable: false,
},
argTypes: {
appearance: {
options: Object.values(MenuButtonAppearance),
control: {
type: 'select',
},
},
shape: {
options: Object.values(MenuButtonShape),
control: {
type: 'select',
},
},
size: {
options: Object.values(MenuButtonSize),
control: {
type: 'select',
},
},
disabled: {
control: 'boolean',
table: {
type: {
summary: 'Sets the disabled state of the component',
},
defaultValue: {
summary: 'false',
},
},
},
disabledFocusable: {
control: 'boolean',
table: {
type: {
summary: 'The component is disabled but still focusable',
},
defaultValue: {
summary: 'false',
},
},
},
content: {
control: 'Button text',
},
},
} as MenuButtonStoryMeta;

export const Button = renderComponent(storyTemplate).bind({});

export const Appearance = renderComponent(html<MenuButtonStoryArgs>`
<fluent-menu-button>Default</fluent-menu-button>
<fluent-menu-button appearance="primary">Primary</fluent-menu-button>
<fluent-menu-button appearance="outline">Outline</fluent-menu-button>
<fluent-menu-button appearance="subtle">Subtle</fluent-menu-button>
<fluent-menu-button appearance="transparent">Transparent</fluent-menu-button>
`);

export const Shape = renderComponent(html<MenuButtonStoryArgs>`
<fluent-menu-button shape="rounded">Rounded</fluent-menu-button>
<fluent-menu-button shape="circular">Circular</fluent-menu-button>
<fluent-menu-button shape="square">Square</fluent-menu-button>
`);

export const Size = renderComponent(html<MenuButtonStoryArgs>`
<fluent-menu-button size="small">Small</fluent-menu-button>
<fluent-menu-button size="small" icon
><svg
fill="currentColor"
slot="start"
aria-hidden="true"
width="1em"
height="1em"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.5 3A2.5 2.5 0 0117 5.5v9a2.5 2.5 0 01-2.5 2.5h-9A2.5 2.5 0 013 14.5v-9A2.5 2.5 0 015.5 3h9zm0 1h-9C4.67 4 4 4.67 4 5.5v9c0 .83.67 1.5 1.5 1.5h9c.83 0 1.5-.67 1.5-1.5v-9c0-.83-.67-1.5-1.5-1.5zM7 11a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2zM7 7a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2z"
fill="currentColor"
></path></svg
>Small with calendar icon</fluent-menu-button
>
<fluent-menu-button size="small" icon-only aria-label="Small icon only button"
><svg
fill="currentColor"
aria-hidden="true"
width="1em"
height="1em"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.5 3A2.5 2.5 0 0117 5.5v9a2.5 2.5 0 01-2.5 2.5h-9A2.5 2.5 0 013 14.5v-9A2.5 2.5 0 015.5 3h9zm0 1h-9C4.67 4 4 4.67 4 5.5v9c0 .83.67 1.5 1.5 1.5h9c.83 0 1.5-.67 1.5-1.5v-9c0-.83-.67-1.5-1.5-1.5zM7 11a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2zM7 7a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2z"
fill="currentColor"
></path></svg
></fluent-menu-button>
<fluent-menu-button size="medium">Medium</fluent-menu-button>
<fluent-menu-button size="medium" icon
><svg
fill="currentColor"
slot="start"
aria-hidden="true"
width="1em"
height="1em"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.5 3A2.5 2.5 0 0117 5.5v9a2.5 2.5 0 01-2.5 2.5h-9A2.5 2.5 0 013 14.5v-9A2.5 2.5 0 015.5 3h9zm0 1h-9C4.67 4 4 4.67 4 5.5v9c0 .83.67 1.5 1.5 1.5h9c.83 0 1.5-.67 1.5-1.5v-9c0-.83-.67-1.5-1.5-1.5zM7 11a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2zM7 7a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2z"
fill="currentColor"
></path></svg
>Medium with calendar icon</fluent-menu-button
>
<fluent-menu-button size="medium" icon-only aria-label="Medium icon only button"
><svg
fill="currentColor"
aria-hidden="true"
width="1em"
height="1em"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.5 3A2.5 2.5 0 0117 5.5v9a2.5 2.5 0 01-2.5 2.5h-9A2.5 2.5 0 013 14.5v-9A2.5 2.5 0 015.5 3h9zm0 1h-9C4.67 4 4 4.67 4 5.5v9c0 .83.67 1.5 1.5 1.5h9c.83 0 1.5-.67 1.5-1.5v-9c0-.83-.67-1.5-1.5-1.5zM7 11a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2zM7 7a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2z"
fill="currentColor"
></path></svg
></fluent-menu-button>
<fluent-menu-button size="large">Large</fluent-menu-button>
<fluent-menu-button size="large" icon
><svg
fill="currentColor"
slot="start"
aria-hidden="true"
width="1em"
height="1em"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.5 3A2.5 2.5 0 0117 5.5v9a2.5 2.5 0 01-2.5 2.5h-9A2.5 2.5 0 013 14.5v-9A2.5 2.5 0 015.5 3h9zm0 1h-9C4.67 4 4 4.67 4 5.5v9c0 .83.67 1.5 1.5 1.5h9c.83 0 1.5-.67 1.5-1.5v-9c0-.83-.67-1.5-1.5-1.5zM7 11a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2zM7 7a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2z"
fill="currentColor"
></path></svg
>Large with calendar icon</fluent-menu-button
>
<fluent-menu-button size="large" icon-only aria-label="Large icon only button"
><svg
fill="currentColor"
aria-hidden="true"
width="1em"
height="1em"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.5 3A2.5 2.5 0 0117 5.5v9a2.5 2.5 0 01-2.5 2.5h-9A2.5 2.5 0 013 14.5v-9A2.5 2.5 0 015.5 3h9zm0 1h-9C4.67 4 4 4.67 4 5.5v9c0 .83.67 1.5 1.5 1.5h9c.83 0 1.5-.67 1.5-1.5v-9c0-.83-.67-1.5-1.5-1.5zM7 11a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2zM7 7a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2zm3 0a1 1 0 110 2 1 1 0 010-2z"
fill="currentColor"
></path></svg
></fluent-menu-button>
`);

export const CustomIcon = renderComponent(html<MenuButtonStoryArgs>`
<fluent-menu-button size="small">
Small
<svg
slot="end"
fill="currentColor"
aria-hidden="true"
width="1em"
height="1em"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.5 13h5a.5.5 0 0 1 .09 1H7.5a.5.5 0 0 1-.09-1h5.09-5Zm-2-4h9a.5.5 0 0 1 .09 1H5.5a.5.5 0 0 1-.09-1h9.09-9Zm-2-4h13a.5.5 0 0 1 .09 1H3.5a.5.5 0 0 1-.09-1H16.5h-13Z"
fill="currentColor"
></path>
</svg>
</fluent-menu-button>
<fluent-menu-button size="medium"
>Medium<svg
slot="end"
fill="currentColor"
aria-hidden="true"
width="1em"
height="1em"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.5 13h5a.5.5 0 0 1 .09 1H7.5a.5.5 0 0 1-.09-1h5.09-5Zm-2-4h9a.5.5 0 0 1 .09 1H5.5a.5.5 0 0 1-.09-1h9.09-9Zm-2-4h13a.5.5 0 0 1 .09 1H3.5a.5.5 0 0 1-.09-1H16.5h-13Z"
fill="currentColor"
></path>
</svg>
</fluent-menu-button>
<fluent-menu-button size="large"
>Large<svg
slot="end"
fill="currentColor"
aria-hidden="true"
width="1em"
height="1em"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.5 13h5a.5.5 0 0 1 .09 1H7.5a.5.5 0 0 1-.09-1h5.09-5Zm-2-4h9a.5.5 0 0 1 .09 1H5.5a.5.5 0 0 1-.09-1h9.09-9Zm-2-4h13a.5.5 0 0 1 .09 1H3.5a.5.5 0 0 1-.09-1H16.5h-13Z"
fill="currentColor"
></path>
</svg>
</fluent-menu-button>
`);

export const Disabled = renderComponent(html<MenuButtonStoryArgs>`
<fluent-menu-button>Enabled state</fluent-menu-button>
<fluent-menu-button disabled>Disabled state</fluent-menu-button>
<fluent-menu-button disabled-focusable>Disabled focusable state</fluent-menu-button>
<fluent-menu-button appearance="primary">Enabled state</fluent-menu-button>
<fluent-menu-button appearance="primary" disabled>Disabled state</fluent-menu-button>
<fluent-menu-button appearance="primary" disabled-focusable>Disabled focusable state</fluent-menu-button>
`);

export const WithLongText = renderComponent(html<MenuButtonStoryArgs>`
<style>
.max-width {
width: 280px;
}
</style>
<fluent-menu-button>Short text</fluent-menu-button>
<fluent-menu-button class="max-width"
>Long text wraps after it hits the max width of the component</fluent-menu-button
>
`);
13 changes: 13 additions & 0 deletions packages/web-components/src/menu-button/menu-button.template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ElementViewTemplate, html } from '@microsoft/fast-element';
import { buttonTemplate } from '@microsoft/fast-foundation';
import type { MenuButton } from './menu-button.js';

/**
* The template for the Button component.
* @public
*/
export const template: ElementViewTemplate<MenuButton> = buttonTemplate<MenuButton>({
end: html.partial(
`<svg slot="end" fill="currentColor" aria-hidden="true" width="1em" height="1em" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M15.85 7.65c.2.2.2.5 0 .7l-5.46 5.49a.55.55 0 0 1-.78 0L4.15 8.35a.5.5 0 1 1 .7-.7L10 12.8l5.15-5.16c.2-.2.5-.2.7 0Z" fill="currentColor"></path></svg>`,
chrisdholt marked this conversation as resolved.
Show resolved Hide resolved
),
});
7 changes: 7 additions & 0 deletions packages/web-components/src/menu-button/menu-button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Button } from '../button/button.js';

/**
* The base class used for constructing a fluent-menu-button custom element
* @public
*/
export class MenuButton extends Button {}