1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-13 15:03:26 +00:00

[CL-273] Update styles for checkbox and form control (#10146)

This commit is contained in:
Victoria League
2024-07-30 12:09:56 -04:00
committed by GitHub
parent b81c2d22f1
commit bfadccdb24
5 changed files with 133 additions and 43 deletions

View File

@@ -17,12 +17,13 @@ export class CheckboxComponent implements BitFormControlAbstraction {
"tw-transition",
"tw-cursor-pointer",
"tw-inline-block",
"tw-align-sub",
"tw-rounded",
"tw-border",
"tw-border-solid",
"tw-border-secondary-600",
"tw-h-3.5",
"tw-w-3.5",
"tw-border-secondary-500",
"tw-h-5",
"tw-w-5",
"tw-mr-1.5",
"tw-bottom-[-1px]", // Fix checkbox looking off-center
"tw-flex-none", // Flexbox fix for bit-form-control
@@ -35,13 +36,16 @@ export class CheckboxComponent implements BitFormControlAbstraction {
"hover:tw-border-2",
"[&>label]:tw-border-2",
"focus-visible:tw-ring-2",
"focus-visible:tw-ring-offset-2",
"focus-visible:tw-ring-primary-700",
// if it exists, the parent form control handles focus
"[&:not(bit-form-control_*)]:focus-visible:tw-ring-2",
"[&:not(bit-form-control_*)]:focus-visible:tw-ring-offset-2",
"[&:not(bit-form-control_*)]:focus-visible:tw-ring-primary-500",
"disabled:tw-cursor-auto",
"disabled:tw-border",
"disabled:hover:tw-border",
"disabled:tw-bg-secondary-100",
"disabled:hover:tw-bg-secondary-100",
"checked:tw-bg-primary-600",
"checked:tw-border-primary-600",
@@ -53,6 +57,7 @@ export class CheckboxComponent implements BitFormControlAbstraction {
"checked:before:tw-mask-position-[center]",
"checked:before:tw-mask-repeat-[no-repeat]",
"checked:disabled:tw-border-secondary-100",
"checked:disabled:hover:tw-border-secondary-100",
"checked:disabled:tw-bg-secondary-100",
"checked:disabled:before:tw-bg-text-muted",
@@ -78,11 +83,11 @@ export class CheckboxComponent implements BitFormControlAbstraction {
@HostBinding("style.--mask-image")
protected maskImage =
`url('data:image/svg+xml,%3Csvg class="svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="8" height="8" viewBox="0 0 10 10"%3E%3Cpath d="M0.5 6.2L2.9 8.6L9.5 1.4" fill="none" stroke="white" stroke-width="2"%3E%3C/path%3E%3C/svg%3E')`;
`url('data:image/svg+xml,%3Csvg class="svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="11" height="11" viewBox="0 0 10 10"%3E%3Cpath d="M0.5 6.2L2.9 8.6L9.5 1.4" fill="none" stroke="white" stroke-width="2"%3E%3C/path%3E%3C/svg%3E')`;
@HostBinding("style.--indeterminate-mask-image")
protected indeterminateImage =
`url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 13 13"%3E%3Cpath stroke="%23fff" stroke-width="2" d="M2.5 6.5h8"/%3E%3C/svg%3E%0A')`;
`url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 13 13"%3E%3Cpath stroke="%23fff" stroke-width="2" d="M2.5 6.5h8"/%3E%3C/svg%3E%0A')`;
@HostBinding()
@Input()

View File

@@ -12,11 +12,12 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/src/platform/abstractions/i18n.service";
import { FormControlModule } from "../form-control";
import { TableModule } from "../table";
import { I18nMockService } from "../utils/i18n-mock.service";
import { CheckboxModule } from "./checkbox.module";
const template = `
const template = /*html*/ `
<form [formGroup]="formObj">
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="checkbox">
@@ -54,7 +55,7 @@ export default {
decorators: [
moduleMetadata({
declarations: [ExampleComponent],
imports: [FormsModule, ReactiveFormsModule, FormControlModule, CheckboxModule],
imports: [FormsModule, ReactiveFormsModule, FormControlModule, CheckboxModule, TableModule],
providers: [
{
provide: I18nService,
@@ -82,7 +83,10 @@ type Story = StoryObj<ExampleComponent>;
export const Default: Story = {
render: (args) => ({
props: args,
template: `<app-example [checked]="checked" [disabled]="disabled"></app-example>`,
template: /*html*/ `
<app-example></app-example>
<app-example [checked]="true"></app-example>
`,
}),
parameters: {
docs: {
@@ -91,9 +95,31 @@ export const Default: Story = {
},
},
},
args: {
checked: false,
disabled: false,
};
export const LongLabel: Story = {
render: () => ({
props: {
formObj: new FormGroup({
checkbox: new FormControl(false),
}),
},
template: /*html*/ `
<form [formGroup]="formObj" class="tw-w-96">
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="checkbox">
<bit-label>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur iaculis consequat enim vitae elementum.
Ut non odio est. </bit-label>
</bit-form-control>
</form>
`,
}),
parameters: {
docs: {
source: {
code: template,
},
},
},
};
@@ -104,7 +130,7 @@ export const Hint: Story = {
checkbox: new FormControl(false),
}),
},
template: `
template: /*html*/ `
<form [formGroup]="formObj">
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="checkbox">
@@ -131,20 +157,37 @@ export const Hint: Story = {
},
};
export const Disabled: Story = {
render: (args) => ({
props: args,
template: /*html*/ `
<app-example [disabled]="true"></app-example>
<app-example [checked]="true" [disabled]="true"></app-example>
`,
}),
parameters: {
docs: {
source: {
code: template,
},
},
},
};
export const Custom: Story = {
render: (args) => ({
props: args,
template: `
template: /*html*/ `
<div class="tw-flex tw-flex-col tw-w-32">
<label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2 tw-items-baseline">
<label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2">
A-Z
<input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox>
</label>
<label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2 tw-items-baseline">
<label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2">
a-z
<input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox>
</label>
<label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2 tw-items-baseline">
<label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2">
0-9
<input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox>
</label>
@@ -156,8 +199,51 @@ export const Custom: Story = {
export const Indeterminate: Story = {
render: (args) => ({
props: args,
template: `
template: /*html*/ `
<input type="checkbox" bitCheckbox [indeterminate]="true">
`,
}),
};
export const InTableRow: Story = {
render: () => ({
template: /*html*/ `
<bit-table>
<ng-container header>
<tr>
<th bitCell>
<input
type="checkbox"
bitCheckbox
id="checkAll"
class="tw-mr-2"
/>
<label for="checkAll" class="tw-mb-0">
All
</label>
</th>
<th bitCell>
Foo
</th>
<th bitCell>
Bar
</th>
</tr>
</ng-container>
<ng-template body>
<tr bitRow>
<td bitCell>
<input
type="checkbox"
bitCheckbox
id="checkOne"
/>
</td>
<td bitCell>Lorem</td>
<td bitCell>Ipsum</td>
</tr>
</ng-template>
</bit-table>
`,
}),
};

View File

@@ -1,13 +1,19 @@
<label [class]="labelClasses">
<label
class="tw-transition tw-select-none tw-mb-0 tw-inline-flex tw-rounded tw-p-0.5 has-[:focus-visible]:tw-ring has-[:focus-visible]:tw-ring-primary-500"
[ngClass]="[formControl.disabled ? 'tw-cursor-auto' : 'tw-cursor-pointer']"
>
<ng-content></ng-content>
<span [class]="labelContentClasses">
<span>
<span
class="tw-inline-flex tw-flex-col"
[ngClass]="formControl.disabled ? 'tw-text-muted' : 'tw-text-main'"
>
<span bitTypography="body1">
<ng-content select="bit-label"></ng-content>
<span *ngIf="required" class="tw-text-xs tw-font-normal"> ({{ "required" | i18n }})</span>
</span>
<ng-content select="bit-hint" *ngIf="!hasError"></ng-content>
</span>
</label>
<div *ngIf="hasError" class="tw-mt-1 tw-text-danger">
<div *ngIf="hasError" class="tw-mt-1 tw-text-danger tw-text-xs tw-ml-0.5">
<i class="bwi bwi-error"></i> {{ displayError }}
</div>

View File

@@ -38,22 +38,6 @@ export class FormControlComponent {
constructor(private i18nService: I18nService) {}
protected get labelClasses() {
return [
"tw-transition",
"tw-select-none",
"tw-mb-0",
"tw-inline-flex",
"tw-items-baseline",
].concat(this.formControl.disabled ? "tw-cursor-auto" : "tw-cursor-pointer");
}
protected get labelContentClasses() {
return ["tw-inline-flex", "tw-flex-col", "tw-font-semibold"].concat(
this.formControl.disabled ? "tw-text-muted" : "tw-text-main",
);
}
get required() {
return this.formControl.required;
}

View File

@@ -1,4 +1,6 @@
import { Directive, ElementRef, HostBinding, Input } from "@angular/core";
import { Directive, ElementRef, HostBinding, Input, Optional } from "@angular/core";
import { FormControlComponent } from "./form-control.component";
// Increments for each instance of this component
let nextId = 0;
@@ -7,10 +9,17 @@ let nextId = 0;
selector: "bit-label",
})
export class BitLabel {
constructor(private elementRef: ElementRef<HTMLInputElement>) {}
constructor(
private elementRef: ElementRef<HTMLInputElement>,
@Optional() private parentFormControl: FormControlComponent,
) {}
@HostBinding("class") @Input() get classList() {
return ["tw-truncate", "tw-inline-flex", "tw-gap-1", "tw-items-baseline", "tw-flex-row"];
const classes = ["tw-inline-flex", "tw-gap-1", "tw-items-baseline", "tw-flex-row"];
/**
* We don't want to truncate checkboxes or radio buttons, which use form-control
*/
return this.parentFormControl ? classes : classes.concat(["tw-truncate"]);
}
@HostBinding("title") get title() {