feat: add the icon button - #16
Conversation
The variant colors, their states, and the focus ring move into the `variants` mixin of `_button-variants.scss`. It takes the selector of a disabled control, so the icon button can share the styles while it marks itself disabled without the native attribute. The compiled CSS of the button stays the same.
`button[dma-icon-button]` is a button with an icon and no visible label, after the `Icon button` Figma component. It shares the variants, sizes, and Loading state of the button, and its variant styles through the shared mixin. Each size is a fixed square with no padding. A disabled icon button keeps its tooltip, so the `disabled` input sets `aria-disabled` and blocks clicks itself. The native attribute stays off, even when a template sets it. `aria-label` is required until a tooltip can name the icon button. `IconButtonHarness` finds icon buttons by label, variant, size, and whether they're disabled or loading.
dnd-mapp-bot
left a comment
There was a problem hiding this comment.
- Can we add a utility function for setting up the test environment and providing the test fixture, harness, harnessloader, etc.?
| function resolveColor(token: string | null, context: HTMLElement) { | ||
| return token === null ? transparent : resolveStyle('color', token, context); | ||
| } | ||
|
|
||
| function toColors({ fill, border, icon }: Look, context: HTMLElement) { | ||
| return { | ||
| fill: resolveColor(fill, context), | ||
| border: resolveColor(border, context), | ||
| icon: resolveColor(icon, context), | ||
| }; | ||
| } | ||
|
|
||
| async function getColors(button: IconButtonHarness) { | ||
| const host = await button.host(); | ||
|
|
||
| return { | ||
| fill: await host.getCssValue('background-color'), | ||
| border: await host.getCssValue('border-top-color'), | ||
| // The icon takes the color of the button, through `currentColor`. | ||
| icon: await (await (await button.getIcon()).host()).getCssValue('fill'), | ||
| }; | ||
| } |
There was a problem hiding this comment.
These functions, and the getLiveRegion function, look like they're duplicated from button tests. Can we centralize it?
There was a problem hiding this comment.
Done in f709b7a and f384bf0. resolveColor(), resolveColors(), getColors(), and getLiveRegion() now live in @dnd-mapp/ui/testing, so apps can use them in their own tests too. transparent became the null case of resolveColor().
The Look interfaces stay in each spec, because the two components check different colors: the button checks a label color on its host, and the icon button checks an icon color on the fill of its icon. getColors() and resolveColors() take whatever keys you give them, so each spec maps its own keys with a short getLook() function instead of forcing both components into one shape.
The button and icon button specs each carried the same setup function and the same color and live region helpers. Move them to `@dnd-mapp/ui/testing`, so the specs share them and apps can use them in their own tests. `setupHarness()` creates a host component and loads a harness inside it. `getColors()` and `resolveColors()` read and resolve colors under the same keys, so one component can name its own colors, and `getLiveRegion()` returns the polite live region.
Replace the local setup function, color helpers, and live region helper of the button and icon button specs with the ones from `@dnd-mapp/ui/testing`. Each spec keeps a `getLook()` function, since the button reads its label color from its host and the icon button reads its icon color from the fill of its icon.
|
Added |
Why
Toolbars and panel headers need actions that show only an icon, such as closing a panel. The
Design systemFigma file has anIcon buttoncomponent for this, andButtonpoints to it for any action with only an icon. The package had no code for it yet.What
IconButtonComponent(button[dma-icon-button]) is built after theIcon buttonFigma component. It shares the variants, the sizes, the Loading state, and the colors of every state withButtonComponent.variants($disabled)SCSS mixin, and the icon button includes the same mixin. The Figma spec asks for the two to stay in step, and a shared mixin keeps them in step by construction. The only difference between them is the disabled selector, which the mixin takes as a parameter. The compiled CSS of the button is byte-identical before and after, and the existing button specs pass unchanged.spacing/0padding, and uses the radius of the button in that size. The icon is the projected content and takes the size of the icon button throughICON_SIZE, like the icons in a button.disabledattribute, which drops focus and hover. Thedisabledinput setsaria-disabled="true"and blocks clicks throughblockClicksWhile(). Adata-disabledattribute drives the disabled colors, because loading setsaria-disabledtoo but keeps the Default colors. A host binding keeps the native attribute off, even when a template writesdisabled. A spec covers that case, and it fails without the binding.aria-labelis a required input untilTooltipexists. The Figma spec allows anaria-labelthat matches the tooltip text. Making it optional later won't break consumers.injectLoadingState()and the same grid-cell overlay as the button, socircle-notchreplaces the icon after 300ms.IconButtonHarnesshas thelabel,disabled,variant,size, andloadingfilters, andgetIcon()returns theIconHarnessof the icon.@dnd-mapp/ui/testingassetupHarness(),getColors(),resolveColors(),resolveColor(), andgetLiveRegion(). They sit in a published entry point rather than a spec-only module, because apps that use these components need the same checks in their own tests.getColors()andresolveColors()take whatever color names the caller gives them, mirroringgetFrame()andresolveFrame(). That way the button can checklabeland the icon button can checkiconwithout one shared type for both.Stories mirror the Figma page in both themes, and the MDX doc, the readme, and the changelog describe the component. Adding a component and the testing helpers is a minor bump.
Test plan
pnpm run format-check,lint-md,lint-ts, andtypecheckpnpm run buildpnpm run test-ci: 187 tests pass (75 new), with coverage above the thresholdspnpm run build-storybook, then compare theStatesandSizesstories with theIcon buttonFigma page in the light and the dark themeStates, and check that it takes focus and shows the focus ringNotes
Tooltipexists, the icon button should take its name from the tooltip througharia-labelledby.ButtonVariant,ButtonSize) are reused as they are, rather than aliased under icon button names, since the Figma spec defines the two components with the same values.@dnd-mapp/ui/testingentry point now imports@angular/cdkand@angular/core/testingforsetupHarness(). Both are already peer dependencies. The readme no longer says the entry point works without@angular/cdk.