1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 15:23:33 +00:00

[CL-712] Update icon button, components using it, and affected virtual scroll heights (#15683)

This commit is contained in:
Vicki League
2025-08-05 10:58:49 -04:00
committed by GitHub
parent 40a1a0a2b7
commit 26c0176e2e
34 changed files with 90 additions and 187 deletions

View File

@@ -1,5 +1,5 @@
<span class="tw-relative">
<span [ngClass]="{ 'tw-invisible': showLoadingStyle() }">
<span class="tw-relative tw-inline-block tw-leading-[0px]">
<span class="tw-inline-block tw-leading-[0px]" [ngClass]="{ 'tw-invisible': showLoadingStyle() }">
<i class="bwi" [ngClass]="iconClass" aria-hidden="true"></i>
</span>
<span

View File

@@ -5,10 +5,10 @@ import { Component, computed, ElementRef, HostBinding, input, model } from "@ang
import { toObservable, toSignal } from "@angular/core/rxjs-interop";
import { debounce, interval } from "rxjs";
import { ButtonLikeAbstraction, ButtonType } from "../shared/button-like.abstraction";
import { ButtonLikeAbstraction } from "../shared/button-like.abstraction";
import { FocusableElement } from "../shared/focusable-element";
export type IconButtonType = ButtonType | "contrast" | "main" | "muted" | "light";
export type IconButtonType = "primary" | "danger" | "contrast" | "main" | "muted" | "nav-contrast";
const focusRing = [
// Workaround for box-shadow with transparent offset issue:
@@ -20,7 +20,7 @@ const focusRing = [
"before:tw-content-['']",
"before:tw-block",
"before:tw-absolute",
"before:-tw-inset-[2px]",
"before:-tw-inset-[1px]",
"before:tw-rounded-lg",
"before:tw-transition",
"before:tw-ring-2",
@@ -30,122 +30,38 @@ const focusRing = [
const styles: Record<IconButtonType, string[]> = {
contrast: [
"tw-bg-transparent",
"!tw-text-contrast",
"tw-border-transparent",
"hover:tw-bg-transparent-hover",
"hover:tw-border-text-contrast",
"hover:!tw-bg-hover-contrast",
"focus-visible:before:tw-ring-text-contrast",
...focusRing,
],
main: [
"tw-bg-transparent",
"!tw-text-main",
"tw-border-transparent",
"hover:tw-bg-transparent-hover",
"hover:tw-border-primary-600",
"focus-visible:before:tw-ring-primary-600",
...focusRing,
],
main: ["!tw-text-main", "focus-visible:before:tw-ring-primary-600", ...focusRing],
muted: [
"tw-bg-transparent",
"!tw-text-muted",
"tw-border-transparent",
"aria-expanded:tw-bg-text-muted",
"aria-expanded:!tw-text-contrast",
"hover:tw-bg-transparent-hover",
"hover:tw-border-primary-600",
"focus-visible:before:tw-ring-primary-600",
"aria-expanded:hover:tw-bg-secondary-700",
"aria-expanded:hover:tw-border-secondary-700",
...focusRing,
],
primary: [
"tw-bg-primary-600",
"!tw-text-contrast",
"tw-border-primary-600",
"hover:tw-bg-primary-600",
"hover:tw-border-primary-600",
"focus-visible:before:tw-ring-primary-600",
...focusRing,
],
secondary: [
"tw-bg-transparent",
"!tw-text-muted",
"tw-border-text-muted",
"hover:!tw-text-contrast",
"hover:tw-bg-text-muted",
"focus-visible:before:tw-ring-primary-600",
...focusRing,
],
danger: [
"tw-bg-transparent",
"!tw-text-danger-600",
"tw-border-transparent",
"hover:!tw-text-danger-600",
"hover:tw-bg-transparent",
"hover:tw-border-primary-600",
"focus-visible:before:tw-ring-primary-600",
...focusRing,
],
light: [
"tw-bg-transparent",
primary: ["!tw-text-primary-600", "focus-visible:before:tw-ring-primary-600", ...focusRing],
danger: ["!tw-text-danger-600", "focus-visible:before:tw-ring-primary-600", ...focusRing],
"nav-contrast": [
"!tw-text-alt2",
"tw-border-transparent",
"hover:tw-bg-transparent-hover",
"hover:tw-border-text-alt2",
"hover:!tw-bg-hover-contrast",
"focus-visible:before:tw-ring-text-alt2",
...focusRing,
],
unstyled: [],
};
const disabledStyles: Record<IconButtonType, string[]> = {
contrast: [
"disabled:tw-opacity-60",
"disabled:hover:tw-border-transparent",
"disabled:hover:tw-bg-transparent",
],
main: [
"disabled:!tw-text-secondary-300",
"disabled:hover:tw-border-transparent",
"disabled:hover:tw-bg-transparent",
],
muted: [
"disabled:!tw-text-secondary-300",
"disabled:hover:tw-border-transparent",
"disabled:hover:tw-bg-transparent",
],
primary: [
"disabled:tw-opacity-60",
"disabled:hover:tw-border-primary-600",
"disabled:hover:tw-bg-primary-600",
],
secondary: [
"disabled:tw-opacity-60",
"disabled:hover:tw-border-text-muted",
"disabled:hover:tw-bg-transparent",
"disabled:hover:!tw-text-muted",
],
danger: [
"disabled:!tw-text-secondary-300",
"disabled:hover:tw-border-transparent",
"disabled:hover:tw-bg-transparent",
"disabled:hover:!tw-text-secondary-300",
],
light: [
"disabled:tw-opacity-60",
"disabled:hover:tw-border-transparent",
"disabled:hover:tw-bg-transparent",
],
unstyled: [],
};
export type IconButtonSize = "default" | "small";
const sizes: Record<IconButtonSize, string[]> = {
default: ["tw-px-2.5", "tw-py-1.5"],
small: ["tw-leading-none", "tw-text-base", "tw-p-1"],
default: ["tw-text-xl", "tw-p-2.5", "tw-rounded-md"],
small: ["tw-text-base", "tw-p-2", "tw-rounded"],
};
/**
* Icon buttons are used when no text accompanies the button. It consists of an icon that may be updated to any icon in the `bwi-font`, a `title` attribute, and an `aria-label`.
@@ -164,6 +80,13 @@ const sizes: Record<IconButtonSize, string[]> = {
imports: [NgClass],
host: {
"[attr.disabled]": "disabledAttr()",
/**
* When the `bitIconButton` input is dynamic from a consumer, Angular doesn't put the
* `bitIconButton` attribute into the DOM. We use the attribute as a css selector in
* a number of components, so this manual attr binding makes sure that the css selector
* works when the input is dynamic.
*/
"[attr.bitIconButton]": "icon()",
},
})
export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableElement {
@@ -176,17 +99,20 @@ export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableE
@HostBinding("class") get classList() {
return [
"tw-font-semibold",
"tw-border",
"tw-border-solid",
"tw-rounded-lg",
"tw-leading-[0px]",
"tw-border-none",
"tw-transition",
"tw-bg-transparent",
"hover:tw-no-underline",
"hover:tw-bg-hover-default",
"focus:tw-outline-none",
]
.concat(styles[this.buttonType()])
.concat(sizes[this.size()])
.concat(
this.showDisabledStyles() || this.disabled() ? disabledStyles[this.buttonType()] : [],
this.showDisabledStyles() || this.disabled()
? ["disabled:tw-opacity-60", "disabled:hover:!tw-bg-transparent"]
: [],
);
}

View File

@@ -23,9 +23,6 @@ Icon buttons can be found in other components such as: the
## Styles
There are 4 common styles for button main, muted, contrast, and danger. The other styles follow the
button component styles.
### Main
Used for general icon buttons appearing on the themes main `background`
@@ -59,22 +56,11 @@ square.
<Canvas of={stories.Primary} />
### Secondary
### Nav Contrast
Used in place of the main button component if no text is used. This allows the button to display
square.
Used on the side nav background that is dark in both light theme and dark theme.
<Canvas of={stories.Secondary} />
### Light
Used on a background that is dark in both light theme and dark theme. Example: end user navigation
styles.
<Canvas of={stories.Light} />
**Note:** Main and contrast styles appear on backgrounds where using `primary-700` as a focus
indicator does not meet WCAG graphic contrast guidelines.
<Canvas of={stories.NavContrast} />
## Sizes

View File

@@ -49,13 +49,6 @@ export const Primary: Story = {
},
};
export const Secondary: Story = {
...Default,
args: {
buttonType: "secondary",
},
};
export const Danger: Story = {
...Default,
args: {
@@ -77,18 +70,18 @@ export const Muted: Story = {
},
};
export const Light: Story = {
export const NavContrast: Story = {
render: (args) => ({
props: args,
template: /*html*/ `
<div class="tw-bg-background-alt2 tw-p-6 tw-w-full tw-inline-block">
<div class="tw-bg-background-alt3 tw-p-6 tw-w-full tw-inline-block">
<!-- <div> used only to provide dark background color -->
<button ${formatArgsForCodeSnippet<BitIconButtonComponent>(args)}>Button</button>
</div>
`,
}),
args: {
buttonType: "light",
buttonType: "nav-contrast",
},
};