1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-13 06:54:07 +00:00

[CL-389] Exclude end slot label content from truncation (#10508)

This commit is contained in:
Victoria League
2024-08-26 13:17:58 -04:00
committed by GitHub
parent d06f1ae6f2
commit 04808578f2
9 changed files with 108 additions and 41 deletions

View File

@@ -11,6 +11,7 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/src/platform/abstractions/i18n.service";
import { BadgeModule } from "../badge";
import { FormControlModule } from "../form-control";
import { TableModule } from "../table";
import { I18nMockService } from "../utils/i18n-mock.service";
@@ -55,7 +56,14 @@ export default {
decorators: [
moduleMetadata({
declarations: [ExampleComponent],
imports: [FormsModule, ReactiveFormsModule, FormControlModule, CheckboxModule, TableModule],
imports: [
FormsModule,
ReactiveFormsModule,
FormControlModule,
CheckboxModule,
TableModule,
BadgeModule,
],
providers: [
{
provide: I18nService,
@@ -111,6 +119,14 @@ export const LongLabel: Story = {
<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>
<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.
<span slot="end" bitBadge variant="success">Premium</span>
</bit-label>
</bit-form-control>
</form>
`,
}),

View File

@@ -4,11 +4,11 @@ import { SharedModule } from "../shared";
import { FormControlComponent } from "./form-control.component";
import { BitHintComponent } from "./hint.component";
import { BitLabel } from "./label.directive";
import { BitLabel } from "./label.component";
@NgModule({
imports: [SharedModule],
declarations: [FormControlComponent, BitLabel, BitHintComponent],
imports: [SharedModule, BitLabel],
declarations: [FormControlComponent, BitHintComponent],
exports: [FormControlComponent, BitLabel, BitHintComponent],
})
export class FormControlModule {}

View File

@@ -0,0 +1,14 @@
<ng-template #endSlotContent>
<ng-content select="[slot=end]"></ng-content>
</ng-template>
<!-- labels inside a form control (checkbox, radio button) should not truncate -->
<span [ngClass]="{ 'tw-truncate': !isInsideFormControl }">
<ng-content></ng-content>
<ng-container *ngIf="isInsideFormControl">
<ng-container *ngTemplateOutlet="endSlotContent"></ng-container>
</ng-container>
</span>
<ng-container *ngIf="!isInsideFormControl">
<ng-container *ngTemplateOutlet="endSlotContent"></ng-container>
</ng-container>

View File

@@ -1,12 +1,16 @@
import { Directive, ElementRef, HostBinding, Input, Optional } from "@angular/core";
import { CommonModule } from "@angular/common";
import { Component, ElementRef, HostBinding, Input, Optional } from "@angular/core";
import { FormControlComponent } from "./form-control.component";
// Increments for each instance of this component
let nextId = 0;
@Directive({
@Component({
selector: "bit-label",
standalone: true,
templateUrl: "label.component.html",
imports: [CommonModule],
})
export class BitLabel {
constructor(
@@ -15,16 +19,16 @@ export class BitLabel {
) {}
@HostBinding("class") @Input() get classList() {
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"]);
return ["tw-inline-flex", "tw-gap-1", "tw-items-baseline", "tw-flex-row", "tw-min-w-0"];
}
@HostBinding("title") get title() {
return this.elementRef.nativeElement.textContent;
return this.elementRef.nativeElement.textContent.trim();
}
@HostBinding() @Input() id = `bit-label-${nextId++}`;
get isInsideFormControl() {
return !!this.parentFormControl;
}
}

View File

@@ -16,6 +16,30 @@
</ng-template>
<div *ngIf="!readOnly; else readOnlyView" class="tw-w-full tw-relative tw-group/bit-form-field">
<div class="tw-absolute tw-w-full tw-h-full tw-top-0 tw-pointer-events-none tw-z-20">
<div class="tw-w-full tw-h-full tw-flex">
<div
class="tw-min-w-3 tw-border-r-0 group-focus-within/bit-form-field:tw-border-r-0 !tw-rounded-l-lg"
[ngClass]="inputBorderClasses"
></div>
<div
class="tw-px-1 tw-shrink tw-min-w-0 tw-mt-px tw-border-x-0 tw-border-t-0 group-focus-within/bit-form-field:tw-border-x-0 group-focus-within/bit-form-field:tw-border-t-0 tw-hidden group-has-[bit-label]/bit-form-field:tw-block"
[ngClass]="inputBorderClasses"
>
<label
class="tw-flex tw-gap-1 tw-text-sm tw-text-muted -tw-translate-y-2.5 tw-mb-0 tw-max-w-full tw-pointer-events-auto"
[attr.for]="input.labelForId"
>
<ng-container *ngTemplateOutlet="labelContent"></ng-container>
<span *ngIf="input.required" class="tw-text-[0.625rem]"> ({{ "required" | i18n }})</span>
</label>
</div>
<div
class="tw-min-w-3 tw-grow tw-border-l-0 group-focus-within/bit-form-field:tw-border-l-0 !tw-rounded-r-lg"
[ngClass]="inputBorderClasses"
></div>
</div>
</div>
<div
class="tw-gap-1 tw-bg-background tw-rounded-lg tw-flex tw-min-h-11 [&:not(:has(button:enabled)):has(input:read-only)]:tw-bg-secondary-100 [&:not(:has(button:enabled)):has(textarea:read-only)]:tw-bg-secondary-100"
>
@@ -43,30 +67,6 @@
<ng-container *ngTemplateOutlet="suffixContent"></ng-container>
</div>
</div>
<div class="tw-absolute tw-w-full tw-h-full tw-top-0 tw-pointer-events-none">
<div class="tw-w-full tw-h-full tw-flex">
<div
class="tw-min-w-3 tw-border-r-0 group-focus-within/bit-form-field:tw-border-r-0 !tw-rounded-l-lg"
[ngClass]="inputBorderClasses"
></div>
<div
class="tw-px-1 tw-shrink tw-min-w-0 tw-mt-px tw-border-x-0 tw-border-t-0 group-focus-within/bit-form-field:tw-border-x-0 group-focus-within/bit-form-field:tw-border-t-0 tw-hidden group-has-[bit-label]/bit-form-field:tw-block"
[ngClass]="inputBorderClasses"
>
<label
class="tw-flex tw-gap-1 tw-text-sm tw-text-muted -tw-translate-y-2.5 tw-mb-0 tw-max-w-full tw-pointer-events-auto"
[attr.for]="input.labelForId"
>
<ng-container *ngTemplateOutlet="labelContent"></ng-container>
<span *ngIf="input.required" class="tw-text-[0.625rem]"> ({{ "required" | i18n }})</span>
</label>
</div>
<div
class="tw-min-w-3 tw-grow tw-border-l-0 group-focus-within/bit-form-field:tw-border-l-0 !tw-rounded-r-lg"
[ngClass]="inputBorderClasses"
></div>
</div>
</div>
</div>
<ng-template #readOnlyView>

View File

@@ -12,7 +12,7 @@ import {
} from "@angular/core";
import { BitHintComponent } from "../form-control/hint.component";
import { BitLabel } from "../form-control/label.directive";
import { BitLabel } from "../form-control/label.component";
import { inputBorderClasses } from "../input/input.directive";
import { BitErrorComponent } from "./error.component";

View File

@@ -132,7 +132,7 @@ export const LabelWithIcon: Story = {
<bit-form-field>
<bit-label>
Label
<a href="#">
<a href="#" slot="end" bitLink>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</bit-label>
@@ -160,6 +160,16 @@ export const LongLabel: Story = {
<input bitInput formControlName="name" />
<bit-hint>Optional Hint</bit-hint>
</bit-form-field>
<bit-form-field>
<bit-label>
Hello I am a very long label with lots of very cool helpful information
<a href="#" slot="end" bitLink>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</bit-label>
<input bitInput formControlName="name" />
<bit-hint>Optional Hint</bit-hint>
</bit-form-field>
</form>
`,
}),
@@ -252,7 +262,7 @@ export const Readonly: Story = {
</bit-form-field>
<bit-form-field>
<bit-label>Textarea <span bitBadge variant="success">Premium</span></bit-label>
<bit-label>Textarea <span slot="end" bitBadge variant="success">Premium</span></bit-label>
<textarea bitInput rows="3" readonly class="tw-resize-none">Row1
Row2
Row3</textarea>
@@ -290,7 +300,12 @@ export const ButtonInputGroup: Story = {
props: args,
template: /*html*/ `
<bit-form-field>
<bit-label>Label</bit-label>
<bit-label>
Label
<a href="#" slot="end" bitLink>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</bit-label>
<button bitPrefix bitIconButton="bwi-star" aria-label="Favorite"></button>
<input bitInput placeholder="Placeholder" />
<button bitSuffix bitIconButton="bwi-eye" aria-label="Hide"></button>

View File

@@ -55,6 +55,18 @@ Be sure to use an appropriate type attribute on fields when defining new field c
`email` for email address or `number` for numerical information) to take advantage of newer input
controls like email verification, number selection, and more.
Labels accept an optional `end` slot for any content that should not be truncated if the label text
is too long, such as info link buttons or badges.
```
<bit-label>
Label text goes here
<a href="#" slot="end" bitLink>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</bit-label>
```
#### Default with required attribute
<Story of={fieldStories.Default} />
@@ -86,6 +98,9 @@ button consists of a label and a radio input.
The full form control + label should be selectable to allow the user a larger click target.
Labels accept an optional `end` slot for any content that is not part of the main label text, such
as info link buttons or badges.
Radio groups should always have a default selected value.
Radio groups may optionally include extra helper text below each radio button.
@@ -112,6 +127,9 @@ Checkboxes can be displayed on their own or in a group (select multiple form que
displayed in a group, include an input Label and any associated required/validation logic for the
field.
Labels accept an optional `end` slot for any content that is not part of the main label text, such
as info link buttons or badges.
Unlike radio groups, checkbox groups are not required to have a default selected value.
Checkbox groups can include extra explanation text below each radio button or just the checkbox

View File

@@ -1,7 +1,7 @@
import { Component, ContentChild, HostBinding, Input, Optional, Self } from "@angular/core";
import { ControlValueAccessor, NgControl, Validators } from "@angular/forms";
import { BitLabel } from "../form-control/label.directive";
import { BitLabel } from "../form-control/label.component";
let nextId = 0;