From 96f32122c0b7aacd7b15fbad8944fcafe335fc5a Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Fri, 24 Jul 2020 16:57:38 -0400 Subject: [PATCH] Add icon button --- docs/_sidebar.md | 1 + docs/components/icon-button.md | 60 +++++++++++++++++++++ src/components.d.ts | 45 ++++++++++++++++ src/components/alert/alert.scss | 26 +-------- src/components/alert/alert.tsx | 14 +---- src/components/dialog/dialog.scss | 23 -------- src/components/dialog/dialog.tsx | 8 +-- src/components/drawer/drawer.scss | 23 -------- src/components/drawer/drawer.tsx | 8 +-- src/components/icon-button/icon-button.scss | 42 +++++++++++++++ src/components/icon-button/icon-button.tsx | 54 +++++++++++++++++++ src/components/tag/tag.scss | 12 ++--- src/components/tag/tag.tsx | 4 +- 13 files changed, 210 insertions(+), 110 deletions(-) create mode 100644 docs/components/icon-button.md create mode 100644 src/components/icon-button/icon-button.scss create mode 100644 src/components/icon-button/icon-button.tsx diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 86dd8f062..5f0fbe19c 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -21,6 +21,7 @@ - [Dropdown](/components/dropdown.md) - [Form](/components/form.md) - [Icon](/components/icon.md) + - [Icon Button](/components/icon-button.md) - [Input](/components/input.md) - [Menu](/components/menu.md) - [Menu Divider](/components/menu-divider.md) diff --git a/docs/components/icon-button.md b/docs/components/icon-button.md new file mode 100644 index 000000000..5c8ae1e98 --- /dev/null +++ b/docs/components/icon-button.md @@ -0,0 +1,60 @@ +# Icon Button + +[component-header:sl-icon-button] + +Icons buttons are simple, icon-only buttons that can be used for actions and in toolbars. + +For a full list of icons that come bundled with Shoelace, refer to the [icon component](/components/icon). + +```html preview + + + +``` + +## Examples + +### Sizes + +Icon buttons inherit their parent element's `font-size`. + +```html preview + + + +``` + +### Colors + +You can customize icon button's color by styling its `base` part. + +```html preview + + + +``` + +### Icon Button with Tooltip + +Wrap a tooltip around an icon button to provide contextual information to the user. + +```html preview + + + +``` + +### Disabled +```html preview + +``` + +[component-metadata:sl-icon-button] diff --git a/src/components.d.ts b/src/components.d.ts index e06086b49..d86a965aa 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -319,6 +319,24 @@ export namespace Components { */ "src": string; } + interface SlIconButton { + /** + * Set to true to disable the button. + */ + "disabled": boolean; + /** + * An alternative description to use for accessibility. If omitted, the name or src will be used to generate it. + */ + "label": string; + /** + * The name of the icon to draw. See the icon component for a full list of icons. + */ + "name": string; + /** + * An external URL of an SVG file. + */ + "src": string; + } interface SlInput { /** * The input's autocaptialize attribute. @@ -956,6 +974,12 @@ declare global { prototype: HTMLSlIconElement; new (): HTMLSlIconElement; }; + interface HTMLSlIconButtonElement extends Components.SlIconButton, HTMLStencilElement { + } + var HTMLSlIconButtonElement: { + prototype: HTMLSlIconButtonElement; + new (): HTMLSlIconButtonElement; + }; interface HTMLSlInputElement extends Components.SlInput, HTMLStencilElement { } var HTMLSlInputElement: { @@ -1084,6 +1108,7 @@ declare global { "sl-dropdown": HTMLSlDropdownElement; "sl-form": HTMLSlFormElement; "sl-icon": HTMLSlIconElement; + "sl-icon-button": HTMLSlIconButtonElement; "sl-input": HTMLSlInputElement; "sl-menu": HTMLSlMenuElement; "sl-menu-divider": HTMLSlMenuDividerElement; @@ -1491,6 +1516,24 @@ declare namespace LocalJSX { */ "src"?: string; } + interface SlIconButton { + /** + * Set to true to disable the button. + */ + "disabled"?: boolean; + /** + * An alternative description to use for accessibility. If omitted, the name or src will be used to generate it. + */ + "label"?: string; + /** + * The name of the icon to draw. See the icon component for a full list of icons. + */ + "name"?: string; + /** + * An external URL of an SVG file. + */ + "src"?: string; + } interface SlInput { /** * The input's autocaptialize attribute. @@ -2082,6 +2125,7 @@ declare namespace LocalJSX { "sl-dropdown": SlDropdown; "sl-form": SlForm; "sl-icon": SlIcon; + "sl-icon-button": SlIconButton; "sl-input": SlInput; "sl-menu": SlMenu; "sl-menu-divider": SlMenuDivider; @@ -2120,6 +2164,7 @@ declare module "@stencil/core" { "sl-dropdown": LocalJSX.SlDropdown & JSXBase.HTMLAttributes; "sl-form": LocalJSX.SlForm & JSXBase.HTMLAttributes; "sl-icon": LocalJSX.SlIcon & JSXBase.HTMLAttributes; + "sl-icon-button": LocalJSX.SlIconButton & JSXBase.HTMLAttributes; "sl-input": LocalJSX.SlInput & JSXBase.HTMLAttributes; "sl-menu": LocalJSX.SlMenu & JSXBase.HTMLAttributes; "sl-menu-divider": LocalJSX.SlMenuDivider & JSXBase.HTMLAttributes; diff --git a/src/components/alert/alert.scss b/src/components/alert/alert.scss index e7f4f8df3..4911db012 100644 --- a/src/components/alert/alert.scss +++ b/src/components/alert/alert.scss @@ -90,30 +90,6 @@ flex: 0 0 auto; display: flex; align-items: center; - background: none; - border: none; - border-radius: var(--sl-border-radius-small); - font-family: inherit; font-size: var(--sl-font-size-large); - font-weight: inherit; - color: var(--sl-color-gray-50); - padding: 0 var(--sl-spacing-large); - cursor: pointer; - transition: var(--sl-transition-fast) color; - -webkit-appearance: none; - - &:hover, - &:focus, - &:active { - color: var(--sl-color-primary-50); - } - - &:focus { - outline: none; - } -} - -.focus-visible .alert__close:focus { - box-shadow: 0 0 0 var(--sl-focus-ring-width) - hsla(var(--sl-color-primary-hue), var(--sl-color-primary-saturation), 50%, var(--sl-focus-ring-alpha)); + padding: 0 var(--sl-spacing-medium); } diff --git a/src/components/alert/alert.tsx b/src/components/alert/alert.tsx index 90acfa4b5..ac77b6715 100644 --- a/src/components/alert/alert.tsx +++ b/src/components/alert/alert.tsx @@ -1,5 +1,4 @@ import { Component, Element, Event, EventEmitter, Host, Method, Prop, Watch, h } from '@stencil/core'; -import { focusVisible } from '../../utilities/focus-visible'; /** * @since 2.0 @@ -7,7 +6,6 @@ import { focusVisible } from '../../utilities/focus-visible'; * * @slot - The alert's content. * @slot icon - An icon to show in the alert. - * @slot close-icon - An icon to use in lieu of the default close icon. * * @part base - The component's base wrapper. * @part icon - The container that wraps the alert icon. @@ -57,18 +55,12 @@ export class Tab { } componentDidLoad() { - focusVisible.observe(this.alert); - // Show on init if open if (this.open) { this.show(); } } - componentDidUnload() { - focusVisible.unobserve(this.alert); - } - /** Shows the alert. */ @Method() async show() { @@ -140,11 +132,7 @@ export class Tab { {this.closable && ( - + )} diff --git a/src/components/dialog/dialog.scss b/src/components/dialog/dialog.scss index dd59083d5..14d521984 100644 --- a/src/components/dialog/dialog.scss +++ b/src/components/dialog/dialog.scss @@ -72,31 +72,8 @@ flex: 0 0 auto; display: flex; align-items: center; - background: none; - border: none; - border-radius: var(--sl-border-radius-small); - font-family: inherit; font-size: var(--sl-font-size-x-large); - font-weight: inherit; - color: var(--sl-color-gray-50); padding: 0 var(--sl-spacing-large); - cursor: pointer; - transition: var(--sl-transition-fast) color; - -webkit-appearance: none; - - &:hover, - &:focus, - &:active { - color: var(--sl-color-primary-50); - } - - &:focus { - outline: none; - } -} - -.focus-visible .dialog__close:focus { - box-shadow: var(--sl-focus-ring-box-shadow); } .dialog__body { diff --git a/src/components/dialog/dialog.tsx b/src/components/dialog/dialog.tsx index 953e1eb09..fbf36eb1c 100644 --- a/src/components/dialog/dialog.tsx +++ b/src/components/dialog/dialog.tsx @@ -1,6 +1,5 @@ import { Component, Element, Event, EventEmitter, Method, Prop, State, Watch, h } from '@stencil/core'; import { lockBodyScrolling, unlockBodyScrolling } from '../../utilities/scroll'; -import { focusVisible } from '../../utilities/focus-visible'; import { hasSlot } from '../../utilities/slot'; let id = 0; @@ -85,8 +84,6 @@ export class Dialog { } componentDidLoad() { - focusVisible.observe(this.dialog); - // Show on init if open if (this.open) { this.show(); @@ -94,7 +91,6 @@ export class Dialog { } componentDidUnload() { - focusVisible.unobserve(this.dialog); unlockBodyScrolling(this.host); this.host.shadowRoot.removeEventListener('slotchange', this.updateSlots); @@ -209,9 +205,7 @@ export class Dialog { {/* If there's no label, use an invisible character to prevent the heading from collapsing */} {this.label || String.fromCharCode(65279)} - + )} diff --git a/src/components/drawer/drawer.scss b/src/components/drawer/drawer.scss index a5be45547..612cf77e1 100644 --- a/src/components/drawer/drawer.scss +++ b/src/components/drawer/drawer.scss @@ -110,31 +110,8 @@ flex: 0 0 auto; display: flex; align-items: center; - background: none; - border: none; - border-radius: var(--sl-border-radius-small); - font-family: inherit; font-size: var(--sl-font-size-x-large); - font-weight: inherit; - color: var(--sl-color-gray-50); padding: 0 var(--sl-spacing-large); - cursor: pointer; - transition: var(--sl-transition-fast) color; - -webkit-appearance: none; - - &:hover, - &:focus, - &:active { - color: var(--sl-color-primary-50); - } - - &:focus { - outline: none; - } -} - -.focus-visible .drawer__close:focus { - box-shadow: var(--sl-focus-ring-box-shadow); } .drawer__body { diff --git a/src/components/drawer/drawer.tsx b/src/components/drawer/drawer.tsx index 502859ff5..3065f874b 100644 --- a/src/components/drawer/drawer.tsx +++ b/src/components/drawer/drawer.tsx @@ -1,6 +1,5 @@ import { Component, Element, Event, EventEmitter, Method, Prop, State, Watch, h } from '@stencil/core'; import { lockBodyScrolling, unlockBodyScrolling } from '../../utilities/scroll'; -import { focusVisible } from '../../utilities/focus-visible'; import { hasSlot } from '../../utilities/slot'; let id = 0; @@ -93,8 +92,6 @@ export class Drawer { } componentDidLoad() { - focusVisible.observe(this.drawer); - // Show on init if open if (this.open) { this.show(); @@ -102,7 +99,6 @@ export class Drawer { } componentDidUnload() { - focusVisible.unobserve(this.drawer); unlockBodyScrolling(this.host); this.host.shadowRoot.removeEventListener('slotchange', this.updateSlots); @@ -229,9 +225,7 @@ export class Drawer { {/* If there's no label, use an invisible character to prevent the heading from collapsing */} {this.label || String.fromCharCode(65279)} - + )} diff --git a/src/components/icon-button/icon-button.scss b/src/components/icon-button/icon-button.scss new file mode 100644 index 000000000..89a1d88aa --- /dev/null +++ b/src/components/icon-button/icon-button.scss @@ -0,0 +1,42 @@ +@import 'component'; + +:host { + display: inline-block; +} + +.icon-button { + flex: 0 0 auto; + display: flex; + align-items: center; + background: none; + border: none; + border-radius: var(--sl-border-radius-medium); + font-size: inherit; + color: var(--sl-color-gray-50); + padding: var(--sl-spacing-x-small); + cursor: pointer; + transition: var(--sl-transition-medium) color; + -webkit-appearance: none; + + &:hover:not(.icon-button--disabled), + &:focus:not(.icon-button--disabled) { + color: var(--sl-color-primary-50); + } + + &:active:not(.icon-button--disabled) { + color: var(--sl-color-primary-40); + } + + &:focus { + outline: none; + } +} + +.icon-button--disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.focus-visible.icon-button:focus { + box-shadow: var(--sl-focus-ring-box-shadow); +} diff --git a/src/components/icon-button/icon-button.tsx b/src/components/icon-button/icon-button.tsx new file mode 100644 index 000000000..c786ec2b7 --- /dev/null +++ b/src/components/icon-button/icon-button.tsx @@ -0,0 +1,54 @@ +import { Component, Prop, h } from '@stencil/core'; +import { focusVisible } from '../../utilities/focus-visible'; + +/** + * @since 2.0 + * @status stable + * + * @part base - The component's base wrapper. + */ + +@Component({ + tag: 'sl-icon-button', + styleUrl: 'icon-button.scss', + shadow: true +}) +export class IconButton { + button: HTMLButtonElement; + + /** The name of the icon to draw. See the icon component for a full list of icons. */ + @Prop() name: string; + + /** An external URL of an SVG file. */ + @Prop() src: string; + + /** An alternative description to use for accessibility. If omitted, the name or src will be used to generate it. */ + @Prop() label: string; + + /** Set to true to disable the button. */ + @Prop() disabled = false; + + componentDidLoad() { + focusVisible.observe(this.button); + } + + componentDidUnload() { + focusVisible.unobserve(this.button); + } + + render() { + return ( + + ); + } +} diff --git a/src/components/tag/tag.scss b/src/components/tag/tag.scss index 59375f4ec..5704317d1 100644 --- a/src/components/tag/tag.scss +++ b/src/components/tag/tag.scss @@ -14,15 +14,9 @@ cursor: default; } -.tag__clear { - display: flex; - align-items: center; - border-radius: var(--sl-border-radius-small); - cursor: pointer; - - &:focus { - outline: none; - } +.tag__clear::part(base) { + color: inherit; + padding: 0; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/components/tag/tag.tsx b/src/components/tag/tag.tsx index bd69e15a6..94bac5ead 100644 --- a/src/components/tag/tag.tsx +++ b/src/components/tag/tag.tsx @@ -73,9 +73,7 @@ export class Tag { {this.clearable && ( - - - + )} );