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:
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user