mirror of
https://github.com/bitwarden/browser
synced 2026-02-14 23:45:37 +00:00
add new button styles
This commit is contained in:
@@ -19,50 +19,133 @@ import { ariaDisableElement } from "../utils";
|
||||
|
||||
const focusRing = [
|
||||
"focus-visible:tw-ring-2",
|
||||
"focus-visible:tw-ring-offset-2",
|
||||
"focus-visible:tw-ring-primary-600",
|
||||
"focus-visible:tw-ring-offset-1",
|
||||
"focus-visible:tw-ring-border-focus",
|
||||
"focus-visible:tw-z-10",
|
||||
];
|
||||
|
||||
const buttonSizeStyles: Record<ButtonSize, string[]> = {
|
||||
small: ["tw-py-1", "tw-px-3", "tw-text-sm"],
|
||||
default: ["tw-py-1.5", "tw-px-3"],
|
||||
const getButtonSizeStyles = (size: ButtonSize): string[] => {
|
||||
const buttonSizeStyles: Record<ButtonSize, string[]> = {
|
||||
small: ["tw-py-1", "tw-px-3", "tw-text-xs"],
|
||||
default: ["tw-py-2.5", "tw-px-4", "tw-text-sm/5"],
|
||||
large: ["tw-py-3", "tw-px-4", "tw-text-base/6"],
|
||||
};
|
||||
|
||||
return buttonSizeStyles[size];
|
||||
};
|
||||
|
||||
const buttonStyles: Record<ButtonType, string[]> = {
|
||||
primary: [
|
||||
"tw-border-primary-600",
|
||||
"tw-bg-primary-600",
|
||||
"!tw-text-contrast",
|
||||
"hover:tw-bg-primary-700",
|
||||
"hover:tw-border-primary-700",
|
||||
const getButtonStyles = (buttonType: ButtonType, size: ButtonSize): string[] => {
|
||||
const normalizedType = buttonType.toLowerCase();
|
||||
|
||||
const buttonStyles: Record<ButtonType, string[]> = {
|
||||
primary: [
|
||||
"tw-border-border-brand",
|
||||
"tw-bg-bg-brand",
|
||||
"hover:tw-bg-bg-brand-strong",
|
||||
"hover:tw-border-bg-brand-strong",
|
||||
],
|
||||
primaryOutline: [
|
||||
"tw-border-border-brand",
|
||||
"tw-text-fg-brand",
|
||||
"hover:tw-border-bg-brand-strong",
|
||||
"hover:tw-text-fg-brand-strong",
|
||||
],
|
||||
primaryGhost: ["tw-text-fg-heading", "hover:tw-text-fg-brand"],
|
||||
secondary: [
|
||||
"tw-bg-bg-secondary",
|
||||
"tw-border-border-base",
|
||||
"tw-text-fg-heading",
|
||||
"hover:tw-bg-bg-quaternary",
|
||||
"hover:tw-text-fg-brand",
|
||||
],
|
||||
subtle: [
|
||||
"tw-border-border-contrast",
|
||||
"tw-bg-bg-contrast",
|
||||
"hover:tw-bg-bg-contrast-strong",
|
||||
"hover:tw-border-border-contrast-strong",
|
||||
],
|
||||
subtleOutline: [
|
||||
"tw-border-border-contrast",
|
||||
"tw-text-fg-heading",
|
||||
"hover:tw-border-border-contrast-strong",
|
||||
"hover:tw-text-fg-heading",
|
||||
],
|
||||
subtleGhost: ["tw-text-fg-heading", "hover:tw-text-fg-heading"],
|
||||
danger: [
|
||||
"tw-bg-bg-danger",
|
||||
"tw-border-border-danger",
|
||||
"hover:tw-bg-bg-danger-strong",
|
||||
"hover:tw-border-border-danger-strong",
|
||||
"hover:tw-text-fg-contrast",
|
||||
],
|
||||
dangerOutline: [
|
||||
"tw-border-border-danger",
|
||||
"tw-text-fg-danger",
|
||||
"hover:tw-border-bg-danger-strong",
|
||||
"hover:!tw-text-fg-danger-strong",
|
||||
],
|
||||
dangerGhost: ["tw-text-fg-danger", "hover:tw-text-fg-danger"],
|
||||
warning: [
|
||||
"tw-bg-bg-warning",
|
||||
"tw-border-border-warning",
|
||||
"hover:tw-bg-bg-warning-strong",
|
||||
"hover:tw-border-border-warning-strong",
|
||||
],
|
||||
warningOutline: [
|
||||
"tw-border-border-warning",
|
||||
"tw-text-fg-warning",
|
||||
"hover:tw-border-border-warning-strong",
|
||||
"hover:!tw-text-fg-warning-strong",
|
||||
],
|
||||
warningGhost: ["tw-text-fg-warning", "hover:tw-text-fg-warning"],
|
||||
success: [
|
||||
"tw-bg-bg-success",
|
||||
"tw-border-border-success",
|
||||
"hover:tw-bg-bg-success-strong",
|
||||
"hover:tw-border-border-success-strong",
|
||||
],
|
||||
successOutline: [
|
||||
"tw-border-border-success",
|
||||
"tw-text-fg-success",
|
||||
"hover:tw-border-border-success-strong",
|
||||
"hover:tw-text-fg-success-strong",
|
||||
],
|
||||
successGhost: ["tw-text-fg-success", "hover:tw-text-fg-success"],
|
||||
unstyled: [],
|
||||
};
|
||||
|
||||
const baseStyles = [
|
||||
"tw-font-medium",
|
||||
"tw-tracking-wide",
|
||||
"tw-rounded-xl",
|
||||
"tw-transition",
|
||||
"tw-border",
|
||||
"tw-border-solid",
|
||||
"tw-text-center",
|
||||
"tw-no-underline",
|
||||
"hover:tw-no-underline",
|
||||
"focus:tw-outline-none",
|
||||
...focusRing,
|
||||
],
|
||||
secondary: [
|
||||
"tw-bg-transparent",
|
||||
"tw-border-primary-600",
|
||||
"!tw-text-primary-600",
|
||||
"hover:tw-bg-hover-default",
|
||||
...focusRing,
|
||||
],
|
||||
danger: [
|
||||
"tw-bg-transparent",
|
||||
"tw-border-danger-600",
|
||||
"!tw-text-danger",
|
||||
"hover:tw-bg-danger-600",
|
||||
"hover:tw-border-danger-600",
|
||||
"hover:!tw-text-contrast",
|
||||
...focusRing,
|
||||
],
|
||||
dangerPrimary: [
|
||||
"tw-border-danger-600",
|
||||
"tw-bg-danger-600",
|
||||
"!tw-text-contrast",
|
||||
"hover:tw-bg-danger-700",
|
||||
"hover:tw-border-danger-700",
|
||||
...focusRing,
|
||||
],
|
||||
unstyled: [],
|
||||
];
|
||||
|
||||
const isOutline = normalizedType.includes("outline");
|
||||
const isGhost = normalizedType.includes("ghost");
|
||||
const isSecondary = normalizedType === "secondary";
|
||||
const isSolid = !isOutline && !isGhost;
|
||||
|
||||
if (isOutline || isGhost) {
|
||||
baseStyles.push("tw-bg-transparent", "hover:tw-bg-bg-hover");
|
||||
}
|
||||
|
||||
if (isSolid && !isSecondary) {
|
||||
baseStyles.push("tw-text-fg-contrast", "hover:tw-text-fg-contrast");
|
||||
}
|
||||
|
||||
if (isGhost) {
|
||||
baseStyles.push("tw-border-transparent", "tw-bg-clip-padding", "hover:tw-border-bg-hover");
|
||||
}
|
||||
|
||||
return [...baseStyles, ...buttonStyles[buttonType], ...getButtonSizeStyles(size)];
|
||||
};
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
@@ -76,19 +159,8 @@ const buttonStyles: Record<ButtonType, string[]> = {
|
||||
})
|
||||
export class ButtonComponent implements ButtonLikeAbstraction {
|
||||
@HostBinding("class") get classList() {
|
||||
return [
|
||||
"tw-font-medium",
|
||||
"tw-rounded-full",
|
||||
"tw-transition",
|
||||
"tw-border-2",
|
||||
"tw-border-solid",
|
||||
"tw-text-center",
|
||||
"tw-no-underline",
|
||||
"hover:tw-no-underline",
|
||||
"focus:tw-outline-none",
|
||||
]
|
||||
return []
|
||||
.concat(this.block() ? ["tw-w-full", "tw-block"] : ["tw-inline-block"])
|
||||
.concat(buttonStyles[this.buttonType() ?? "secondary"])
|
||||
.concat(
|
||||
this.showDisabledStyles() || this.disabled()
|
||||
? [
|
||||
@@ -103,7 +175,7 @@ export class ButtonComponent implements ButtonLikeAbstraction {
|
||||
]
|
||||
: [],
|
||||
)
|
||||
.concat(buttonSizeStyles[this.size() || "default"]);
|
||||
.concat(getButtonStyles(this.buttonType() || "secondary", this.size() || "default"));
|
||||
}
|
||||
|
||||
protected readonly disabledAttr = computed(() => {
|
||||
|
||||
@@ -29,7 +29,7 @@ export default {
|
||||
},
|
||||
argTypes: {
|
||||
size: {
|
||||
options: ["small", "default"],
|
||||
options: ["small", "default", "large"],
|
||||
control: { type: "radio" },
|
||||
},
|
||||
},
|
||||
@@ -62,10 +62,38 @@ export const Primary: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const DangerPrimary: Story = {
|
||||
export const PrimaryOutline: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
buttonType: "dangerPrimary",
|
||||
buttonType: "primaryOutline",
|
||||
},
|
||||
};
|
||||
|
||||
export const PrimaryGhost: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
buttonType: "primaryGhost",
|
||||
},
|
||||
};
|
||||
|
||||
export const Subtle: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
buttonType: "subtle",
|
||||
},
|
||||
};
|
||||
|
||||
export const SubtleOutline: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
buttonType: "subtleOutline",
|
||||
},
|
||||
};
|
||||
|
||||
export const SubtleGhost: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
buttonType: "subtleGhost",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -76,6 +104,62 @@ export const Danger: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const DangerOutline: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
buttonType: "dangerOutline",
|
||||
},
|
||||
};
|
||||
|
||||
export const DangerGhost: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
buttonType: "dangerGhost",
|
||||
},
|
||||
};
|
||||
|
||||
export const Warning: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
buttonType: "warning",
|
||||
},
|
||||
};
|
||||
|
||||
export const WarningOutline: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
buttonType: "warningOutline",
|
||||
},
|
||||
};
|
||||
|
||||
export const WarningGhost: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
buttonType: "warningGhost",
|
||||
},
|
||||
};
|
||||
|
||||
export const Success: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
buttonType: "success",
|
||||
},
|
||||
};
|
||||
|
||||
export const SuccessOutline: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
buttonType: "successOutline",
|
||||
},
|
||||
};
|
||||
|
||||
export const SuccessGhost: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
buttonType: "successGhost",
|
||||
},
|
||||
};
|
||||
|
||||
export const Small: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
@@ -84,7 +168,6 @@ export const Small: Story = {
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" buttonType="primary" [size]="size" [block]="block">Primary small</button>
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" buttonType="secondary" [size]="size" [block]="block">Secondary small</button>
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" buttonType="danger" [size]="size" [block]="block">Danger small</button>
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" buttonType="dangerPrimary" [size]="size" [block]="block">Danger Primary small</button>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
|
||||
@@ -1,8 +1,25 @@
|
||||
import { ModelSignal } from "@angular/core";
|
||||
|
||||
export type ButtonType = "primary" | "secondary" | "danger" | "dangerPrimary" | "unstyled";
|
||||
export type ButtonType =
|
||||
| "primary"
|
||||
| "primaryOutline"
|
||||
| "primaryGhost"
|
||||
| "secondary"
|
||||
| "subtle"
|
||||
| "subtleOutline"
|
||||
| "subtleGhost"
|
||||
| "danger"
|
||||
| "dangerOutline"
|
||||
| "dangerGhost"
|
||||
| "warning"
|
||||
| "warningOutline"
|
||||
| "warningGhost"
|
||||
| "success"
|
||||
| "successOutline"
|
||||
| "successGhost"
|
||||
| "unstyled";
|
||||
|
||||
export type ButtonSize = "default" | "small";
|
||||
export type ButtonSize = "default" | "small" | "large";
|
||||
|
||||
export abstract class ButtonLikeAbstraction {
|
||||
abstract loading: ModelSignal<boolean>;
|
||||
|
||||
@@ -330,7 +330,7 @@
|
||||
/* Brand Border */
|
||||
--color-border-brand-soft: var(--color-brand-200);
|
||||
--color-border-brand: var(--color-brand-700);
|
||||
--color-border-brand-strong: var(--color-brand-900);
|
||||
--color-border-brand-strong: var(--color-brand-800);
|
||||
|
||||
/* Status Border */
|
||||
--color-border-success-soft: var(--color-green-200);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</ng-container>
|
||||
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton type="button" buttonType="dangerPrimary" (click)="confirmLeave()">
|
||||
<button bitButton type="button" buttonType="danger" (click)="confirmLeave()">
|
||||
{{ "leaveConfirmationDialogConfirmButton" | i18n: organizationName }}
|
||||
</button>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user