1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-06-27 10:46:02 +02:00

[PM-2195] Adjust radio groups margin (#5410)

* Adjust radio groups margin

* Move hint margin to input field

* Tweak field spacing

* Add story for hint, and fix hint display

* Fix label ssue

* Revert input margin

* Re-add margin to hint

* Change font weight

* Fix required placement

* Add support for required

* Change margin of radio group

* Remove unnecessary div

* Fix long inputs cutting off

* Fix radio story

* Remove newline

---------

Co-authored-by: Will Martin <contact@willmartian.com>
This commit is contained in:
Oscar Hinton 2023-11-21 04:59:03 +01:00 committed by GitHub
parent 651593bcd2
commit 50493ab6f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 168 additions and 23 deletions

View File

@ -25,6 +25,7 @@ export class CheckboxComponent implements BitFormControlAbstraction {
"tw-w-3.5",
"tw-mr-1.5",
"tw-bottom-[-1px]", // Fix checkbox looking off-center
"tw-flex-none", // Flexbox fix for bit-form-control
"before:tw-content-['']",
"before:tw-block",

View File

@ -1,5 +1,12 @@
import { Component, Input } from "@angular/core";
import { FormsModule, ReactiveFormsModule, FormBuilder, Validators } from "@angular/forms";
import {
FormsModule,
ReactiveFormsModule,
FormBuilder,
Validators,
FormGroup,
FormControl,
} from "@angular/forms";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/src/platform/abstractions/i18n.service";
@ -15,7 +22,8 @@ const template = `
<input type="checkbox" bitCheckbox formControlName="checkbox">
<bit-label>Click me</bit-label>
</bit-form-control>
</form>`;
</form>
`;
@Component({
selector: "app-example",
@ -89,6 +97,40 @@ export const Default: Story = {
},
};
export const Hint: Story = {
render: (args) => ({
props: {
formObj: new FormGroup({
checkbox: new FormControl(false),
}),
},
template: `
<form [formGroup]="formObj">
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="checkbox">
<bit-label>Really long value that never ends.</bit-label>
<bit-hint>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur iaculis consequat enim vitae elementum.
Ut non odio est. Duis eu nisi ultrices, porttitor lorem eget, ornare libero. Fusce ex ante, consequat ac
sem et, euismod placerat tellus.
</bit-hint>
</bit-form-control>
</form>
`,
}),
parameters: {
docs: {
source: {
code: template,
},
},
},
args: {
checked: false,
disabled: false,
},
};
export const Custom: Story = {
render: (args) => ({
props: args,

View File

@ -1,13 +1,13 @@
<label [class]="labelClasses">
<ng-content></ng-content>
<span [class]="labelContentClasses">
<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-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>
<ng-content select="bit-hint" *ngIf="!hasError"></ng-content>
</div>
<div *ngIf="hasError" class="tw-mt-1 tw-text-danger">
<i class="bwi bwi-error"></i> {{ displayError }}
</div>

View File

@ -20,22 +20,36 @@ export class FormControlComponent {
this._inline = coerceBooleanProperty(value);
}
private _disableMargin = false;
@Input() set disableMargin(value: boolean | "") {
this._disableMargin = coerceBooleanProperty(value);
}
get disableMargin() {
return this._disableMargin;
}
@ContentChild(BitFormControlAbstraction) protected formControl: BitFormControlAbstraction;
@HostBinding("class") get classes() {
return ["tw-mb-6"].concat(this.inline ? ["tw-inline-block", "tw-mr-4"] : ["tw-block"]);
return []
.concat(this.inline ? ["tw-inline-block", "tw-mr-4"] : ["tw-block"])
.concat(this.disableMargin ? [] : ["tw-mb-6"]);
}
constructor(private i18nService: I18nService) {}
protected get labelClasses() {
return ["tw-transition", "tw-select-none", "tw-mb-0"].concat(
this.formControl.disabled ? "tw-cursor-auto" : "tw-cursor-pointer"
);
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-font-semibold"].concat(
return ["tw-inline-flex", "tw-flex-col", "tw-font-semibold"].concat(
this.formControl.disabled ? "tw-text-muted" : "tw-text-main"
);
}

View File

@ -6,7 +6,7 @@ let nextId = 0;
@Directive({
selector: "bit-hint",
host: {
class: "tw-text-muted tw-inline-block tw-mt-1",
class: "tw-text-muted tw-font-normal tw-inline-block tw-mt-1",
},
})
export class BitHintComponent {

View File

@ -1,15 +1,15 @@
<bit-form-control inline>
<bit-form-control [inline]="!block" disableMargin>
<input
type="radio"
bitRadio
[id]="inputId"
[name]="name"
[disabled]="groupDisabled || disabled"
[value]="value"
[checked]="selected"
(change)="onInputChange()"
(blur)="onBlur()"
/>
<ng-content select="bit-label" ngProjectAs="bit-label"></ng-content>
<ng-content select="bit-hint" ngProjectAs="bit-hint"></ng-content>
</bit-form-control>

View File

@ -10,6 +10,10 @@ let nextId = 0;
})
export class RadioButtonComponent {
@HostBinding("attr.id") @Input() id = `bit-radio-button-${nextId++}`;
@HostBinding("class") get classList() {
return [this.block ? "tw-block" : "tw-inline-block", "tw-mb-2"];
}
@Input() value: unknown;
@Input() disabled = false;
@ -31,6 +35,10 @@ export class RadioButtonComponent {
return this.groupComponent.disabled;
}
get block() {
return this.groupComponent.block;
}
protected onInputChange() {
this.groupComponent.onInputChange(this.value);
}

View File

@ -2,13 +2,14 @@ import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { FormControlModule } from "../form-control";
import { SharedModule } from "../shared";
import { RadioButtonComponent } from "./radio-button.component";
import { RadioGroupComponent } from "./radio-group.component";
import { RadioInputComponent } from "./radio-input.component";
@NgModule({
imports: [CommonModule, FormControlModule],
imports: [CommonModule, SharedModule, FormControlModule],
declarations: [RadioInputComponent, RadioButtonComponent, RadioGroupComponent],
exports: [FormControlModule, RadioInputComponent, RadioButtonComponent, RadioGroupComponent],
})

View File

@ -1,5 +1,5 @@
import { FormsModule, ReactiveFormsModule, FormControl, FormGroup } from "@angular/forms";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -67,6 +67,37 @@ export const Inline: Story = {
}),
};
export const InlineHint: Story = {
render: () => ({
props: {
formObj: new FormGroup({
radio: new FormControl(0),
}),
},
template: `
<form [formGroup]="formObj">
<bit-radio-group formControlName="radio" aria-label="Example radio group">
<bit-label>Group of radio buttons</bit-label>
<bit-radio-button id="radio-first" [value]="0">
<bit-label>First</bit-label>
</bit-radio-button>
<bit-radio-button id="radio-second" [value]="1">
<bit-label>Second</bit-label>
</bit-radio-button>
<bit-radio-button id="radio-third" [value]="2">
<bit-label>Third</bit-label>
</bit-radio-button>
<bit-hint>This is a hint for the radio group</bit-hint>
</bit-radio-group>
</form>
`,
}),
};
export const Block: Story = {
render: () => ({
props: {
@ -76,20 +107,20 @@ export const Block: Story = {
},
template: `
<form [formGroup]="formObj">
<bit-radio-group formControlName="radio" aria-label="Example radio group">
<bit-radio-group formControlName="radio" aria-label="Example radio group" [block]="true">
<bit-label>Group of radio buttons</bit-label>
<bit-radio-button id="radio-first" class="tw-block" [value]="0">
<bit-radio-button id="radio-first" [value]="0">
<bit-label>First</bit-label>
<bit-hint>This is a hint for the first option</bit-hint>
</bit-radio-button>
<bit-radio-button id="radio-second" class="tw-block" [value]="1">
<bit-radio-button id="radio-second" [value]="1">
<bit-label>Second</bit-label>
<bit-hint>This is a hint for the second option</bit-hint>
</bit-radio-button>
<bit-radio-button id="radio-third" class="tw-block" [value]="2">
<bit-radio-button id="radio-third" [value]="2">
<bit-label>Third</bit-label>
<bit-hint>This is a hint for the third option</bit-hint>
</bit-radio-button>
@ -98,3 +129,37 @@ export const Block: Story = {
`,
}),
};
export const BlockHint: Story = {
render: () => ({
props: {
formObj: new FormGroup({
radio: new FormControl(0),
}),
},
template: `
<form [formGroup]="formObj">
<bit-radio-group formControlName="radio" aria-label="Example radio group" [block]="true">
<bit-label>Group of radio buttons</bit-label>
<bit-radio-button id="radio-first" [value]="0">
<bit-label>First</bit-label>
<bit-hint>This is a hint for the first option</bit-hint>
</bit-radio-button>
<bit-radio-button id="radio-second" [value]="1">
<bit-label>Second</bit-label>
<bit-hint>This is a hint for the second option</bit-hint>
</bit-radio-button>
<bit-radio-button id="radio-third" [value]="2">
<bit-label>Third</bit-label>
<bit-hint>This is a hint for the third option</bit-hint>
</bit-radio-button>
<bit-hint>This is a hint for the radio group</bit-hint>
</bit-radio-group>
</form>
`,
}),
};

View File

@ -2,6 +2,7 @@
<fieldset>
<legend class="tw-mb-1 tw-block tw-text-sm tw-font-semibold tw-text-main">
<ng-content select="bit-label"></ng-content>
<span *ngIf="required" class="tw-text-xs tw-font-normal"> ({{ "required" | i18n }})</span>
</legend>
<ng-container *ngTemplateOutlet="content"></ng-container>
</fieldset>
@ -11,4 +12,9 @@
<ng-container *ngTemplateOutlet="content"></ng-container>
</ng-container>
<ng-template #content><ng-content></ng-content></ng-template>
<ng-template #content>
<div>
<ng-content></ng-content>
</div>
<ng-content select="bit-hint" ngProjectAs="bit-hint"></ng-content>
</ng-template>

View File

@ -1,5 +1,5 @@
import { Component, ContentChild, HostBinding, Input, Optional, Self } from "@angular/core";
import { ControlValueAccessor, NgControl } from "@angular/forms";
import { ControlValueAccessor, NgControl, Validators } from "@angular/forms";
import { BitLabel } from "../form-control/label.directive";
@ -21,8 +21,11 @@ export class RadioGroupComponent implements ControlValueAccessor {
this._name = value;
}
@Input() block = false;
@HostBinding("attr.role") role = "radiogroup";
@HostBinding("attr.id") @Input() id = `bit-radio-group-${nextId++}`;
@HostBinding("class") classList = ["tw-block", "tw-mb-4"];
@ContentChild(BitLabel) protected label: BitLabel;
@ -32,6 +35,10 @@ export class RadioGroupComponent implements ControlValueAccessor {
}
}
get required() {
return this.ngControl?.control?.hasValidator(Validators.required) ?? false;
}
// ControlValueAccessor
onChange: (value: unknown) => void;
onTouched: () => void;

View File

@ -29,6 +29,7 @@ export class RadioInputComponent implements BitFormControlAbstraction {
"tw-h-3.5",
"tw-mr-1.5",
"tw-bottom-[-1px]", // Fix checkbox looking off-center
"tw-flex-none", // Flexbox fix for bit-form-control
"hover:tw-border-2",
"[&>label:hover]:tw-border-2",