From caa1abfe6e43a56d89ce2e12b9b2e3f97e9fb519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benny=20Powers=20-=20=D7=A2=D7=9D=20=D7=99=D7=A9=D7=A8?= =?UTF-8?q?=D7=90=D7=9C=20=D7=97=D7=99!?= Date: Mon, 16 Sep 2024 16:59:57 +0300 Subject: [PATCH] feat(button): icon set (#1859) * fix(button): use rh-icon * fix(button): narrow type of icon and set * fix(footer): document accepted icons in social links * chore: max out netlify's card * perf(button): lazy-load icon * fix(button): icon layouts * fix(button): fancy icon layouts, etc * docs(button): icon usage * perf(button): shadow classes --- .changeset/slimy-guests-rescue.md | 9 ++ elements/rh-button/demo/icon.html | 22 +++++ elements/rh-button/docs/20-guidelines.md | 34 ++++---- elements/rh-button/rh-button.css | 92 +++++++++------------ elements/rh-button/rh-button.ts | 48 ++++++----- elements/rh-footer/rh-footer-social-link.ts | 10 ++- netlify.toml | 2 + 7 files changed, 122 insertions(+), 95 deletions(-) create mode 100644 .changeset/slimy-guests-rescue.md create mode 100644 elements/rh-button/demo/icon.html diff --git a/.changeset/slimy-guests-rescue.md b/.changeset/slimy-guests-rescue.md new file mode 100644 index 0000000000..1c1f9469e6 --- /dev/null +++ b/.changeset/slimy-guests-rescue.md @@ -0,0 +1,9 @@ +--- +"@rhds/elements": minor +--- +``: add `icon-set` attribute, corresponding to `` + +```html +Donate +``` diff --git a/elements/rh-button/demo/icon.html b/elements/rh-button/demo/icon.html new file mode 100644 index 0000000000..466bb92457 --- /dev/null +++ b/elements/rh-button/demo/icon.html @@ -0,0 +1,22 @@ +
+ Danger + Primary + Link + Secondary + Secondary Danger + Tertiary + Disabled +
+ + + + diff --git a/elements/rh-button/docs/20-guidelines.md b/elements/rh-button/docs/20-guidelines.md index 73ff75eecb..12b690e673 100644 --- a/elements/rh-button/docs/20-guidelines.md +++ b/elements/rh-button/docs/20-guidelines.md @@ -23,7 +23,7 @@ function to users. Therefore, it is important that each variant is implemented consistently so they communicate the correct actions. - Image of the seven available button variant + Image of the seven available button variant @@ -77,7 +77,7 @@ Use a Play button to indicate that audio or video will play when selected. - Image of play button examples; a video thumbnail on the left and a text layout on the right + Image of play button examples; a video thumbnail on the left and a text layout on the right ### Close button @@ -86,9 +86,13 @@ Use a Close button to indicate that a window will close when selected. Close buttons are mostly found in [dialogs](/elements/dialog/). - Image of a dialog with a close button in the top right corner + Image of a dialog with a close button in the top right corner +### Other icons + +When adding icons, prefer to use the [microns](/icons/#micron-icons) as they fit +the button layout better. ### Disabled @@ -97,7 +101,7 @@ completed first, most buttons can become disabled. However, the Play and Close buttons do not include a disabled state. - Image of five disabled buttons not including play and close buttons, underneath is a dropdown with a disabled button + Image of five disabled buttons not including play and close buttons, underneath is a dropdown with a disabled button ## Writing content @@ -124,7 +128,7 @@ When writing link button text labels, use specific and action-focused language that matches what users will see when they arrive at their location. - Image of link button text labels + Image of link button text labels ### Button vs. call to action text labels @@ -133,7 +137,7 @@ Button text labels are written to be short and communicate an action whereas call to action text labels are written to entice users to select a link. - Image of two buttons on the left and two calls to action on the right + Image of two buttons on the left and two calls to action on the right ### Character and word count @@ -178,7 +182,7 @@ consistent across all contexts elements are stacked vertically - Image of buttons used in a dialog and a form + Image of buttons used in a dialog and a form ### Hierarchy @@ -187,7 +191,7 @@ Buttons are ordered by hierarchy from left to right. Do not use multiple Danger, Primary, or Close buttons in the same area. - Image of buttons grouped by hierarchy from left to right + Image of buttons grouped by hierarchy from left to right ### Grouping @@ -196,7 +200,7 @@ Grouping buttons is a useful way of aligning buttons that have a relationship. Group buttons logically into sets based on hierarchy and usage. - Image of button groups and their hierarchy from left to right + Image of button groups and their hierarchy from left to right ### Space in groups @@ -206,7 +210,7 @@ buttons. If buttons are stacked, the spacing between each button should be `8px`. - Image of button groups and their horizontal and vertical spacing in between each button + Image of button groups and their horizontal and vertical spacing in between each button ## Best practices @@ -216,7 +220,7 @@ buttons. If buttons are stacked, the spacing between each button should be Buttons should never have more than one line of text. - Image of a button with two lines of text which is incorrect usage + Image of a button with two lines of text which is incorrect usage ### Multiple buttons @@ -224,7 +228,7 @@ Buttons should never have more than one line of text. Do not use multiple Danger or Primary buttons in the same area. - Image of two danger and two primary button groups which is incorrect usage + Image of two danger and two primary button groups which is incorrect usage ### Text labels @@ -232,7 +236,7 @@ Do not use multiple Danger or Primary buttons in the same area. Do not write button text labels that are expressive or ambiguous. - Image of two buttons; one has expressive language and the other has ambiguous language which is incorrect usage + Image of two buttons; one has expressive language and the other has ambiguous language which is incorrect usage ### Danger button @@ -240,7 +244,7 @@ Do not write button text labels that are expressive or ambiguous. Do not use a Danger button for non-destructive purposes. - Image of a search bar using a danger button which is incorrect usage + Image of a search bar using a danger button which is incorrect usage ### Button as a call to action @@ -249,5 +253,5 @@ Do not use buttons as links or change the Primary button styling, use a link or call to action instead. - Image of text styles with a button underneath that resembles a call to action which is incorrect usage + Image of text styles with a button underneath that resembles a call to action which is incorrect usage diff --git a/elements/rh-button/rh-button.css b/elements/rh-button/rh-button.css index fbfb11f1d3..2d4aa23bdc 100644 --- a/elements/rh-button/rh-button.css +++ b/elements/rh-button/rh-button.css @@ -60,14 +60,13 @@ button > span { .hasIcon { position: relative; display: flex; + gap: var(--rh-space-sm, 6px); align-items: center; -} + padding-inline-start: calc(var(--rh-space-lg, 16px) * 0.75); -.hasIcon [part='icon'] { - display: inline-flex; - align-items: center; - position: absolute; - width: 16px; + & [part='icon'] { + display: contents; + } } [part='icon'] ::slotted(*) { @@ -77,11 +76,6 @@ button > span { max-height: 16px; } -.hasIcon button { - position: absolute; - inset: 0; -} - .light { /* PRIMARY */ --_primary-color: var(--rh-color-text-primary-on-dark, #ffffff); @@ -277,11 +271,6 @@ button > span { --_play-hover-background-color: rgb(var(--_white-rgb) / var(--rh-opacity-80, 80%)); } -:host([hidden]), -[hidden] { - display: none !important; -} - rh-icon { color: currentcolor; } @@ -328,7 +317,7 @@ button:hover { * * ******************************/ -:host([danger]) button { +button.danger { --_default-color: var(--_danger-color); --_default-background-color: var(--_danger-background-color); --_default-border-color: var(--_danger-border-color); @@ -349,7 +338,7 @@ button:hover { * * ******************************/ -:host([variant='secondary' i]) button { +button.secondary { --_default-color: var(--_secondary-color); --_default-background-color: var(--_secondary-background-color); --_default-border-color: var(--_secondary-border-color); @@ -373,7 +362,7 @@ button:hover { * * ******************************/ -:host([variant='tertiary' i]) button { +button.tertiary { --_default-color: var(--_tertiary-color); --_default-background-color: var(--_tertiary-background-color); --_default-border-color: var(--_tertiary-border-color); @@ -396,8 +385,12 @@ button:hover { * * ******************************/ -:host([variant='link' i]) button { - display: inline; +button.link { + display: inline-flex; + + & rh-icon { + order: 1; + } --_default-color: var(--_link-color); --_default-background-color: var(--_link-background-color); @@ -420,7 +413,7 @@ button:hover { * * ******************************/ -:host([variant='close' i]) button { +button.close { --_default-color: var(--_close-color); --_default-background-color: var(--_close-background-color); --_active-color: var(--_close-active-color); @@ -444,10 +437,11 @@ button:hover { * * ******************************/ -:host([variant='play' i]) button { +button.play { border-radius: 100%; width: var(--rh-length-4xl, 64px); + --rh-icon-size: var(--rh-size-icon-02, 24px); --_default-color: var(--_play-color); --_default-background-color: var(--_play-background-color); --_default-background-opacity: var(--_play-background-opacity); @@ -462,44 +456,40 @@ button:hover { --_hover-background-color: var(--_play-hover-background-color); --_hover-background-opacity: var(--_play-hover-background-opacity); --_icon-size: var(--rh-size-icon-02, 24px); -} -:host(:is([variant='play'])) [part='icon'] { - position: relative; - inset-inline-start: 3px; + & [part='icon'] { + display: contents; + } + + & rh-icon { + translate: 10%; /* perceptually center play icon only */ + } } -:host(:is([variant='play' i], [variant='close' i])) button { +button:is(.play,.close) { aspect-ratio: 1; display: inline-flex; align-items: center; justify-content: center; padding: 0; -} - -:host(:is([variant='play' i], [variant='close' i])) [part='icon'] { - display: inline-block; - width: var(--_icon-size, var(--rh-size-icon-01, 16px)); - height: var(--_icon-size, var(--rh-size-icon-01, 16px)); -} -:host(:is([variant='play' i], [variant='close' i])) svg { - fill: currentcolor; - stroke: currentcolor; + /* visually hidden */ + & #text { + display: inline; + position: absolute !important; + width: 1px !important; + height: 1px !important; + padding: 0 !important; + margin: -1px !important; + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + white-space: nowrap !important; + border: 0 !important; + } } -/* visually hidden */ -:host(:is([variant='play' i], [variant='close' i])) #text { - display: inline; - position: absolute !important; - width: 1px !important; - height: 1px !important; - padding: 0 !important; - margin: -1px !important; - overflow: hidden !important; - clip: rect(0, 0, 0, 0) !important; - white-space: nowrap !important; - border: 0 !important; +:host(:is([variant='play' i], [variant='close' i])) { + line-height: 0; } /****************************** @@ -534,7 +524,7 @@ button[disabled] { * * ******************************/ -:host([variant='secondary' i][danger]) button { +button.secondary.danger { --_default-color: var(--_secondary-danger-color); --_default-background-color: transparent; --_default-border-color: var(--_danger-background-color); diff --git a/elements/rh-button/rh-button.ts b/elements/rh-button/rh-button.ts index 2180887162..f140efda0d 100644 --- a/elements/rh-button/rh-button.ts +++ b/elements/rh-button/rh-button.ts @@ -1,3 +1,5 @@ +import type { IconNameFor, IconSetName } from '@rhds/icons'; + import { LitElement, html, type TemplateResult } from 'lit'; import { customElement } from 'lit/decorators/custom-element.js'; import { property } from 'lit/decorators/property.js'; @@ -47,7 +49,10 @@ export class RhButton extends LitElement { @property() name?: string; /** Shorthand for the `icon` slot, the value is icon name */ - @property() icon?: string; + @property() icon?: IconNameFor; + + /** Icon set for the `icon` property - 'ui' by default */ + @property({ attribute: 'icon-set' }) iconSet?: IconSetName; @query('button') private _button!: HTMLButtonElement; @@ -78,34 +83,37 @@ export class RhButton extends LitElement { @colorContextConsumer() private on?: ColorTheme; get #hasIcon() { - return !!this.icon; + return this.variant === 'play' || this.variant === 'close' || !!this.icon; } #internals = InternalsController.of(this); override willUpdate() { - const variant = this.variant.toLowerCase(); - switch (variant) { - case 'close': - case 'play': - this.icon = variant; - break; + if (this.#hasIcon) { + import('@rhds/elements/rh-icon/rh-icon.js'); } } override render() { - const { on = 'light' } = this; + const { danger, variant, on = 'light' } = this; const hasIcon = this.#hasIcon; return html` @@ -133,24 +141,14 @@ export class RhButton extends LitElement { * * ``` */ - #renderDefaultIcon(): TemplateResult | string { + #renderIcon(): TemplateResult { switch (this.variant.toLowerCase()) { - // TODO: revisit when rh-icon is ready - // return html``; case 'close': - return html` - - - - `; + return html``; case 'play': - return html` - - - - `; + return html``; default: - return ''; + return html``; } } diff --git a/elements/rh-footer/rh-footer-social-link.ts b/elements/rh-footer/rh-footer-social-link.ts index 55a64d6f6d..1e19863cb7 100644 --- a/elements/rh-footer/rh-footer-social-link.ts +++ b/elements/rh-footer/rh-footer-social-link.ts @@ -1,3 +1,5 @@ +import type { IconNameFor } from '@rhds/icons'; + import { LitElement, html } from 'lit'; import { customElement } from 'lit/decorators/custom-element.js'; import { property } from 'lit/decorators/property.js'; @@ -10,7 +12,8 @@ import style from './rh-footer-social-link.css'; export class RhFooterSocialLink extends LitElement { static readonly styles = style; - @property() icon?: string; + /** Icon for this social link e.g. `'facebook'` */ + @property() icon?: IconNameFor<'social'>; #logger = new Logger(this); @@ -31,9 +34,8 @@ export class RhFooterSocialLink extends LitElement { newDiv.querySelectorAll('[_rendered]').forEach(i => i.remove()); // NB: icons are restricted to social set, so as not to require a minor release // rh-icon is slated to deal with this problem in-house - newDiv.innerHTML = `${newDiv.innerHTML}`; + newDiv.innerHTML = + `${newDiv.innerHTML}`; // add a11y settings newDiv.setAttribute('aria-label', newDiv.textContent || ''); if (!newDiv.getAttribute('aria-label')) { diff --git a/netlify.toml b/netlify.toml index 137d7761f6..681d0861e9 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,5 +1,7 @@ [build] command = "npm run docs" +[build.environment] + NODE_OPTIONS = "--max_old_space_size=8192" [[redirects]] from = '/components/get-started/*'