Skip to content

Commit

Permalink
add menu button as new component (#27396)
Browse files Browse the repository at this point in the history
* add menu button as a new component

* remove unnecessary icon attr in stories
  • Loading branch information
chrisdholt authored and radium-v committed May 3, 2024
1 parent f948564 commit 02ae3c9
Show file tree
Hide file tree
Showing 11 changed files with 365 additions and 0 deletions.
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'],
::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';
21 changes: 21 additions & 0 deletions packages/web-components/src/menu-button/menu-button.definition.ts
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>`,
),
});
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 {}

0 comments on commit 02ae3c9

Please sign in to comment.