1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +00:00

[CL-265] CL/extension refresh feature branch (#8696)

* [CL-245] Update palette to new light and dark theme colors (#8633)

* [CL-245] Add new color swatches to storybook (#8697)

* [CL-238] update typography (#8997)

* [CL-230] [CL-296] Update button styles (#9345)

* [CL-237] Update menu styles for extension refresh (#9525)

* [CL-267] Add 100-level color variants and update primary-600 (#9550)

* [CL-286] Update badge to use focus-visible instead of focus (#9551)

* [CL-250] Update badge styles for extension refresh (#9572)

* [CL-234] callout style refresh (#9920)

* [CL-233] Update form field styles (#9776)

* [CL-239][CL-251][CL-342] dialog style refresh (#10096)

* [CL-239] simple dialog style refresh

* [CL-342] fix text overflow in dialog; add story

* [CL-244] readonly fields (#10164)

* [CL-352] Fix Angular errors related to form element changes (#10211)

* [CL-273] Update styles for checkbox and form control (#10146)

* [CL-274] Update styling for radio button (#10333)

* [CL-338] Remove extra space in item content when end slot is empty (#10350)

* [CL-377] Fix extension style conflict for input background (#10351)

* [CL-271] Update styles for toggle (#10377)

* [CL-381] Update spacing around form elements (#10432)

* [CL-229] Update icon button styles (#10405)

* [CL-380] Remove hover state from disabled form fields (#10639)

* [CL-405] Allow toggle group input to be full width (#10658)

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

* [CL-383] Remove manual focus when password toggle is clicked (#10749)

* [CL-278][CL-391] misc bit-item style fixes (#10758)

* [CL-391] use pointer cursor on hover when link or button

* [CL-210] Change base font size from 14px to 16px (#10779)

* [CL-291] Finalize styling for chip select (#10771)

* [CL-257] update banner component styles (#10766)

* [CL-443] Fix sizing issues (#10893)

* [CL-445] Fix small sizing and spacing issues (#10962)

* [CL-382] Reduce element shifting on readonly hover (#10956)

* [CL-396] Update theme colors to new hexes (#10968)

* [CL-395] Remove text headers color (#10997)

* [CL-404] Switch to primary-600 for all focus indicators (#11015)

* [CL-397] Remove primary-500 (#11036)

* [CL-447] Ensure DM Sans displays correctly at all font weights (#11041)

* [CL-448] Scrollbar Styles (#11111)

* CL-252/update toast (#10996)

* [CL-275] Update link styles (#11174)

* [CL-446] Update hover state for unselected chip selects (#11172)

* [CL-454] Improve color a11y for toast and banner interactive elements (#11200)

* [CL-457] Center input text for select and multiselect (#11239)

* [CL-455] Do not use responsive margin for sections in dialogs or extension (#11243)

* [CL-459] Fix chip behavior when opening menu while item is selected (#11227)

* [CL-388] Update vertical nav colors for new palette (#11226)

* scope styled scrollbar to only select elements (#11247)

* edit radio buttons to be block inputs and update spacing (#11291)

* [CL-453] Fix multiselect chip spacing and truncation (#11300)

* [PM-11131] Prevent duplicated sr labels on form field icon buttons (#11383)

* [CL-303] Prevent chip menu from running offscreen (#11348)

* [CL-476] Fix DM Sans font on Windows (#11409)

* implements scrollbar styles for firefox/chrome and safari (#11447)

* [CL-472] Fix search background color in extension (#11466)

* [CL-481] Style updates for bit-item, bit-card, and primary-100 (#11473)

* [CL-478] Remove underline on hover for most components (#11477)

* [CL-477] Remove focus styles for readonly input (#11510)

* [CL-487] Fix vault items virtual scroll height (#11581)

* [PM-8625] Increase popup width (#11686)

* [CL-494] Wrap long words in toggle group (#11659)

* [CL-13820] Add class to remove link underline (#11762)

* [CL-435] Prevent Windows extension from shifting (#11851)

* [CL-503] Add notification color variables (#11802)

* [PM-14043] Update size of toggle group label to fit more content (#11881)

* [CL-498] Set chip menu width minimum to chip select width (#11905)

---------

Co-authored-by: Will Martin <contact@willmartian.com>
Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
Co-authored-by: Nick Krantz <125900171+nick-livefront@users.noreply.github.com>
Co-authored-by: Merissa Weinstein <merissa.k.weinstein@gmail.com>
Co-authored-by: Danielle Flinn <43477473+danielleflinn@users.noreply.github.com>
This commit is contained in:
Victoria League
2024-11-15 09:21:17 -05:00
committed by GitHub
parent 5c540a86f4
commit 3b5b2d6bd6
150 changed files with 1767 additions and 1059 deletions

View File

@@ -9,7 +9,7 @@ let nextId = 0;
selector: "bit-error",
template: `<i class="bwi bwi-error"></i> {{ displayError }}`,
host: {
class: "tw-block tw-mt-1 tw-text-danger",
class: "tw-block tw-mt-1 tw-text-danger tw-text-xs",
"aria-live": "assertive",
},
})

View File

@@ -19,5 +19,6 @@ export abstract class BitFormFieldControl {
error: [string, any];
type?: InputTypes;
spellcheck?: boolean;
readOnly?: boolean;
focus?: () => void;
}

View File

@@ -1,16 +1,114 @@
<label class="tw-mb-1 tw-block tw-font-semibold tw-text-main" [attr.for]="input.labelForId">
<ng-content select="bit-label"></ng-content>
<span *ngIf="input.required" class="tw-text-xs tw-font-normal"> ({{ "required" | i18n }})</span>
</label>
<div class="tw-flex">
<div *ngIf="prefixChildren.length" class="tw-flex">
<ng-content select="[bitPrefix]"></ng-content>
</div>
<!-- We need to use templates since the content slots are repeated between the readonly and read-write views. -->
<ng-template #defaultContent>
<ng-content></ng-content>
<div *ngIf="suffixChildren.length" class="tw-flex">
<ng-content select="[bitSuffix]"></ng-content>
</ng-template>
<ng-template #labelContent>
<ng-content select="bit-label"></ng-content>
</ng-template>
<ng-template #prefixContent>
<ng-content select="[bitPrefix]"></ng-content>
</ng-template>
<ng-template #suffixContent>
<ng-content select="[bitSuffix]"></ng-content>
</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-[0.675rem] 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] tw-relative tw-bottom-[-1px]">
({{ "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"
>
<div
#prefixContainer
class="tw-flex tw-items-center tw-gap-1 tw-pl-3 tw-py-2"
[hidden]="!prefixHasChildren()"
>
<ng-container *ngTemplateOutlet="prefixContent"></ng-container>
</div>
<div
class="default-content tw-w-full tw-relative tw-py-2 has-[bit-select]:tw-p-0 has-[bit-multi-select]:tw-p-0 has-[input:read-only:not([hidden])]:tw-bg-secondary-100 has-[textarea:read-only:not([hidden])]:tw-bg-secondary-100"
[ngClass]="[
prefixHasChildren() ? '' : 'tw-rounded-l-lg tw-pl-3',
suffixHasChildren() ? '' : 'tw-rounded-r-lg tw-pr-3',
]"
>
<ng-container *ngTemplateOutlet="defaultContent"></ng-container>
</div>
<div
#suffixContainer
class="tw-flex tw-items-center tw-gap-1 tw-pr-3 tw-py-2"
[hidden]="!suffixHasChildren()"
>
<ng-container *ngTemplateOutlet="suffixContent"></ng-container>
</div>
</div>
</div>
<ng-template #readOnlyView>
<div class="tw-w-full tw-relative">
<label
class="tw-flex tw-gap-1 tw-text-sm tw-text-muted tw-mb-0 tw-max-w-full"
[attr.for]="input.labelForId"
>
<ng-container *ngTemplateOutlet="labelContent"></ng-container>
</label>
<div
class="tw-gap-1 tw-flex tw-min-h-[1.85rem] tw-border-0 tw-border-solid"
[ngClass]="{
'tw-border-secondary-300/50 tw-border-b tw-pb-[2px]': !disableReadOnlyBorder,
'tw-border-transparent tw-pb-[3px]': disableReadOnlyBorder,
}"
>
<div
#prefixContainer
[hidden]="!prefixHasChildren()"
class="tw-flex tw-items-center tw-gap-1 tw-pl-1"
>
<ng-container *ngTemplateOutlet="prefixContent"></ng-container>
</div>
<div
class="default-content tw-w-full tw-pb-0 tw-relative [&>*]:tw-p-0 [&>*::selection]:tw-bg-primary-700 [&>*::selection]:tw-text-contrast"
>
<ng-container *ngTemplateOutlet="defaultContent"></ng-container>
</div>
<div
#suffixContainer
[hidden]="!suffixHasChildren()"
class="tw-flex tw-items-center tw-gap-1 tw-pr-1"
>
<ng-container *ngTemplateOutlet="suffixContent"></ng-container>
</div>
</div>
</div>
</ng-template>
<ng-container [ngSwitch]="input.hasError">
<ng-content select="bit-hint" *ngSwitchCase="false"></ng-content>
<bit-error [error]="input.error" *ngSwitchCase="true"></bit-error>

View File

@@ -1,22 +1,22 @@
import { coerceBooleanProperty } from "@angular/cdk/coercion";
import {
AfterContentChecked,
booleanAttribute,
Component,
ContentChild,
ContentChildren,
ElementRef,
HostBinding,
HostListener,
Input,
QueryList,
ViewChild,
signal,
} from "@angular/core";
import { BitHintComponent } from "../form-control/hint.component";
import { BitLabel } from "../form-control/label.component";
import { inputBorderClasses } from "../input/input.directive";
import { BitErrorComponent } from "./error.component";
import { BitFormFieldControl } from "./form-field-control";
import { BitPrefixDirective } from "./prefix.directive";
import { BitSuffixDirective } from "./suffix.directive";
@Component({
selector: "bit-form-field",
@@ -25,30 +25,74 @@ import { BitSuffixDirective } from "./suffix.directive";
export class BitFormFieldComponent implements AfterContentChecked {
@ContentChild(BitFormFieldControl) input: BitFormFieldControl;
@ContentChild(BitHintComponent) hint: BitHintComponent;
@ContentChild(BitLabel) label: BitLabel;
@ViewChild("prefixContainer") prefixContainer: ElementRef<HTMLDivElement>;
@ViewChild("suffixContainer") suffixContainer: ElementRef<HTMLDivElement>;
@ViewChild(BitErrorComponent) error: BitErrorComponent;
@ContentChildren(BitPrefixDirective) prefixChildren: QueryList<BitPrefixDirective>;
@ContentChildren(BitSuffixDirective) suffixChildren: QueryList<BitSuffixDirective>;
@Input({ transform: booleanAttribute })
disableMargin = false;
private _disableMargin = false;
@Input() set disableMargin(value: boolean | "") {
this._disableMargin = coerceBooleanProperty(value);
}
get disableMargin() {
return this._disableMargin;
}
/**
* NOTE: Placeholder to match the API of the form-field component in the `ps/extension` branch,
* no functionality is implemented as of now.
*/
/** If `true`, remove the bottom border for `readonly` inputs */
@Input({ transform: booleanAttribute })
disableReadOnlyBorder = false;
protected prefixHasChildren = signal(false);
protected suffixHasChildren = signal(false);
get inputBorderClasses(): string {
const shouldFocusBorderAppear = this.defaultContentIsFocused();
const groupClasses = [
this.input.hasError
? "group-hover/bit-form-field:tw-border-danger-700"
: "group-hover/bit-form-field:tw-border-primary-600",
// the next 2 selectors override the above hover selectors when the input (or text area) is non-interactive (i.e. readonly, disabled)
"group-has-[input:read-only]/bit-form-field:group-hover/bit-form-field:tw-border-secondary-500",
"group-has-[textarea:read-only]/bit-form-field:group-hover/bit-form-field:tw-border-secondary-500",
"group-focus-within/bit-form-field:tw-outline-none",
shouldFocusBorderAppear ? "group-focus-within/bit-form-field:tw-border-2" : "",
shouldFocusBorderAppear ? "group-focus-within/bit-form-field:tw-border-primary-600" : "",
shouldFocusBorderAppear
? "group-focus-within/bit-form-field:group-hover/bit-form-field:tw-border-primary-600"
: "",
];
const baseInputBorderClasses = inputBorderClasses(this.input.hasError);
const borderClasses = baseInputBorderClasses.concat(groupClasses);
return borderClasses.join(" ");
}
@HostBinding("class")
get classList() {
return ["tw-block"].concat(this.disableMargin ? [] : ["tw-mb-6"]);
return ["tw-block"]
.concat(this.disableMargin ? [] : ["tw-mb-4"])
.concat(this.readOnly ? [] : "tw-pt-2");
}
/**
* If the currently focused element is not part of the default content, then we don't want to show focus on the
* input field itself.
*
* This is necessary because the `tw-group/bit-form-field` wraps the input and any prefix/suffix
* buttons
*/
protected defaultContentIsFocused = signal(false);
@HostListener("focusin", ["$event.target"])
onFocusIn(target: HTMLElement) {
this.defaultContentIsFocused.set(target.matches(".default-content *:focus-visible"));
}
@HostListener("focusout")
onFocusOut() {
this.defaultContentIsFocused.set(false);
}
protected get readOnly(): boolean {
return this.input.readOnly;
}
ngAfterContentChecked(): void {
@@ -59,5 +103,8 @@ export class BitFormFieldComponent implements AfterContentChecked {
} else {
this.input.ariaDescribedBy = undefined;
}
this.prefixHasChildren.set(this.prefixContainer?.nativeElement.childElementCount > 0);
this.suffixHasChildren.set(this.suffixContainer?.nativeElement.childElementCount > 0);
}
}

View File

@@ -1,3 +1,4 @@
import { TextFieldModule } from "@angular/cdk/text-field";
import {
AbstractControl,
UntypedFormBuilder,
@@ -9,14 +10,19 @@ import {
} from "@angular/forms";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { A11yTitleDirective } from "@bitwarden/angular/src/directives/a11y-title.directive";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { AsyncActionsModule } from "../async-actions";
import { BadgeModule } from "../badge";
import { ButtonModule } from "../button";
import { CardComponent } from "../card";
import { CheckboxModule } from "../checkbox";
import { IconButtonModule } from "../icon-button";
import { InputModule } from "../input/input.module";
import { LinkModule } from "../link";
import { RadioButtonModule } from "../radio-button";
import { SectionComponent } from "../section";
import { SelectModule } from "../select";
import { I18nMockService } from "../utils/i18n-mock.service";
@@ -39,7 +45,13 @@ export default {
CheckboxModule,
RadioButtonModule,
SelectModule,
LinkModule,
CardComponent,
SectionComponent,
TextFieldModule,
BadgeModule,
],
declarations: [A11yTitleDirective],
providers: [
{
provide: I18nService,
@@ -49,6 +61,7 @@ export default {
required: "required",
inputRequired: "Input is required.",
inputEmail: "Input is not an email-address.",
toggleVisibility: "Toggle visibility",
});
},
},
@@ -74,6 +87,7 @@ const defaultFormObj = fb.group({
email: ["", [Validators.required, Validators.email, forbiddenNameValidator(/bit/i)]],
terms: [false, [Validators.requiredTrue]],
updates: ["yes"],
file: [""],
});
// Custom error message, `message` is shown as the error message
@@ -96,7 +110,7 @@ export const Default: Story = {
submit: submit,
...args,
},
template: `
template: /*html*/ `
<form [formGroup]="formObj">
<bit-form-field>
<bit-label>Label</bit-label>
@@ -108,13 +122,68 @@ export const Default: Story = {
}),
};
export const LabelWithIcon: Story = {
render: (args) => ({
props: {
formObj: defaultFormObj,
submit: submit,
...args,
},
template: /*html*/ `
<form [formGroup]="formObj">
<bit-form-field>
<bit-label>
Label
<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>
`,
}),
};
export const LongLabel: Story = {
render: (args) => ({
props: {
formObj: defaultFormObj,
submit: submit,
...args,
},
template: /*html*/ `
<form [formGroup]="formObj" style="width: 200px">
<bit-form-field>
<bit-label>
Hello I am a very long label with lots of very cool helpful information
</bit-label>
<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>
`,
}),
};
export const Required: Story = {
render: (args) => ({
props: {
formObj: formObj,
...args,
},
template: `
template: /*html*/ `
<bit-form-field>
<bit-label>Label</bit-label>
<input bitInput required placeholder="Placeholder" />
@@ -134,7 +203,7 @@ export const Hint: Story = {
formObj: formObj,
...args,
},
template: `
template: /*html*/ `
<bit-form-field [formGroup]="formObj">
<bit-label>FormControl</bit-label>
<input bitInput formControlName="required" placeholder="Placeholder" />
@@ -147,7 +216,7 @@ export const Hint: Story = {
export const Disabled: Story = {
render: (args) => ({
props: args,
template: `
template: /*html*/ `
<bit-form-field>
<bit-label>Label</bit-label>
<input bitInput placeholder="Placeholder" disabled />
@@ -160,16 +229,54 @@ export const Disabled: Story = {
export const Readonly: Story = {
render: (args) => ({
props: args,
template: `
template: /*html*/ `
<bit-form-field>
<bit-label>Input</bit-label>
<input bitInput value="Foobar" readonly />
</bit-form-field>
<bit-form-field>
<bit-label>Input</bit-label>
<input bitInput type="password" value="Foobar" [readonly]="true" />
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
<button type="button" bitSuffix bitIconButton="bwi-clone" [appA11yTitle]="'Clone Input'"></button>
</bit-form-field>
<bit-form-field>
<bit-label>Textarea</bit-label>
<textarea bitInput rows="4" readonly>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</textarea>
</bit-form-field>
<div class="tw-p-4 tw-mt-10 tw-border-2 tw-border-solid tw-border-black tw-bg-background-alt">
<h2 bitTypography="h2">Inside card</h2>
<bit-section>
<bit-card>
<bit-form-field>
<bit-label>Input</bit-label>
<input bitInput value="Foobar" readonly />
</bit-form-field>
<bit-form-field>
<bit-label>Input</bit-label>
<input bitInput type="password" value="Foobar" readonly />
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
<button type="button" bitSuffix bitIconButton="bwi-clone" [appA11yTitle]="'Clone Input'"></button>
</bit-form-field>
<bit-form-field>
<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>
</bit-form-field>
<bit-form-field disableMargin disableReadOnlyBorder>
<bit-label>Sans margin & border</bit-label>
<input bitInput value="Foobar" readonly />
</bit-form-field>
</bit-card>
</bit-section>
</div>
`,
}),
args: {},
@@ -178,7 +285,7 @@ export const Readonly: Story = {
export const InputGroup: Story = {
render: (args) => ({
props: args,
template: `
template: /*html*/ `
<bit-form-field>
<bit-label>Label</bit-label>
<input bitInput placeholder="Placeholder" />
@@ -193,13 +300,19 @@ export const InputGroup: Story = {
export const ButtonInputGroup: Story = {
render: (args) => ({
props: args,
template: `
template: /*html*/ `
<bit-form-field>
<button bitPrefix bitIconButton="bwi-star"></button>
<bit-label>
Label
<a href="#" slot="end" bitLink [appA11yTitle]="'More info'">
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</bit-label>
<button bitPrefix bitIconButton="bwi-star" [appA11yTitle]="'Favorite Label'"></button>
<input bitInput placeholder="Placeholder" />
<button bitSuffix bitIconButton="bwi-eye"></button>
<button bitSuffix bitIconButton="bwi-clone"></button>
<button bitSuffix bitButton>
<button bitSuffix bitIconButton="bwi-eye" [appA11yTitle]="'Hide Label'"></button>
<button bitSuffix bitIconButton="bwi-clone" [appA11yTitle]="'Clone Label'"></button>
<button bitSuffix bitLink>
Apply
</button>
</bit-form-field>
@@ -211,14 +324,32 @@ export const ButtonInputGroup: Story = {
export const DisabledButtonInputGroup: Story = {
render: (args) => ({
props: args,
template: `
template: /*html*/ `
<bit-form-field>
<bit-label>Label</bit-label>
<button bitPrefix bitIconButton="bwi-star" disabled></button>
<button bitPrefix bitIconButton="bwi-star" disabled [appA11yTitle]="'Favorite Label'"></button>
<input bitInput placeholder="Placeholder" disabled />
<button bitSuffix bitIconButton="bwi-eye" disabled></button>
<button bitSuffix bitIconButton="bwi-clone" disabled></button>
<button bitSuffix bitButton disabled>
<button bitSuffix bitIconButton="bwi-eye" disabled [appA11yTitle]="'Hide Label'"></button>
<button bitSuffix bitIconButton="bwi-clone" disabled [appA11yTitle]="'Clone Label'"></button>
<button bitSuffix bitLink disabled>
Apply
</button>
</bit-form-field>
`,
}),
args: {},
};
export const PartiallyDisabledButtonInputGroup: Story = {
render: (args) => ({
props: args,
template: /*html*/ `
<bit-form-field>
<bit-label>Label</bit-label>
<input bitInput placeholder="Placeholder" disabled />
<button bitSuffix bitIconButton="bwi-eye" [appA11yTitle]="'Hide Label'"></button>
<button bitSuffix bitIconButton="bwi-clone" [appA11yTitle]="'Clone Label'"></button>
<button bitSuffix bitLink disabled>
Apply
</button>
</bit-form-field>
@@ -230,7 +361,7 @@ export const DisabledButtonInputGroup: Story = {
export const Select: Story = {
render: (args: BitFormFieldComponent) => ({
props: args,
template: `
template: /*html*/ `
<bit-form-field>
<bit-label>Label</bit-label>
<select bitInput>
@@ -246,7 +377,7 @@ export const Select: Story = {
export const AdvancedSelect: Story = {
render: (args: BitFormFieldComponent) => ({
props: args,
template: `
template: /*html*/ `
<bit-form-field>
<bit-label>Label</bit-label>
<bit-select>
@@ -258,10 +389,40 @@ export const AdvancedSelect: Story = {
}),
};
export const FileInput: Story = {
render: (args) => ({
props: {
formObj: defaultFormObj,
submit: submit,
...args,
},
template: /*html*/ `
<form [formGroup]="formObj">
<bit-form-field>
<bit-label>File</bit-label>
<div class="tw-text-main">
<button bitButton type="button" buttonType="secondary">
Choose File
</button>
No file chosen
</div>
<input
bitInput
#fileSelector
type="file"
formControlName="file"
hidden
/>
</bit-form-field>
</form>
`,
}),
};
export const Textarea: Story = {
render: (args: BitFormFieldComponent) => ({
props: args,
template: `
template: /*html*/ `
<bit-form-field>
<bit-label>Textarea</bit-label>
<textarea bitInput rows="4"></textarea>

View File

@@ -66,9 +66,9 @@ export const actionsData = {
};
const fb = new FormBuilder();
const formObjFactory = () =>
const formObjFactory = (isDisabled = false) =>
fb.group({
select: [[], [Validators.required]],
select: fb.control({ value: [], disabled: isDisabled }, { validators: [Validators.required] }),
});
function submit(formObj: FormGroup) {
@@ -85,7 +85,7 @@ export const Loading: Story = {
...args,
onItemsConfirmed: actionsData.onItemsConfirmed,
},
template: `
template: /*html*/ `
<form [formGroup]="formObj" (ngSubmit)="submit(formObj)">
<bit-form-field>
<bit-label>{{ name }}</bit-label>
@@ -100,7 +100,6 @@ export const Loading: Story = {
</bit-multi-select>
<bit-hint>{{ hint }}</bit-hint>
</bit-form-field>
<button type="submit" bitButton buttonType="primary">Submit</button>
</form>
`,
}),
@@ -113,8 +112,33 @@ export const Loading: Story = {
};
export const Disabled: Story = {
...Loading,
render: (args) => ({
props: {
formObj: formObjFactory(true),
submit: submit,
...args,
onItemsConfirmed: actionsData.onItemsConfirmed,
},
template: /*html*/ `
<form [formGroup]="formObj" (ngSubmit)="submit(formObj)">
<bit-form-field>
<bit-label>{{ name }}</bit-label>
<bit-multi-select
class="tw-w-full"
formControlName="select"
[baseItems]="baseItems"
[removeSelectedItems]="removeSelectedItems"
[loading]="loading"
[disabled]="disabled"
(onItemsConfirmed)="onItemsConfirmed($event)">
</bit-multi-select>
<bit-hint>{{ hint }}</bit-hint>
</bit-form-field>
</form>
`,
}),
args: {
baseItems: [] as any,
name: "Disabled",
disabled: true,
hint: "This is what a disabled multi-select looks like",
@@ -178,7 +202,7 @@ export const Members: Story = {
{
id: "7",
listName: "Final listName (fname@mail.me)",
labelName: "(fname@mail.me)",
labelName: "Final listName (fname@mail.me)",
icon: "bwi-user",
},
],
@@ -269,34 +293,3 @@ export const RemoveSelected: Story = {
removeSelectedItems: true,
},
};
export const Standalone: Story = {
render: (args) => ({
props: {
...args,
onItemsConfirmed: actionsData.onItemsConfirmed,
},
template: `
<bit-multi-select
class="tw-w-full"
[baseItems]="baseItems"
[removeSelectedItems]="removeSelectedItems"
[loading]="loading"
[disabled]="disabled"
(onItemsConfirmed)="onItemsConfirmed($event)">
</bit-multi-select>
`,
}),
args: {
baseItems: [
{ id: "1", listName: "Group 1", labelName: "Group 1", icon: "bwi-family" },
{ id: "2", listName: "Group 2", labelName: "Group 2", icon: "bwi-family" },
{ id: "3", listName: "Group 3", labelName: "Group 3", icon: "bwi-family" },
{ id: "4", listName: "Group 4", labelName: "Group 4", icon: "bwi-family" },
{ id: "5", listName: "Group 5", labelName: "Group 5", icon: "bwi-family" },
{ id: "6", listName: "Group 6", labelName: "Group 6", icon: "bwi-family" },
{ id: "7", listName: "Group 7", labelName: "Group 7", icon: "bwi-family" },
],
removeSelectedItems: true,
},
};

View File

@@ -37,8 +37,6 @@ export class BitPasswordInputToggleDirective implements AfterContentInit, OnChan
this.toggledChange.emit(this.toggled);
this.update();
this.formField.input?.focus();
}
constructor(

View File

@@ -43,7 +43,7 @@ type Story = StoryObj<BitPasswordInputToggleDirective>;
export const Default: Story = {
render: (args) => ({
props: args,
template: `
template: /*html*/ `
<form>
<bit-form-field>
<bit-label>Password</bit-label>
@@ -58,7 +58,7 @@ export const Default: Story = {
export const Binding: Story = {
render: (args) => ({
props: args,
template: `
template: /*html*/ `
<form>
<bit-form-field>
<bit-label>Password</bit-label>

View File

@@ -1,51 +1,20 @@
import { Directive, HostBinding, Input, OnInit, Optional } from "@angular/core";
import { ButtonLikeAbstraction } from "../shared/button-like.abstraction";
export const PrefixClasses = [
"tw-bg-background-alt",
"tw-border",
"tw-border-solid",
"tw-border-secondary-600",
"tw-text-muted",
"tw-rounded-none",
];
export const PrefixButtonClasses = [
"hover:tw-bg-text-muted",
"hover:tw-text-contrast",
"disabled:tw-opacity-100",
"disabled:tw-bg-secondary-100",
"disabled:hover:tw-bg-secondary-100",
"disabled:hover:tw-text-muted",
"focus-visible:tw-ring-primary-700",
"focus-visible:tw-border-primary-700",
"focus-visible:tw-ring-1",
"focus-visible:tw-ring-inset",
"focus-visible:tw-ring-primary-700",
"focus-visible:tw-z-10",
];
export const PrefixStaticContentClasses = ["tw-block", "tw-px-3", "tw-py-1.5"];
import { BitIconButtonComponent } from "../icon-button/icon-button.component";
@Directive({
selector: "[bitPrefix]",
})
export class BitPrefixDirective implements OnInit {
constructor(@Optional() private buttonComponent: ButtonLikeAbstraction) {}
@HostBinding("class") @Input() get classList() {
return PrefixClasses.concat([
"tw-border-r-0",
"first:tw-rounded-l",
"focus-visible:tw-border-r",
"focus-visible:tw-mr-[-1px]",
]).concat(this.buttonComponent != undefined ? PrefixButtonClasses : PrefixStaticContentClasses);
return ["tw-text-muted"];
}
ngOnInit(): void {
this.buttonComponent?.setButtonType("unstyled");
constructor(@Optional() private iconButtonComponent: BitIconButtonComponent) {}
ngOnInit() {
if (this.iconButtonComponent) {
this.iconButtonComponent.size = "small";
}
}
}

View File

@@ -1,26 +1,20 @@
import { Directive, HostBinding, Input, OnInit, Optional } from "@angular/core";
import { ButtonLikeAbstraction } from "../shared/button-like.abstraction";
import { PrefixButtonClasses, PrefixClasses, PrefixStaticContentClasses } from "./prefix.directive";
import { BitIconButtonComponent } from "../icon-button/icon-button.component";
@Directive({
selector: "[bitSuffix]",
})
export class BitSuffixDirective implements OnInit {
constructor(@Optional() private buttonComponent: ButtonLikeAbstraction) {}
@HostBinding("class") @Input() get classList() {
return PrefixClasses.concat([
"tw-border-l-0",
"last:tw-rounded-r",
"focus-visible:tw-border-l",
"focus-visible:tw-ml-[-1px]",
]).concat(this.buttonComponent != undefined ? PrefixButtonClasses : PrefixStaticContentClasses);
return ["tw-text-muted"];
}
ngOnInit(): void {
this.buttonComponent?.setButtonType("unstyled");
constructor(@Optional() private iconButtonComponent: BitIconButtonComponent) {}
ngOnInit() {
if (this.iconButtonComponent) {
this.iconButtonComponent.size = "small";
}
}
}