forked from microsoft/fluentui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(button): add button to web components v3 (microsoft#27278)
* add button as a new web component * change files * add package export * add icon attr, fixup disabled styles * update color => fill * add disabled focusable click handling * move to attribute syntax for aria-disabled * remove icon attribute in favor of min-height to accommodate icons * use long form for padding to ensure we catch browser defaults * add basic readme with deltas
- Loading branch information
1 parent
fc26116
commit c2bc2aa
Showing
12 changed files
with
760 additions
and
0 deletions.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
change/@fluentui-web-components-be1b97fa-2097-4adc-ad03-f84e0ac0b1b1.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"type": "prerelease", | ||
"comment": "feat(button): add button web component", | ||
"packageName": "@fluentui/web-components", | ||
"email": "chhol@microsoft.com", | ||
"dependentChangeType": "patch" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# Button | ||
|
||
The `Button` component allows users to commit a change or trigger an action via a single click or tap and is often found inside forms, dialogs, panels and/or pages. | ||
|
||
## **Design Spec** | ||
|
||
[Link to Button in Figma](https://www.figma.com/file/Nj9EBBvOZmS11zKNJfilVR/Button?node-id=1723%3A380&t=PNVwuI4rLXjxAFNJ-1) | ||
|
||
<br /> | ||
|
||
## **Engineering Spec** | ||
|
||
Fluent WC3 Button has feature parity with the Fluent UI React 9 Button implementation but not direct parity. | ||
|
||
<br /> | ||
|
||
## Class: `Button` | ||
|
||
<br /> | ||
|
||
### **Component Name** | ||
|
||
`<fluent-button></fluent-button>` | ||
|
||
<br /> | ||
|
||
## **Preparation** | ||
|
||
<br /> | ||
|
||
### **Fluent Web Component v3 v.s Fluent React 9** | ||
|
||
<br /> | ||
|
||
**Component and Slot Mapping** | ||
|
||
| Fluent UI React 9 | Fluent Web Components 3 | | ||
| ----------------- | ----------------------- | | ||
| `<Button>` | `<fluent-button>` | | ||
|
||
<br /> | ||
|
||
**Property Mapping** | ||
| Fluent UI React 9 | Fluent Web Components 3 | Description of difference | | ||
| ------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------- | | ||
| `icon`is a slot | The default slot or `start`, and `end` | In FUIR9, `icon` is a slot. In the web components implementation, an icon can be passed into the default slot and paired with an `icon-only` attribute, or supplementally in the `start` and/or `end` slots | | ||
| `defaultValue` | `current-value` | In React, `defaultValue` sets the default value of form controls. In HTML, `value` is the default value, it doesn't update. [This RFC](https://github.com/microsoft/fast/issues/5119) provides more detail on how we came to decide to go with `current-value` instead of alternatives. Ultimately, the decision stems on staying aligned to the web platform as deviating would likely mean deviation for good. | | ||
| `as` | A separate web component for anchor implementations | In FUIR9, HTML is returned so interpolating tags in the virtual DOM doesn't present a problem. In WC's, we can't safely interpolate tags and the cost to provide two sets of API's, one form associated and one not`icon` is a slot. In the web components implementation, conditional rendering brings with it a cost as both templates need to be enumerated. Additionally, button is a form associated element whereas anchors are not. For this reason, we'll provide an "anchor-button" as a separate component. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { FluentDesignSystem } from '../fluent-design-system.js'; | ||
import { Button } from './button.js'; | ||
import { styles } from './button.styles.js'; | ||
import { template } from './button.template.js'; | ||
|
||
/** | ||
* The Fluent Button Element. Implements {@link @microsoft/fast-foundation#Button }, | ||
* {@link @microsoft/fast-foundation#buttonTemplate} | ||
* | ||
* @public | ||
* @remarks | ||
* HTML Element: \<fluent-button\> | ||
*/ | ||
export const definition = Button.compose({ | ||
name: `${FluentDesignSystem.prefix}-button`, | ||
template, | ||
styles, | ||
shadowOptions: { | ||
delegatesFocus: true, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { ButtonOptions, ValuesOf } from '@microsoft/fast-foundation'; | ||
|
||
/** | ||
* ButtonAppearance constants | ||
* @public | ||
*/ | ||
export const ButtonAppearance = { | ||
primary: 'primary', | ||
outline: 'outline', | ||
subtle: 'subtle', | ||
secondary: 'secondary', | ||
transparent: 'transparent', | ||
} as const; | ||
|
||
/** | ||
* A Button can be secondary, primary, outline, subtle, transparent | ||
* @public | ||
*/ | ||
export type ButtonAppearance = ValuesOf<typeof ButtonAppearance>; | ||
|
||
/** | ||
* A Button can be square, circular or rounded. | ||
* @public | ||
*/ | ||
export const ButtonShape = { | ||
circular: 'circular', | ||
rounded: 'rounded', | ||
square: 'square', | ||
} as const; | ||
|
||
/** | ||
* A Button can be square, circular or rounded | ||
* @public | ||
*/ | ||
export type ButtonShape = ValuesOf<typeof ButtonShape>; | ||
|
||
/** | ||
* A Button can be a size of small, medium or large. | ||
* @public | ||
*/ | ||
export const ButtonSize = { | ||
small: 'small', | ||
medium: 'medium', | ||
large: 'large', | ||
} as const; | ||
|
||
/** | ||
* A Button can be on of several preset sizes. | ||
* @public | ||
*/ | ||
export type ButtonSize = ValuesOf<typeof ButtonSize>; | ||
|
||
export { ButtonOptions }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
import { html } from '@microsoft/fast-element'; | ||
import type { Args, Meta } from '@storybook/html'; | ||
import { renderComponent } from '../helpers.stories.js'; | ||
import type { Button as FluentButton } from './button.js'; | ||
import { ButtonAppearance, ButtonShape, ButtonSize } from './button.options.js'; | ||
import './define.js'; | ||
|
||
type ButtonStoryArgs = Args & FluentButton; | ||
type ButtonStoryMeta = Meta<ButtonStoryArgs>; | ||
|
||
const storyTemplate = html<ButtonStoryArgs>` | ||
<fluent-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}" | ||
?icon="${x => x.icon}" | ||
> | ||
${x => x.content} | ||
</fluent-button> | ||
`; | ||
|
||
export default { | ||
title: 'Components/Button/Button', | ||
args: { | ||
content: 'Button', | ||
disabled: false, | ||
disabledFocusable: false, | ||
}, | ||
argTypes: { | ||
appearance: { | ||
options: Object.values(ButtonAppearance), | ||
control: { | ||
type: 'select', | ||
}, | ||
}, | ||
shape: { | ||
options: Object.values(ButtonShape), | ||
control: { | ||
type: 'select', | ||
}, | ||
}, | ||
size: { | ||
options: Object.values(ButtonSize), | ||
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 ButtonStoryMeta; | ||
|
||
export const Button = renderComponent(storyTemplate).bind({}); | ||
|
||
export const Appearance = renderComponent(html<ButtonStoryArgs>` | ||
<fluent-button>Default</fluent-button> | ||
<fluent-button appearance="primary">Primary</fluent-button> | ||
<fluent-button appearance="outline">Outline</fluent-button> | ||
<fluent-button appearance="subtle">Subtle</fluent-button> | ||
<fluent-button appearance="transparent">Transparent</fluent-button> | ||
`); | ||
|
||
export const Shape = renderComponent(html<ButtonStoryArgs>` | ||
<fluent-button shape="rounded">Rounded</fluent-button> | ||
<fluent-button shape="circular">Circular</fluent-button> | ||
<fluent-button shape="square">Square</fluent-button> | ||
`); | ||
|
||
export const Size = renderComponent(html<ButtonStoryArgs>` | ||
<fluent-button size="small">Small</fluent-button> | ||
<fluent-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-button | ||
> | ||
<fluent-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-button> | ||
<fluent-button size="medium">Medium</fluent-button> | ||
<fluent-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-button | ||
> | ||
<fluent-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-button> | ||
<fluent-button size="large">Large</fluent-button> | ||
<fluent-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-button | ||
> | ||
<fluent-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-button> | ||
`); | ||
|
||
export const Disabled = renderComponent(html<ButtonStoryArgs>` | ||
<fluent-button>Enabled state</fluent-button> | ||
<fluent-button disabled>Disabled state</fluent-button> | ||
<fluent-button disabled-focusable>Disabled focusable state</fluent-button> | ||
<fluent-button appearance="primary">Enabled state</fluent-button> | ||
<fluent-button appearance="primary" disabled>Disabled state</fluent-button> | ||
<fluent-button appearance="primary" disabled-focusable>Disabled focusable state</fluent-button> | ||
`); | ||
|
||
export const WithLongText = renderComponent(html<ButtonStoryArgs>` | ||
<style> | ||
.max-width { | ||
width: 280px; | ||
} | ||
</style> | ||
<fluent-button>Short text</fluent-button> | ||
<fluent-button class="max-width">Long text wraps after it hits the max width of the component</fluent-button> | ||
`); |
Oops, something went wrong.