mirror of
https://github.com/bitwarden/browser
synced 2026-02-22 20:34:04 +00:00
resolve merge conflicts
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
[title]="label"
|
||||
#menuTrigger="menuTrigger"
|
||||
(click)="setMenuWidth()"
|
||||
#chipSelectButton
|
||||
>
|
||||
<span class="tw-inline-flex tw-items-center tw-gap-1.5 tw-truncate">
|
||||
<i class="bwi !tw-text-[inherit]" [ngClass]="icon"></i>
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
DestroyRef,
|
||||
ElementRef,
|
||||
HostBinding,
|
||||
HostListener,
|
||||
Input,
|
||||
@@ -45,6 +46,7 @@ export type ChipSelectOption<T> = Option<T> & {
|
||||
export class ChipSelectComponent<T = unknown> implements ControlValueAccessor, AfterViewInit {
|
||||
@ViewChild(MenuComponent) menu: MenuComponent;
|
||||
@ViewChildren(MenuItemDirective) menuItems: QueryList<MenuItemDirective>;
|
||||
@ViewChild("chipSelectButton") chipSelectButton: ElementRef<HTMLButtonElement>;
|
||||
|
||||
/** Text to show when there is no selected option */
|
||||
@Input({ required: true }) placeholderText: string;
|
||||
@@ -210,11 +212,16 @@ export class ChipSelectComponent<T = unknown> implements ControlValueAccessor, A
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the width of the menu according to the initially rendered options
|
||||
* Calculate the width of the menu based on whichever is larger, the chip select width or the width of
|
||||
* the initially rendered options
|
||||
*/
|
||||
protected setMenuWidth() {
|
||||
this.menuWidth =
|
||||
const chipWidth = this.chipSelectButton.nativeElement.getBoundingClientRect().width;
|
||||
|
||||
const firstMenuItemWidth =
|
||||
this.menu.menuItems.first.elementRef.nativeElement.getBoundingClientRect().width;
|
||||
|
||||
this.menuWidth = Math.max(chipWidth, firstMenuItemWidth);
|
||||
}
|
||||
|
||||
/** Control Value Accessor */
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Directive, HostBinding, HostListener, Input } from "@angular/core";
|
||||
|
||||
import { DisclosureComponent } from "./disclosure.component";
|
||||
|
||||
@Directive({
|
||||
selector: "[bitDisclosureTriggerFor]",
|
||||
exportAs: "disclosureTriggerFor",
|
||||
standalone: true,
|
||||
})
|
||||
export class DisclosureTriggerForDirective {
|
||||
/**
|
||||
* Accepts template reference for a bit-disclosure component instance
|
||||
*/
|
||||
@Input("bitDisclosureTriggerFor") disclosure: DisclosureComponent;
|
||||
|
||||
@HostBinding("attr.aria-expanded") get ariaExpanded() {
|
||||
return this.disclosure.open;
|
||||
}
|
||||
|
||||
@HostBinding("attr.aria-controls") get ariaControls() {
|
||||
return this.disclosure.id;
|
||||
}
|
||||
|
||||
@HostListener("click") click() {
|
||||
this.disclosure.open = !this.disclosure.open;
|
||||
}
|
||||
}
|
||||
21
libs/components/src/disclosure/disclosure.component.ts
Normal file
21
libs/components/src/disclosure/disclosure.component.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Component, HostBinding, Input, booleanAttribute } from "@angular/core";
|
||||
|
||||
let nextId = 0;
|
||||
|
||||
@Component({
|
||||
selector: "bit-disclosure",
|
||||
standalone: true,
|
||||
template: `<ng-content></ng-content>`,
|
||||
})
|
||||
export class DisclosureComponent {
|
||||
/**
|
||||
* Optionally init the disclosure in its opened state
|
||||
*/
|
||||
@Input({ transform: booleanAttribute }) open?: boolean = false;
|
||||
|
||||
@HostBinding("class") get classList() {
|
||||
return this.open ? "" : "tw-hidden";
|
||||
}
|
||||
|
||||
@HostBinding("id") id = `bit-disclosure-${nextId++}`;
|
||||
}
|
||||
55
libs/components/src/disclosure/disclosure.mdx
Normal file
55
libs/components/src/disclosure/disclosure.mdx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./disclosure.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
```ts
|
||||
import { DisclosureComponent, DisclosureTriggerForDirective } from "@bitwarden/components";
|
||||
```
|
||||
|
||||
# Disclosure
|
||||
|
||||
The `bit-disclosure` component is used in tandem with the `bitDisclosureTriggerFor` directive to
|
||||
create an accessible content area whose visibility is controlled by a trigger button.
|
||||
|
||||
To compose a disclosure and trigger:
|
||||
|
||||
1. Create a trigger component (see "Supported Trigger Components" section below)
|
||||
2. Create a `bit-disclosure`
|
||||
3. Set a template reference on the `bit-disclosure`
|
||||
4. Use the `bitDisclosureTriggerFor` directive on the trigger component, and pass it the
|
||||
`bit-disclosure` template reference
|
||||
5. Set the `open` property on the `bit-disclosure` to init the disclosure as either currently
|
||||
expanded or currently collapsed. The disclosure will default to `false`, meaning it defaults to
|
||||
being hidden.
|
||||
|
||||
```
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-sliders"
|
||||
[buttonType]="'muted'"
|
||||
[bitDisclosureTriggerFor]="disclosureRef"
|
||||
></button>
|
||||
<bit-disclosure #disclosureRef open>click button to hide this content</bit-disclosure>
|
||||
```
|
||||
|
||||
<Story of={stories.DisclosureWithIconButton} />
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
## Supported Trigger Components
|
||||
|
||||
This is the list of currently supported trigger components:
|
||||
|
||||
- Icon button `muted` variant
|
||||
|
||||
## Accessibility
|
||||
|
||||
The disclosure and trigger directive functionality follow the
|
||||
[Disclosure (Show/Hide)](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/) pattern for
|
||||
accessibility, automatically handling the `aria-controls` and `aria-expanded` properties. A `button`
|
||||
element must be used as the trigger for the disclosure. The `button` element must also have an
|
||||
accessible label/title -- please follow the accessibility guidelines for whatever trigger component
|
||||
you choose.
|
||||
29
libs/components/src/disclosure/disclosure.stories.ts
Normal file
29
libs/components/src/disclosure/disclosure.stories.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
||||
|
||||
import { IconButtonModule } from "../icon-button";
|
||||
|
||||
import { DisclosureTriggerForDirective } from "./disclosure-trigger-for.directive";
|
||||
import { DisclosureComponent } from "./disclosure.component";
|
||||
|
||||
export default {
|
||||
title: "Component Library/Disclosure",
|
||||
component: DisclosureComponent,
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [DisclosureTriggerForDirective, DisclosureComponent, IconButtonModule],
|
||||
}),
|
||||
],
|
||||
} as Meta<DisclosureComponent>;
|
||||
|
||||
type Story = StoryObj<DisclosureComponent>;
|
||||
|
||||
export const DisclosureWithIconButton: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<button type="button" bitIconButton="bwi-sliders" [buttonType]="'muted'" [bitDisclosureTriggerFor]="disclosureRef">
|
||||
</button>
|
||||
<bit-disclosure #disclosureRef class="tw-text-main tw-block" open>click button to hide this content</bit-disclosure>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
2
libs/components/src/disclosure/index.ts
Normal file
2
libs/components/src/disclosure/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./disclosure-trigger-for.directive";
|
||||
export * from "./disclosure.component";
|
||||
@@ -52,10 +52,14 @@ const styles: Record<IconButtonType, string[]> = {
|
||||
"tw-bg-transparent",
|
||||
"!tw-text-muted",
|
||||
"tw-border-transparent",
|
||||
"aria-expanded:tw-bg-text-muted",
|
||||
"aria-expanded:!tw-text-contrast",
|
||||
"hover:tw-bg-transparent-hover",
|
||||
"hover:tw-border-primary-600",
|
||||
"focus-visible:before:tw-ring-primary-600",
|
||||
"disabled:!tw-text-secondary-300",
|
||||
"aria-expanded:hover:tw-bg-secondary-700",
|
||||
"aria-expanded:hover:tw-border-secondary-700",
|
||||
"disabled:hover:tw-border-transparent",
|
||||
"disabled:hover:tw-bg-transparent",
|
||||
...focusRing,
|
||||
|
||||
@@ -29,8 +29,6 @@ Icon buttons can be found in other components such as: the
|
||||
[dialog](?path=/docs/component-library-dialogs--docs), and
|
||||
[table](?path=/docs/component-library-table--docs).
|
||||
|
||||
<Story id="component-library-banner--premium" />
|
||||
|
||||
## Styles
|
||||
|
||||
There are 4 common styles for button main, muted, contrast, and danger. The other styles follow the
|
||||
@@ -40,48 +38,48 @@ button component styles.
|
||||
|
||||
Used for general icon buttons appearing on the theme’s main `background`
|
||||
|
||||
<Story id="component-library-icon-button--main" />
|
||||
<Story of={stories.Main} />
|
||||
|
||||
### Muted
|
||||
|
||||
Used for low emphasis icon buttons appearing on the theme’s main `background`
|
||||
|
||||
<Story id="component-library-icon-button--muted" />
|
||||
<Story of={stories.Muted} />
|
||||
|
||||
### Contrast
|
||||
|
||||
Used on a theme’s colored or contrasting backgrounds such as in the navigation or on toasts and
|
||||
banners.
|
||||
|
||||
<Story id="component-library-icon-button--contrast" />
|
||||
<Story of={stories.Contrast} />
|
||||
|
||||
### Danger
|
||||
|
||||
Danger is used for “trash” actions throughout the experience, most commonly in the bottom right of
|
||||
the dialog component.
|
||||
|
||||
<Story id="component-library-icon-button--danger" />
|
||||
<Story of={stories.Danger} />
|
||||
|
||||
### Primary
|
||||
|
||||
Used in place of the main button component if no text is used. This allows the button to display
|
||||
square.
|
||||
|
||||
<Story id="component-library-icon-button--primary" />
|
||||
<Story of={stories.Primary} />
|
||||
|
||||
### Secondary
|
||||
|
||||
Used in place of the main button component if no text is used. This allows the button to display
|
||||
square.
|
||||
|
||||
<Story id="component-library-icon-button--secondary" />
|
||||
<Story of={stories.Secondary} />
|
||||
|
||||
### Light
|
||||
|
||||
Used on a background that is dark in both light theme and dark theme. Example: end user navigation
|
||||
styles.
|
||||
|
||||
<Story id="component-library-icon-button--light" />
|
||||
<Story of={stories.Light} />
|
||||
|
||||
**Note:** Main and contrast styles appear on backgrounds where using `primary-700` as a focus
|
||||
indicator does not meet WCAG graphic contrast guidelines.
|
||||
@@ -95,11 +93,11 @@ with less padding around the icon, such as in the navigation component.
|
||||
|
||||
### Small
|
||||
|
||||
<Story id="component-library-icon-button--small" />
|
||||
<Story of={stories.Small} />
|
||||
|
||||
### Default
|
||||
|
||||
<Story id="component-library-icon-button--default" />
|
||||
<Story of={stories.Default} />
|
||||
|
||||
## Accessibility
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ type Story = StoryObj<BitIconButtonComponent>;
|
||||
export const Default: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<div class="tw-space-x-4">
|
||||
<button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="main" [size]="size">Button</button>
|
||||
<button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="muted" [size]="size">Button</button>
|
||||
@@ -56,7 +56,7 @@ export const Small: Story = {
|
||||
export const Primary: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size">Button</button>
|
||||
`,
|
||||
}),
|
||||
@@ -96,7 +96,7 @@ export const Muted: Story = {
|
||||
export const Light: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<div class="tw-bg-background-alt2 tw-p-6 tw-w-full tw-inline-block">
|
||||
<button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size">Button</button>
|
||||
</div>
|
||||
@@ -110,7 +110,7 @@ export const Light: Story = {
|
||||
export const Contrast: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
template: /*html*/ `
|
||||
<div class="tw-bg-primary-600 tw-p-6 tw-w-full tw-inline-block">
|
||||
<button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size">Button</button>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./search";
|
||||
export * from "./security";
|
||||
export * from "./no-access";
|
||||
export * from "./no-results";
|
||||
|
||||
50
libs/components/src/icon/icons/security.ts
Normal file
50
libs/components/src/icon/icons/security.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { svgIcon } from "../icon";
|
||||
|
||||
export const Security = svgIcon`
|
||||
<svg width="96" height="96" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="96" height="96" class="tw-fill-background"/>
|
||||
<rect x="5" y="5" width="86" height="77" rx="7" class="tw-stroke-art-primary" stroke-width="2" stroke-linecap="round"/>
|
||||
<rect x="63" y="15" width="18" height="18" rx="3" class="tw-stroke-art-primary" stroke-width="2" stroke-linecap="round"/>
|
||||
<rect x="39" y="15" width="18" height="18" rx="3" class="tw-stroke-art-primary" stroke-width="2" stroke-linecap="round"/>
|
||||
<rect x="15" y="15" width="18" height="18" rx="3" class="tw-stroke-art-primary" stroke-width="2" stroke-linecap="round"/>
|
||||
<rect x="13" y="41" width="70" height="14" rx="7" class="tw-stroke-art-primary tw-fill-background" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M21.0039 48.3526V45.5728" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.0039 48.3525L23.6272 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.0039 48.3524L22.6279 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.0029 48.3524L19.3789 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.0022 48.3525L18.3789 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M30.0039 48.3526V45.5728" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M30.0039 48.3525L32.6272 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M30.0039 48.3524L31.6279 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M30.0029 48.3524L28.3789 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M30.0022 48.3525L27.3789 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M39.0039 48.3526V45.5728" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M39.0039 48.3525L41.6272 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M39.0039 48.3524L40.6279 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M39.0029 48.3524L37.3789 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M39.0022 48.3525L36.3789 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M48.0039 48.3526V45.5728" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M48.0039 48.3525L50.6272 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M48.0039 48.3524L49.6279 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M48.0029 48.3524L46.3789 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M48.0022 48.3525L45.3789 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M57.0039 48.3526V45.5728" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M57.0039 48.3525L59.6272 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M57.0039 48.3524L58.6279 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M57.0029 48.3524L55.3789 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M57.0022 48.3525L54.3789 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M66.0039 48.3526V45.5728" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M66.0039 48.3525L68.6272 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M66.0039 48.3524L67.6279 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M66.0029 48.3524L64.3789 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M66.0022 48.3525L63.3789 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M75.0039 48.3526V45.5728" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M75.0039 48.3525L77.6272 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M75.0039 48.3524L76.6279 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M75.0029 48.3524L73.3789 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M75.0022 48.3525L72.3789 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<rect x="35" y="72" width="26" height="20" rx="2" class="tw-stroke-art-primary tw-fill-background" stroke-width="2"/>
|
||||
<rect x="47" y="78" width="2" height="8" rx="1" class="tw-stroke-art-accent"/>
|
||||
<path d="M55 71V69C55 65.134 51.866 62 48 62V62C44.134 62 41 65.134 41 69V71" class="tw-stroke-art-primary" stroke-width="2"/>
|
||||
</svg>
|
||||
`;
|
||||
@@ -13,6 +13,7 @@ export * from "./chip-select";
|
||||
export * from "./color-password";
|
||||
export * from "./container";
|
||||
export * from "./dialog";
|
||||
export * from "./disclosure";
|
||||
export * from "./form-field";
|
||||
export * from "./icon-button";
|
||||
export * from "./icon";
|
||||
|
||||
@@ -71,7 +71,7 @@ The content can be a button, anchor, or static container.
|
||||
<bit-item>
|
||||
<button bit-item-content type="button">
|
||||
<bit-avatar slot="start" text="Foo"></bit-avatar>
|
||||
foo@bitwarden.com
|
||||
foo@bitwarden.com
|
||||
<span bitBadge variant="primary" slot="default-trailing">Auto-fill</span>
|
||||
<ng-container slot="secondary">
|
||||
<div>Bitwarden.com</div>
|
||||
|
||||
@@ -94,7 +94,7 @@ export const ContentSlots: Story = {
|
||||
slot="start"
|
||||
[text]="'Foo'"
|
||||
></bit-avatar>
|
||||
foo@bitwarden.com
|
||||
foo@bitwarden.com
|
||||
<ng-container slot="secondary">
|
||||
<div>Bitwarden.com</div>
|
||||
<div><em>locked</em></div>
|
||||
@@ -287,37 +287,37 @@ export const SingleActionList: Story = {
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
</bit-item-group>
|
||||
`,
|
||||
@@ -334,14 +334,14 @@ export const SingleActionWithBadge: Story = {
|
||||
Foobar
|
||||
<span bitBadge variant="primary" slot="default-trailing">Auto-fill</span>
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Helloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo!
|
||||
<span bitBadge variant="primary" slot="default-trailing">Auto-fill</span>
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
</bit-item-group>
|
||||
`,
|
||||
|
||||
@@ -28,6 +28,7 @@ const commonStyles = [
|
||||
"tw-border-none",
|
||||
"tw-rounded",
|
||||
"tw-transition",
|
||||
"tw-no-underline",
|
||||
"hover:tw-underline",
|
||||
"hover:tw-decoration-1",
|
||||
"disabled:tw-no-underline",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
(blur)="onBlur()"
|
||||
[labelForId]="labelForId"
|
||||
[clearable]="false"
|
||||
(close)="onClose()"
|
||||
appendTo="body"
|
||||
>
|
||||
<ng-template ng-option-tmp let-item="item">
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
QueryList,
|
||||
Self,
|
||||
ViewChild,
|
||||
Output,
|
||||
EventEmitter,
|
||||
} from "@angular/core";
|
||||
import { ControlValueAccessor, NgControl, Validators } from "@angular/forms";
|
||||
import { NgSelectComponent } from "@ng-select/ng-select";
|
||||
@@ -31,6 +33,7 @@ export class SelectComponent<T> implements BitFormFieldControl, ControlValueAcce
|
||||
/** Optional: Options can be provided using an array input or using `bit-option` */
|
||||
@Input() items: Option<T>[] = [];
|
||||
@Input() placeholder = this.i18nService.t("selectPlaceholder");
|
||||
@Output() closed = new EventEmitter();
|
||||
|
||||
protected selectedValue: T;
|
||||
protected selectedOption: Option<T>;
|
||||
@@ -156,4 +159,9 @@ export class SelectComponent<T> implements BitFormFieldControl, ControlValueAcce
|
||||
private findSelectedOption(items: Option<T>[], value: T): Option<T> | undefined {
|
||||
return items.find((item) => item.value === value);
|
||||
}
|
||||
|
||||
/**Emits the closed event. */
|
||||
protected onClose() {
|
||||
this.closed.emit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,10 @@ export const Table = (args) => (
|
||||
{Row("info-600")}
|
||||
{Row("info-700")}
|
||||
</tbody>
|
||||
<tbody>
|
||||
{Row("notification-100")}
|
||||
{Row("notification-600")}
|
||||
</tbody>
|
||||
<tbody>
|
||||
{Row("art-primary")}
|
||||
{Row("art-accent")}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { coerceBooleanProperty } from "@angular/cdk/coercion";
|
||||
import { Component, HostBinding, Input, OnInit } from "@angular/core";
|
||||
|
||||
import type { SortFn } from "./table-data-source";
|
||||
import type { SortDirection, SortFn } from "./table-data-source";
|
||||
import { TableComponent } from "./table.component";
|
||||
|
||||
@Component({
|
||||
@@ -19,12 +19,16 @@ export class SortableComponent implements OnInit {
|
||||
*/
|
||||
@Input() bitSortable: string;
|
||||
|
||||
private _default: boolean;
|
||||
private _default: SortDirection | boolean = false;
|
||||
/**
|
||||
* Mark the column as the default sort column
|
||||
*/
|
||||
@Input() set default(value: boolean | "") {
|
||||
this._default = coerceBooleanProperty(value);
|
||||
@Input() set default(value: SortDirection | boolean | "") {
|
||||
if (value === "desc" || value === "asc") {
|
||||
this._default = value;
|
||||
} else {
|
||||
this._default = coerceBooleanProperty(value) ? "asc" : false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,6 +36,11 @@ export class SortableComponent implements OnInit {
|
||||
*
|
||||
* @example
|
||||
* fn = (a, b) => a.name.localeCompare(b.name)
|
||||
*
|
||||
* fn = (a, b, direction) => {
|
||||
* const result = a.name.localeCompare(b.name)
|
||||
* return direction === 'asc' ? result : -result;
|
||||
* }
|
||||
*/
|
||||
@Input() fn: SortFn;
|
||||
|
||||
@@ -52,8 +61,18 @@ export class SortableComponent implements OnInit {
|
||||
|
||||
protected setActive() {
|
||||
if (this.table.dataSource) {
|
||||
const direction = this.isActive && this.direction === "asc" ? "desc" : "asc";
|
||||
this.table.dataSource.sort = { column: this.bitSortable, direction: direction, fn: this.fn };
|
||||
const defaultDirection = this._default === "desc" ? "desc" : "asc";
|
||||
const direction = this.isActive
|
||||
? this.direction === "asc"
|
||||
? "desc"
|
||||
: "asc"
|
||||
: defaultDirection;
|
||||
|
||||
this.table.dataSource.sort = {
|
||||
column: this.bitSortable,
|
||||
direction: direction,
|
||||
fn: this.fn,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { DataSource } from "@angular/cdk/collections";
|
||||
import { BehaviorSubject, combineLatest, map, Observable, Subscription } from "rxjs";
|
||||
|
||||
export type SortDirection = "asc" | "desc";
|
||||
export type SortFn = (a: any, b: any) => number;
|
||||
export type SortFn = (a: any, b: any, direction?: SortDirection) => number;
|
||||
export type Sort = {
|
||||
column?: string;
|
||||
direction: SortDirection;
|
||||
@@ -166,7 +166,7 @@ export class TableDataSource<T> extends DataSource<T> {
|
||||
return data.sort((a, b) => {
|
||||
// If a custom sort function is provided, use it instead of the default.
|
||||
if (sort.fn) {
|
||||
return sort.fn(a, b) * directionModifier;
|
||||
return sort.fn(a, b, sort.direction) * directionModifier;
|
||||
}
|
||||
|
||||
let valueA = this.sortingDataAccessor(a, column);
|
||||
|
||||
@@ -105,7 +105,7 @@ within the `ng-template`which provides access to the rows using `let-rows$`.
|
||||
|
||||
We provide a simple component for displaying sortable column headers. The `bitSortable` component
|
||||
wires up to the `TableDataSource` and will automatically sort the data when clicked and display an
|
||||
indicator for which column is currently sorted. The dafault sorting can be specified by setting the
|
||||
indicator for which column is currently sorted. The default sorting can be specified by setting the
|
||||
`default`.
|
||||
|
||||
```html
|
||||
@@ -113,10 +113,23 @@ indicator for which column is currently sorted. The dafault sorting can be speci
|
||||
<th bitCell bitSortable="name" default>Name</th>
|
||||
```
|
||||
|
||||
For default sorting in descending order, set default="desc"
|
||||
|
||||
```html
|
||||
<th bitCell bitSortable="name" default="desc">Name</th>
|
||||
```
|
||||
|
||||
It's also possible to define a custom sorting function by setting the `fn` input.
|
||||
|
||||
```ts
|
||||
// Basic sort function
|
||||
const sortFn = (a: T, b: T) => (a.id > b.id ? 1 : -1);
|
||||
|
||||
// Direction aware sort function
|
||||
const sortByName = (a: T, b: T, direction?: SortDirection) => {
|
||||
const result = a.name.localeCompare(b.name);
|
||||
return direction === "asc" ? result : -result;
|
||||
};
|
||||
```
|
||||
|
||||
### Filtering
|
||||
|
||||
@@ -59,6 +59,7 @@ export class ToggleComponent<TValue> implements AfterContentChecked {
|
||||
"tw-leading-5",
|
||||
"tw-transition",
|
||||
"tw-text-center",
|
||||
"tw-text-sm",
|
||||
"tw-border-primary-600",
|
||||
"!tw-text-primary-600",
|
||||
"tw-border-solid",
|
||||
@@ -85,7 +86,7 @@ export class ToggleComponent<TValue> implements AfterContentChecked {
|
||||
"peer-checked/toggle-input:tw-border-primary-600",
|
||||
"peer-checked/toggle-input:!tw-text-contrast",
|
||||
"tw-py-1.5",
|
||||
"tw-px-4",
|
||||
"tw-px-3",
|
||||
|
||||
// Fix for bootstrap styles that add bottom margin
|
||||
"!tw-mb-0",
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
--color-success-600: 12 128 24;
|
||||
--color-success-700: 11 111 21;
|
||||
|
||||
--color-notification-100: 255 225 247;
|
||||
--color-notification-600: 192 17 118;
|
||||
|
||||
--color-art-primary: 2 15 102;
|
||||
--color-art-accent: 44 221 223;
|
||||
|
||||
@@ -92,6 +95,9 @@
|
||||
--color-info-600: 121 161 233;
|
||||
--color-info-700: 219 229 246;
|
||||
|
||||
--color-notification-100: 117 37 83;
|
||||
--color-notification-600: 255 143 208;
|
||||
|
||||
--color-art-primary: 243 246 249;
|
||||
--color-art-accent: 44 221 233;
|
||||
|
||||
|
||||
@@ -58,6 +58,10 @@ module.exports = {
|
||||
600: rgba("--color-info-600"),
|
||||
700: rgba("--color-info-700"),
|
||||
},
|
||||
notification: {
|
||||
100: rgba("--color-notification-100"),
|
||||
600: rgba("--color-notification-600"),
|
||||
},
|
||||
art: {
|
||||
primary: rgba("--color-art-primary"),
|
||||
accent: rgba("--color-art-accent"),
|
||||
@@ -116,6 +120,9 @@ module.exports = {
|
||||
300: rgba("--color-secondary-300"),
|
||||
700: rgba("--color-secondary-700"),
|
||||
},
|
||||
notification: {
|
||||
600: rgba("--color-notification-600"),
|
||||
},
|
||||
},
|
||||
ringOffsetColor: ({ theme }) => ({
|
||||
DEFAULT: theme("colors.background"),
|
||||
|
||||
Reference in New Issue
Block a user