mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-02 18:17:46 +01:00
[CL-500] Add disclosure component and directive (#11865)
This commit is contained in:
parent
f206e0f817
commit
e8dac0cc12
@ -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-bg-transparent",
|
||||||
"!tw-text-muted",
|
"!tw-text-muted",
|
||||||
"tw-border-transparent",
|
"tw-border-transparent",
|
||||||
|
"aria-expanded:tw-bg-text-muted",
|
||||||
|
"aria-expanded:!tw-text-contrast",
|
||||||
"hover:tw-bg-transparent-hover",
|
"hover:tw-bg-transparent-hover",
|
||||||
"hover:tw-border-primary-700",
|
"hover:tw-border-primary-700",
|
||||||
"focus-visible:before:tw-ring-primary-700",
|
"focus-visible:before:tw-ring-primary-700",
|
||||||
"disabled:tw-opacity-60",
|
"disabled:tw-opacity-60",
|
||||||
|
"aria-expanded:hover:tw-bg-secondary-700",
|
||||||
|
"aria-expanded:hover:tw-border-secondary-700",
|
||||||
"disabled:hover:tw-border-transparent",
|
"disabled:hover:tw-border-transparent",
|
||||||
"disabled:hover:tw-bg-transparent",
|
"disabled:hover:tw-bg-transparent",
|
||||||
...focusRing,
|
...focusRing,
|
||||||
|
@ -29,8 +29,6 @@ Icon buttons can be found in other components such as: the
|
|||||||
[dialog](?path=/docs/component-library-dialogs--docs), and
|
[dialog](?path=/docs/component-library-dialogs--docs), and
|
||||||
[table](?path=/docs/component-library-table--docs).
|
[table](?path=/docs/component-library-table--docs).
|
||||||
|
|
||||||
<Story id="component-library-banner--premium" />
|
|
||||||
|
|
||||||
## Styles
|
## Styles
|
||||||
|
|
||||||
There are 4 common styles for button main, muted, contrast, and danger. The other styles follow the
|
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`
|
Used for general icon buttons appearing on the theme’s main `background`
|
||||||
|
|
||||||
<Story id="component-library-icon-button--main" />
|
<Story of={stories.Main} />
|
||||||
|
|
||||||
### Muted
|
### Muted
|
||||||
|
|
||||||
Used for low emphasis icon buttons appearing on the theme’s main `background`
|
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
|
### Contrast
|
||||||
|
|
||||||
Used on a theme’s colored or contrasting backgrounds such as in the navigation or on toasts and
|
Used on a theme’s colored or contrasting backgrounds such as in the navigation or on toasts and
|
||||||
banners.
|
banners.
|
||||||
|
|
||||||
<Story id="component-library-icon-button--contrast" />
|
<Story of={stories.Contrast} />
|
||||||
|
|
||||||
### Danger
|
### Danger
|
||||||
|
|
||||||
Danger is used for “trash” actions throughout the experience, most commonly in the bottom right of
|
Danger is used for “trash” actions throughout the experience, most commonly in the bottom right of
|
||||||
the dialog component.
|
the dialog component.
|
||||||
|
|
||||||
<Story id="component-library-icon-button--danger" />
|
<Story of={stories.Danger} />
|
||||||
|
|
||||||
### Primary
|
### Primary
|
||||||
|
|
||||||
Used in place of the main button component if no text is used. This allows the button to display
|
Used in place of the main button component if no text is used. This allows the button to display
|
||||||
square.
|
square.
|
||||||
|
|
||||||
<Story id="component-library-icon-button--primary" />
|
<Story of={stories.Primary} />
|
||||||
|
|
||||||
### Secondary
|
### Secondary
|
||||||
|
|
||||||
Used in place of the main button component if no text is used. This allows the button to display
|
Used in place of the main button component if no text is used. This allows the button to display
|
||||||
square.
|
square.
|
||||||
|
|
||||||
<Story id="component-library-icon-button--secondary" />
|
<Story of={stories.Secondary} />
|
||||||
|
|
||||||
### Light
|
### Light
|
||||||
|
|
||||||
Used on a background that is dark in both light theme and dark theme. Example: end user navigation
|
Used on a background that is dark in both light theme and dark theme. Example: end user navigation
|
||||||
styles.
|
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
|
**Note:** Main and contrast styles appear on backgrounds where using `primary-700` as a focus
|
||||||
indicator does not meet WCAG graphic contrast guidelines.
|
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
|
### Small
|
||||||
|
|
||||||
<Story id="component-library-icon-button--small" />
|
<Story of={stories.Small} />
|
||||||
|
|
||||||
### Default
|
### Default
|
||||||
|
|
||||||
<Story id="component-library-icon-button--default" />
|
<Story of={stories.Default} />
|
||||||
|
|
||||||
## Accessibility
|
## Accessibility
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ type Story = StoryObj<BitIconButtonComponent>;
|
|||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
template: `
|
template: /*html*/ `
|
||||||
<div class="tw-space-x-4">
|
<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="main" [size]="size">Button</button>
|
||||||
<button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="muted" [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 = {
|
export const Primary: Story = {
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
template: `
|
template: /*html*/ `
|
||||||
<button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size">Button</button>
|
<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 = {
|
export const Light: Story = {
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
template: `
|
template: /*html*/ `
|
||||||
<div class="tw-bg-background-alt2 tw-p-6 tw-w-full tw-inline-block">
|
<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>
|
<button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size">Button</button>
|
||||||
</div>
|
</div>
|
||||||
@ -110,7 +110,7 @@ export const Light: Story = {
|
|||||||
export const Contrast: Story = {
|
export const Contrast: Story = {
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
template: `
|
template: /*html*/ `
|
||||||
<div class="tw-bg-primary-600 tw-p-6 tw-w-full tw-inline-block">
|
<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>
|
<button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size">Button</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,6 +13,7 @@ export * from "./chip-select";
|
|||||||
export * from "./color-password";
|
export * from "./color-password";
|
||||||
export * from "./container";
|
export * from "./container";
|
||||||
export * from "./dialog";
|
export * from "./dialog";
|
||||||
|
export * from "./disclosure";
|
||||||
export * from "./form-field";
|
export * from "./form-field";
|
||||||
export * from "./icon-button";
|
export * from "./icon-button";
|
||||||
export * from "./icon";
|
export * from "./icon";
|
||||||
|
Loading…
Reference in New Issue
Block a user