Skip to content

Commit

Permalink
Support primary and accent variants for toggle and menu buttons (#1934)
Browse files Browse the repository at this point in the history
## 🀨 Rationale

Fixes: #1906 

## πŸ‘©β€πŸ’» Implementation

- The ask was for the accent appearance variant on the menu button, but
because the menu button uses the toggle button internally, I added the
`appearance-variant` attribute to both toggle and menu button.
- I added support for both primary and accent appearance variants.
- When adding the new attribute states to the toggle and menu button
matrix stories, I moved some common button state arrays to a new file in
`patterns/buttons/tests`.
- While examining visual designs/colors, I found and fixed several
issues:
- The `DigitalGreenDark` token value was incorrect, based on the Figma
document. I updated this value, which affected block-accent button color
and the rich-text mention font color.
- Anchors in active, prominent, and prominent+active states were using
the wrong color tokens.
- The icon color for outline-accent buttons should match the text color,
but didn't.
- After consulting with Brandon, he directed me to change the
active/pressed colors for the block-primary and block-accent button
appearances so they would be consistent with other button active/pressed
colors. Previously, they were slightly darker versions of the primary
and accent colors (see below). This allowed me to remove the two,
now-unused tokens: `buttonFillActivePrimaryColor` and
`buttonFillAccentActiveColor`.

![image](https://github.com/ni/nimble/assets/7282195/29237669-5812-44dd-a426-5388ccac7262)

To avoid increasing the size of this PR, Angular/Blazor support will be
added in a follow-up PR.

## πŸ§ͺ Testing

Lots of manual state styling testing in Storybook. Also Chromatic visual
tests.

## βœ… Checklist

- [x] I have updated the project documentation to reflect my changes or
determined no changes are needed.

---------

Co-authored-by: Milan Raj <[email protected]>
  • Loading branch information
m-akinc and rajsite authored Mar 26, 2024
1 parent 95fa45b commit 594ac22
Show file tree
Hide file tree
Showing 26 changed files with 449 additions and 449 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "major",
"comment": "Support primary and accent variants for toggle and menu buttons. BREAKING CHANGE: Removed theme-aware tokens `buttonFillActivePrimaryColor` and `buttonFillAccentActiveColor`. Use `fillSelectedColor` instead.",
"packageName": "@ni/nimble-components",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Fix value of DigitalGreenDark",
"packageName": "@ni/nimble-tokens",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import type { StoryFn, Meta } from '@storybook/html';
import { html, ViewTemplate, when } from '@microsoft/fast-element';
import { pascalCase } from '@microsoft/fast-web-utilities';
import {
ButtonAppearance,
ButtonAppearanceVariant
} from '../../patterns/button/types';
import {
createMatrix,
sharedMatrixParameters,
Expand All @@ -17,6 +12,14 @@ import { textCustomizationWrapper } from '../../utilities/tests/text-customizati
import { anchorButtonTag } from '..';
import { iconLinkTag } from '../../icons/link';
import { iconArrowExpanderRightTag } from '../../icons/arrow-expander-right';
import {
appearanceStates,
type AppearanceState,
type AppearanceVariantState,
type PartVisibilityState,
appearanceVariantStates,
partVisibilityStates
} from '../../patterns/button/tests/states';

const metadata: Meta = {
title: 'Tests/Anchor Button',
Expand All @@ -27,26 +30,6 @@ const metadata: Meta = {

export default metadata;

/* array of iconVisible, labelVisible, endIconVisible */
const partVisibilityStates = [
[true, true, false],
[true, false, false],
[false, true, false],
[true, true, true],
[false, true, true]
] as const;
type PartVisibilityState = (typeof partVisibilityStates)[number];

const appearanceStates: [string, string | undefined][] = Object.entries(
ButtonAppearance
).map(([key, value]) => [pascalCase(key), value]);
type AppearanceState = (typeof appearanceStates)[number];

const appearanceVariantStates: [string, string | undefined][] = Object.entries(
ButtonAppearanceVariant
).map(([key, value]) => [pascalCase(key), value]);
type AppearanceVariantState = (typeof appearanceVariantStates)[number];

// prettier-ignore
const component = (
[disabledName, disabled]: DisabledState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
} from '@storybook/blocks';
import TargetDocs from '../../patterns/anchor/tests/target-docs.mdx';
import ContentHiddenDocs from '../../patterns/button/tests/content-hidden-docs.mdx';
import StylingDocs from '../../patterns/button/tests/styling-docs.mdx';
import { anchorButtonTag } from '..';
import * as anchorButtonStories from './anchor-button.stories';

<Meta of={anchorButtonStories} />
Expand All @@ -18,9 +20,7 @@ An anchor button is a component with the visual appearance of a button, but it n
<Canvas of={anchorButtonStories.outlineAnchorButton} />
<Controls of={anchorButtonStories.outlineAnchorButton} />

{/* ## Appearances */}

{/* ## Appearance Variants */}
<StylingDocs components={{ Button: anchorButtonTag }} />

{/* ## Usage */}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { StoryFn, Meta } from '@storybook/html';
import { html, ViewTemplate, when } from '@microsoft/fast-element';
import { pascalCase } from '@microsoft/fast-web-utilities';
import { ButtonAppearance, ButtonAppearanceVariant } from '../types';
import {
createMatrix,
sharedMatrixParameters,
Expand All @@ -15,6 +13,14 @@ import { buttonTag } from '..';
import { iconKeyTag } from '../../icons/key';
import { iconArrowExpanderDownTag } from '../../icons/arrow-expander-down';
import { bodyFont } from '../../theme-provider/design-tokens';
import {
appearanceStates,
type AppearanceState,
type AppearanceVariantState,
type PartVisibilityState,
appearanceVariantStates,
partVisibilityStates
} from '../../patterns/button/tests/states';

const metadata: Meta = {
title: 'Tests/Button',
Expand All @@ -25,26 +31,6 @@ const metadata: Meta = {

export default metadata;

/* array of iconVisible, labelVisible, endIconVisible */
const partVisibilityStates = [
[true, true, false],
[true, false, false],
[false, true, false],
[true, true, true],
[false, true, true]
] as const;
type PartVisibilityState = (typeof partVisibilityStates)[number];

const appearanceStates: [string, string | undefined][] = Object.entries(
ButtonAppearance
).map(([key, value]) => [pascalCase(key), value]);
type AppearanceState = (typeof appearanceStates)[number];

const appearanceVariantStates: [string, string | undefined][] = Object.entries(
ButtonAppearanceVariant
).map(([key, value]) => [pascalCase(key), value]);
type AppearanceVariantState = (typeof appearanceVariantStates)[number];

// prettier-ignore
const component = (
[disabledName, disabled]: DisabledState,
Expand Down
202 changes: 2 additions & 200 deletions packages/nimble-components/src/button/tests/button.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Canvas, Meta, Controls, Title } from '@storybook/blocks';
import ContentHiddenDocs from '../../patterns/button/tests/content-hidden-docs.mdx';
import { NimbleButton } from './button.react';
import { NimbleIconKey } from '../../icons/tests/key.react';
import StylingDocs from '../../patterns/button/tests/styling-docs.mdx';
import { buttonTag } from '..';
import { anchorButtonTag } from '../../anchor-button';
import * as buttonStories from './button.stories';
Expand All @@ -18,209 +17,12 @@ If you want a button that triggers navigation to a URL, use the <Tag name={ancho
<Canvas of={buttonStories.outlineButton} />
<Controls of={buttonStories.outlineButton} />

## Styling

### Appearances

These appearances have the default styling of the <Tag name={buttonTag}/>. Each should be considered for use before using appearance variant buttons.

#### Ghost Button:

<Frame>
<Container>
<Column stylingClass="controls">
<NimbleButton appearance="ghost">{`Ghost Button`}</NimbleButton>
<NimbleButton appearance="ghost" content-hidden>
<NimbleIconKey slot="start"></NimbleIconKey>
{`Ghost Button`}
</NimbleButton>

</Column>
<Column>
<ul>
<li>Ghost is the default appearance and should be the first considered for use.</li>
<li>
Use as the default and standard option to create a clean airy and open UI
feel. Ghost buttons fit comfortably in tight spaces and help control the
visual density of the UI.
</li>
<li>
Be careful when using that the surrounding context does not cause this
button to be confused for emphasized body text, tabs or a standalone links.
</li>
<li>Use in combination with a primary outline or primary block buttons to create a hierarchy of importance. There is no primary ghost button.</li>
</ul>
</Column>
</Container>

</Frame>

#### Outline Button:

<Frame>
<Container>
<Column stylingClass="controls">
<NimbleButton appearance="outline">{`Outline Button`}</NimbleButton>
<NimbleButton appearance="outline" content-hidden>
<NimbleIconKey slot="start"></NimbleIconKey>
{`Outline Button`}
</NimbleButton>
</Column>
<Column>
<ul>
<li>
Outline is the secondary style and should be considered for
use when ghost button is not sufficient.
</li>
<li>
Use as an alternative standard button when a ghost button is
not suitable. Use like a ghost button to create a clean,
light and airy feel.
</li>
<li>
The outline button is more visually direct about the
control's functionality than a ghost button.
</li>
<li>
Use in combination with ghost buttons (but not block
buttons) to create hierarchy.
</li>
</ul>
</Column>
</Container>
</Frame>

#### Block Button:

<Frame>
<Container>
<Column stylingClass="controls">
<NimbleButton appearance="block">{`Block Button`}</NimbleButton>
<NimbleButton appearance="block" content-hidden>
<NimbleIconKey slot="start"></NimbleIconKey>
{`Block Button`}
</NimbleButton>
</Column>
<Column>
<ul>
<li>
Block is the tertiary style used for creating the most eye
catching and functionally direct button. Use in areas where
controls are not often present or obvious, or when lots of
busy information can cause an important control to be
overlooked.
</li>
<li>
Use as a standard button when the most visible solution is
required. Use as an alternative to overly subtle button
solutions when it is important to emphasize an action and
the functionality of the control.
</li>
<li>
Use in combination with ghost buttons (but not outline
buttons) to create hierarchy.
</li>
</ul>
</Column>
</Container>
</Frame>

### Appearance Variants

Button appearance variants are mainly used when a button needs be distinguished for one of the following reasons:

- To indicate the action that allows the user to accomplish their most common or important goal
- To indicate the action that allows the user to complete their task

There are currently two available values for `appearance-variant`: `primary` and `accent`.

#### Accent Button:

<Frame>
<Container config="325px 1fr">
<Column stylingClass="controls">
<NimbleButton appearance="ghost">{`Ghost`}</NimbleButton>
<NimbleButton appearance="ghost">{`Ghost`}</NimbleButton>
<NimbleButton
appearance="outline"
appearance-variant="accent"
>{`Outline Accent`}</NimbleButton>
<Divider />
<NimbleButton appearance="ghost">{`Ghost`}</NimbleButton>
<NimbleButton appearance="ghost">{`Ghost`}</NimbleButton>
<NimbleButton
appearance="block"
appearance-variant="accent"
>{`Block Accent`}</NimbleButton>
</Column>
<Column>
<Do>
Use only 1 accent button in a section. It should be used when
trying to achieve the most prominent eye-catching approach.
Consider the contextual implications of using a green colored
button in relation to its name or metaphor. Remember that color
has accessibility constraints and the color alone should not be
relied on.
</Do>
<Do>
Use in situations that lack color and enthusiasm to help support
the brand.
</Do>
<Do>Use in combination with ghost buttons to create hierarchy.</Do>
<Dont>
Do not use an outline button with a block button to create
hierarchy.
</Dont>
</Column>
</Container>
</Frame>

#### Primary Button:

<Frame>
<Container config="325px 1fr">
<Column stylingClass="controls">
<NimbleButton appearance="ghost">{`Ghost`}</NimbleButton>
<NimbleButton appearance="ghost">{`Ghost`}</NimbleButton>
<NimbleButton appearance="outline" appearance-variant="primary">
{`Outline Primary`}
</NimbleButton>
<Divider/>
<NimbleButton appearance="ghost">{`Ghost`}</NimbleButton>
<NimbleButton appearance="ghost">{`Ghost`}</NimbleButton>
<NimbleButton appearance="block" appearance-variant="primary">
{`Block Primary`}
</NimbleButton>
</Column>
<Column>
<Do>
Use only 1 primary button in a section. It should be used when
there is a conflict with color and its context.
</Do>
<Do>Use in combination with ghost buttons to create hierarchy.</Do>
<Dont>
Do not use an outline button with a block button to create
hierarchy.
</Dont>
</Column>
</Container>

</Frame>

#### Examples:

To see more examples of appearance variant button hierarchy, see the nimble-button Figma docs for [Primary and Standard Actions](https://www.figma.com/file/PO9mFOu5BCl8aJvFchEeuN/Nimble_Components?type=design&node-id=1604-74603&mode=design&t=SQ3lyK83VHBUaOkg-0).
<StylingDocs components={{ Button: buttonTag }} />

## Sizing

Nimble Buttons are currently always 32px tall. Designs exist for other sizes; if you need these in an application, please comment on [Configurable height for nimble controls (#610)](https://github.com/ni/nimble/issues/610).

## Styling / Theme

Use the Color UI version for backgrounds with color (e.g. purple, blue).

See the usage details for more information on button styling / usage.

## Accessibility

Please work with your designer and ensure you have a 4.5:1
Expand Down
11 changes: 9 additions & 2 deletions packages/nimble-components/src/menu-button/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ import {
keyArrowUp,
keyEscape
} from '@microsoft/fast-web-utilities';
import { ButtonAppearance } from '../button/types';
import {
ButtonAppearance,
ButtonAppearanceVariant,
MenuButtonToggleEventDetail,
MenuButtonPosition
} from './types';
import type { ToggleButton } from '../toggle-button';
import { styles } from './styles';
import { template } from './template';
import { MenuButtonToggleEventDetail, MenuButtonPosition } from './types';
import type { ButtonPattern } from '../patterns/button/types';
import type { AnchoredRegion } from '../anchored-region';

Expand All @@ -27,6 +31,9 @@ export class MenuButton extends FoundationElement implements ButtonPattern {
@attr
public appearance: ButtonAppearance = ButtonAppearance.outline;

@attr({ attribute: 'appearance-variant' })
public appearanceVariant: ButtonAppearanceVariant;

@attr({ mode: 'boolean' })
public disabled = false;

Expand Down
1 change: 1 addition & 0 deletions packages/nimble-components/src/menu-button/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const template = html<MenuButton>`
<${toggleButtonTag}
part="button"
appearance="${x => x.appearance}"
appearance-variant="${x => x.appearanceVariant}"
?content-hidden="${x => x.contentHidden}"
?checked="${x => x.open}"
?disabled="${x => x.disabled}"
Expand Down
Loading

0 comments on commit 594ac22

Please sign in to comment.