mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-21 16:18:28 +01: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:
parent
651593bcd2
commit
50493ab6f7
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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],
|
||||
})
|
||||
|
@ -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>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user