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:
@@ -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>
|
||||
`,
|
||||
}),
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
14
libs/components/src/form-control/label.component.html
Normal file
14
libs/components/src/form-control/label.component.html
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user