feat(ui-shell): support button tooltip in HeaderGlobalAction (#1894)

Closes #1893
This commit is contained in:
spburtsev 2024-03-08 06:37:58 +02:00 committed by GitHub
commit d8bc65163e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 92 additions and 90 deletions

View file

@ -379,7 +379,7 @@ export type BreakpointValue = 320 | 672 | 1056 | 1312 | 1584;
| size | No | <code>let</code> | No | <code>"default" &#124; "field" &#124; "small" &#124; "lg" &#124; "xl"</code> | <code>"default"</code> | Specify the size of button | | size | No | <code>let</code> | No | <code>"default" &#124; "field" &#124; "small" &#124; "lg" &#124; "xl"</code> | <code>"default"</code> | Specify the size of button |
| expressive | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to use Carbon's expressive typesetting | | expressive | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to use Carbon's expressive typesetting |
| isSelected | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to enable the selected state for an icon-only, ghost button | | isSelected | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to enable the selected state for an icon-only, ghost button |
| icon | No | <code>let</code> | No | <code>typeof import("svelte").SvelteComponent<any></code> | <code>undefined</code> | Specify the icon to render | | icon | No | <code>let</code> | No | <code>typeof import("svelte").SvelteComponent<any></code> | <code>undefined</code> | Specify the icon to render<br />Alternatively, use the named slot "icon" (e.g., `&lt;Icon slot="icon" size="{20}" /&gt;`) |
| iconDescription | No | <code>let</code> | No | <code>string</code> | <code>undefined</code> | Specify the ARIA label for the button icon | | iconDescription | No | <code>let</code> | No | <code>string</code> | <code>undefined</code> | Specify the ARIA label for the button icon |
| tooltipAlignment | No | <code>let</code> | No | <code>"start" &#124; "center" &#124; "end"</code> | <code>"center"</code> | Set the alignment of the tooltip relative to the icon.<br />Only applies to icon-only buttons | | tooltipAlignment | No | <code>let</code> | No | <code>"start" &#124; "center" &#124; "end"</code> | <code>"center"</code> | Set the alignment of the tooltip relative to the icon.<br />Only applies to icon-only buttons |
| tooltipPosition | No | <code>let</code> | No | <code>"top" &#124; "right" &#124; "bottom" &#124; "left"</code> | <code>"bottom"</code> | Set the position of the tooltip relative to the icon | | tooltipPosition | No | <code>let</code> | No | <code>"top" &#124; "right" &#124; "bottom" &#124; "left"</code> | <code>"bottom"</code> | Set the position of the tooltip relative to the icon |
@ -395,6 +395,7 @@ export type BreakpointValue = 320 | 672 | 1056 | 1312 | 1584;
| Slot name | Default | Props | Fallback | | Slot name | Default | Props | Fallback |
| :-------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------- | :------- | | :-------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------- | :------- |
| -- | Yes | <code>{ props: { role: "button"; type?: string; tabindex: any; disabled: boolean; href?: string; class: string; [key: string]: any; } } </code> | -- | | -- | Yes | <code>{ props: { role: "button"; type?: string; tabindex: any; disabled: boolean; href?: string; class: string; [key: string]: any; } } </code> | -- |
| icon | No | -- | -- |
### Events ### Events
@ -1666,15 +1667,13 @@ None.
| Prop name | Required | Kind | Reactive | Type | Default value | Description | | Prop name | Required | Kind | Reactive | Type | Default value | Description |
| :-------- | :------- | :--------------- | :------- | --------------------------------------------------------- | ---------------------- | --------------------------------------------- | | :-------- | :------- | :--------------- | :------- | --------------------------------------------------------- | ---------------------- | --------------------------------------------- |
| ref | No | <code>let</code> | Yes | <code>null &#124; HTMLButtonElement</code> | <code>null</code> | Obtain a reference to the HTML button element | | ref | No | <code>let</code> | Yes | <code>HTMLButtonElement</code> | <code>null</code> | Obtain a reference to the HTML button element |
| isActive | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to use the active variant | | isActive | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to use the active variant |
| icon | No | <code>let</code> | No | <code>typeof import("svelte").SvelteComponent<any></code> | <code>undefined</code> | Specify the icon to render | | icon | No | <code>let</code> | No | <code>typeof import("svelte").SvelteComponent<any></code> | <code>undefined</code> | Specify the icon to render |
### Slots ### Slots
| Slot name | Default | Props | Fallback | None.
| :-------- | :------ | :---- | :---------------------------------------------------------------- |
| -- | Yes | -- | <code>&lt;svelte:component this="{icon}" size="{20}" /&gt;</code> |
### Events ### Events

View file

@ -500,7 +500,7 @@
{ {
"name": "icon", "name": "icon",
"kind": "let", "kind": "let",
"description": "Specify the icon to render", "description": "Specify the icon to render\nAlternatively, use the named slot \"icon\" (e.g., `<Icon slot=\"icon\" size=\"{20}\" />`)",
"type": "typeof import(\"svelte\").SvelteComponent<any>", "type": "typeof import(\"svelte\").SvelteComponent<any>",
"isFunction": false, "isFunction": false,
"isFunctionDeclaration": false, "isFunctionDeclaration": false,
@ -633,7 +633,8 @@
"name": "__default__", "name": "__default__",
"default": true, "default": true,
"slot_props": "{ props: { role: \"button\"; type?: string; tabindex: any; disabled: boolean; href?: string; class: string; [key: string]: any; } }" "slot_props": "{ props: { role: \"button\"; type?: string; tabindex: any; disabled: boolean; href?: string; class: string; [key: string]: any; } }"
} },
{ "name": "icon", "default": false, "slot_props": "{}" }
], ],
"events": [ "events": [
{ "type": "forwarded", "name": "click", "element": "ButtonSkeleton" }, { "type": "forwarded", "name": "click", "element": "ButtonSkeleton" },
@ -5103,7 +5104,7 @@
"name": "ref", "name": "ref",
"kind": "let", "kind": "let",
"description": "Obtain a reference to the HTML button element", "description": "Obtain a reference to the HTML button element",
"type": "null | HTMLButtonElement", "type": "HTMLButtonElement",
"value": "null", "value": "null",
"isFunction": false, "isFunction": false,
"isFunctionDeclaration": false, "isFunctionDeclaration": false,
@ -5113,17 +5114,14 @@
} }
], ],
"moduleExports": [], "moduleExports": [],
"slots": [ "slots": [],
{ "events": [{ "type": "forwarded", "name": "click", "element": "Button" }],
"name": "__default__",
"default": true,
"fallback": "<svelte:component this=\"{icon}\" size=\"{20}\" />",
"slot_props": "{}"
}
],
"events": [{ "type": "forwarded", "name": "click", "element": "button" }],
"typedefs": [], "typedefs": [],
"rest_props": { "type": "Element", "name": "button" } "rest_props": { "type": "InlineComponent", "name": "Button" },
"extends": {
"interface": "ButtonProps",
"import": "\"../Button/Button.svelte\""
}
}, },
{ {
"moduleName": "HeaderNav", "moduleName": "HeaderNav",

View file

@ -2,11 +2,7 @@
import { import {
Header, Header,
HeaderUtilities, HeaderUtilities,
HeaderAction,
HeaderGlobalAction, HeaderGlobalAction,
HeaderPanelLinks,
HeaderPanelDivider,
HeaderPanelLink,
SideNav, SideNav,
SideNavItems, SideNavItems,
SideNavMenu, SideNavMenu,
@ -18,12 +14,11 @@
Row, Row,
Column, Column,
} from "carbon-components-svelte"; } from "carbon-components-svelte";
import Logout from "carbon-icons-svelte/lib/Logout.svelte";
import SettingsAdjust from "carbon-icons-svelte/lib/SettingsAdjust.svelte"; import SettingsAdjust from "carbon-icons-svelte/lib/SettingsAdjust.svelte";
import UserAvatarFilledAlt from "carbon-icons-svelte/lib/UserAvatarFilledAlt.svelte"; import UserAvatarFilledAlt from "carbon-icons-svelte/lib/UserAvatarFilledAlt.svelte";
let isSideNavOpen = false; let isSideNavOpen = false;
let isOpen1 = false;
let isOpen2 = false;
</script> </script>
<Header company="IBM" platformName="Carbon Svelte" bind:isSideNavOpen> <Header company="IBM" platformName="Carbon Svelte" bind:isSideNavOpen>
@ -31,37 +26,20 @@
<SkipToContent /> <SkipToContent />
</svelte:fragment> </svelte:fragment>
<HeaderUtilities> <HeaderUtilities>
<HeaderGlobalAction aria-label="Settings" icon="{SettingsAdjust}" /> <HeaderGlobalAction
<HeaderAction iconDescription="Settings"
bind:isOpen="{isOpen1}" tooltipAlignment="start"
icon="{SettingsAdjust}"
/>
<HeaderGlobalAction
iconDescription="Profile"
icon="{UserAvatarFilledAlt}" icon="{UserAvatarFilledAlt}"
closeIcon="{UserAvatarFilledAlt}" />
> <HeaderGlobalAction
<HeaderPanelLinks> iconDescription="Log out"
<HeaderPanelDivider>Switcher subject 1</HeaderPanelDivider> tooltipAlignment="end"
<HeaderPanelLink>Switcher item 1</HeaderPanelLink> icon="{Logout}"
<HeaderPanelLink>Switcher item 2</HeaderPanelLink> />
<HeaderPanelLink>Switcher item 3</HeaderPanelLink>
<HeaderPanelLink>Switcher item 4</HeaderPanelLink>
<HeaderPanelDivider>Switcher subject 2</HeaderPanelDivider>
<HeaderPanelLink>Switcher item 1</HeaderPanelLink>
<HeaderPanelLink>Switcher item 2</HeaderPanelLink>
<HeaderPanelDivider>Switcher subject 3</HeaderPanelDivider>
<HeaderPanelLink>Switcher item 1</HeaderPanelLink>
</HeaderPanelLinks>
</HeaderAction>
<HeaderAction bind:isOpen="{isOpen2}">
<HeaderPanelLinks>
<HeaderPanelDivider>Switcher subject 1</HeaderPanelDivider>
<HeaderPanelLink>Switcher item 1</HeaderPanelLink>
<HeaderPanelDivider>Switcher subject 2</HeaderPanelDivider>
<HeaderPanelLink>Switcher item 1</HeaderPanelLink>
<HeaderPanelLink>Switcher item 2</HeaderPanelLink>
<HeaderPanelLink>Switcher item 3</HeaderPanelLink>
<HeaderPanelLink>Switcher item 4</HeaderPanelLink>
<HeaderPanelLink>Switcher item 5</HeaderPanelLink>
</HeaderPanelLinks>
</HeaderAction>
</HeaderUtilities> </HeaderUtilities>
</Header> </Header>

View file

@ -27,6 +27,8 @@
/** /**
* Specify the icon to render * Specify the icon to render
* Alternatively, use the named slot "icon" (e.g., `<Icon slot="icon" size="{20}" />`)
*
* @type {typeof import("svelte").SvelteComponent<any>} * @type {typeof import("svelte").SvelteComponent<any>}
*/ */
export let icon = undefined; export let icon = undefined;
@ -85,7 +87,12 @@
$: if (ctx && ref) { $: if (ctx && ref) {
ctx.declareRef(ref); ctx.declareRef(ref);
} }
$: hasIconOnly = icon && !$$slots.default; $: hasIconOnly = (icon || $$slots.icon) && !$$slots.default;
$: iconProps = {
"aria-hidden": "true",
class: "bx--btn__icon",
"aria-label": iconDescription,
};
$: buttonProps = { $: buttonProps = {
type: href && !disabled ? undefined : type, type: href && !disabled ? undefined : type,
tabindex, tabindex,
@ -158,12 +165,20 @@
{#if hasIconOnly} {#if hasIconOnly}
<span class:bx--assistive-text="{true}">{iconDescription}</span> <span class:bx--assistive-text="{true}">{iconDescription}</span>
{/if} {/if}
<slot /><svelte:component <slot />
this="{icon}" {#if $$slots.icon}
aria-hidden="true" <slot
class="bx--btn__icon" name="icon"
aria-label="{iconDescription}" style="{hasIconOnly ? 'margin-left: 0' : undefined}"
/> {...iconProps}
/>
{:else if icon}
<svelte:component
this="{icon}"
style="{hasIconOnly ? 'margin-left: 0' : undefined}"
{...iconProps}
/>
{/if}
</a> </a>
{:else} {:else}
<button <button
@ -179,12 +194,19 @@
{#if hasIconOnly} {#if hasIconOnly}
<span class:bx--assistive-text="{true}">{iconDescription}</span> <span class:bx--assistive-text="{true}">{iconDescription}</span>
{/if} {/if}
<slot /><svelte:component <slot />
this="{icon}" {#if $$slots.icon}
aria-hidden="true" <slot
class="bx--btn__icon" name="icon"
style="{hasIconOnly ? 'margin-left: 0' : undefined}" style="{hasIconOnly ? 'margin-left: 0' : undefined}"
aria-label="{iconDescription}" {...iconProps}
/> />
{:else if icon}
<svelte:component
this="{icon}"
style="{hasIconOnly ? 'margin-left: 0' : undefined}"
{...iconProps}
/>
{/if}
</button> </button>
{/if} {/if}

View file

@ -1,4 +1,8 @@
<script> <script>
/**
* @extends {"../Button/Button.svelte"} ButtonProps
*/
/** Set to `true` to use the active variant */ /** Set to `true` to use the active variant */
export let isActive = false; export let isActive = false;
@ -8,19 +12,22 @@
*/ */
export let icon = undefined; export let icon = undefined;
/** Obtain a reference to the HTML button element */ /** Obtain a reference to the HTML button element
* @type {HTMLButtonElement}
*/
export let ref = null; export let ref = null;
import Button from "../Button/Button.svelte";
$: buttonClass = [
"bx--header__action",
isActive && " bx--header__action--active",
$$restProps.class,
]
.filter(Boolean)
.join(" ");
</script> </script>
<button <Button bind:ref {...$$restProps} class="{buttonClass}" on:click>
type="button" <svelte:component this="{icon}" slot="icon" size="{20}" />
bind:this="{ref}" </Button>
class:bx--header__action="{true}"
class:bx--header__action--active="{isActive}"
{...$$restProps}
on:click
>
<slot>
<svelte:component this="{icon}" size="{20}" />
</slot>
</button>

View file

@ -30,7 +30,7 @@
<SkipToContent /> <SkipToContent />
</div> </div>
<HeaderUtilities> <HeaderUtilities>
<HeaderGlobalAction aria-label="Settings" icon="{SettingsAdjust}" /> <HeaderGlobalAction iconDescription="Settings" icon="{SettingsAdjust}" />
<HeaderAction <HeaderAction
bind:isOpen bind:isOpen
on:open on:open

View file

@ -41,6 +41,7 @@ export interface ButtonProps extends ButtonSkeletonProps, RestProps {
/** /**
* Specify the icon to render * Specify the icon to render
* Alternatively, use the named slot "icon" (e.g., `<Icon slot="icon" size="{20}" />`)
* @default undefined * @default undefined
*/ */
icon?: typeof import("svelte").SvelteComponent<any>; icon?: typeof import("svelte").SvelteComponent<any>;
@ -132,5 +133,6 @@ export default class Button extends SvelteComponentTyped<
[key: string]: any; [key: string]: any;
}; };
}; };
icon: {};
} }
> {} > {}

View file

@ -1,9 +1,7 @@
import type { SvelteComponentTyped } from "svelte"; import type { SvelteComponentTyped } from "svelte";
import type { SvelteHTMLElements } from "svelte/elements"; import type { ButtonProps } from "../Button/Button.svelte";
type RestProps = SvelteHTMLElements["button"]; export interface HeaderGlobalActionProps extends ButtonProps {
export interface HeaderGlobalActionProps extends RestProps {
/** /**
* Set to `true` to use the active variant * Set to `true` to use the active variant
* @default false * @default false
@ -20,13 +18,11 @@ export interface HeaderGlobalActionProps extends RestProps {
* Obtain a reference to the HTML button element * Obtain a reference to the HTML button element
* @default null * @default null
*/ */
ref?: null | HTMLButtonElement; ref?: HTMLButtonElement;
[key: `data-${string}`]: any;
} }
export default class HeaderGlobalAction extends SvelteComponentTyped< export default class HeaderGlobalAction extends SvelteComponentTyped<
HeaderGlobalActionProps, HeaderGlobalActionProps,
{ click: WindowEventMap["click"] }, { click: WindowEventMap["click"] },
{ default: {} } {}
> {} > {}