1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-25 12:15:18 +01:00

[CL-294] Make section-header work across clients (#9386)

This commit is contained in:
Victoria League 2024-06-05 15:07:00 -04:00 committed by GitHub
parent bf51469404
commit d3f1992ad5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 281 additions and 150 deletions

View File

@ -1,11 +0,0 @@
<div class="tw-flex tw-justify-between tw-items-end tw-gap-1 tw-px-1 tw-pb-1">
<div class="tw-flex tw-items-center tw-gap-1">
<h2 bitTypography="h6" noMargin class="tw-mb-0 tw-text-headers">
{{ title }}
</h2>
<ng-content select="[slot=title-suffix]"></ng-content>
</div>
<div class="tw-text-muted has-[button]:-tw-mb-1">
<ng-content select="[slot=end]"></ng-content>
</div>
</div>

View File

@ -1,13 +0,0 @@
import { Component, Input } from "@angular/core";
import { TypographyModule } from "@bitwarden/components";
@Component({
standalone: true,
selector: "popup-section-header",
templateUrl: "./popup-section-header.component.html",
imports: [TypographyModule],
})
export class PopupSectionHeaderComponent {
@Input() title: string;
}

View File

@ -1,104 +0,0 @@
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
import {
CardComponent,
IconButtonModule,
SectionComponent,
TypographyModule,
} from "@bitwarden/components";
import { PopupSectionHeaderComponent } from "./popup-section-header.component";
export default {
title: "Browser/Popup Section Header",
component: PopupSectionHeaderComponent,
args: {
title: "Title",
},
decorators: [
moduleMetadata({
imports: [SectionComponent, CardComponent, TypographyModule, IconButtonModule],
}),
],
} as Meta<PopupSectionHeaderComponent>;
type Story = StoryObj<PopupSectionHeaderComponent>;
export const OnlyTitle: Story = {
render: (args) => ({
props: args,
template: `
<popup-section-header [title]="title"></popup-section-header>
`,
}),
args: {
title: "Only Title",
},
};
export const TrailingText: Story = {
render: (args) => ({
props: args,
template: `
<popup-section-header [title]="title">
<span bitTypography="body2" slot="end">13</span>
</popup-section-header>
`,
}),
args: {
title: "Trailing Text",
},
};
export const TailingIcon: Story = {
render: (args) => ({
props: args,
template: `
<popup-section-header [title]="title">
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</popup-section-header>
`,
}),
args: {
title: "Trailing Icon",
},
};
export const TitleSuffix: Story = {
render: (args) => ({
props: args,
template: `
<popup-section-header [title]="title">
<button bitIconButton="bwi-refresh" size="small" slot="title-suffix"></button>
</popup-section-header>
`,
}),
args: {
title: "Title Suffix",
},
};
export const WithSections: Story = {
render: () => ({
template: `
<div class="tw-bg-background-alt tw-p-2">
<bit-section>
<popup-section-header title="Section 1">
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</popup-section-header>
<bit-card>
<h3 bitTypography="h3">Card 1 Content</h3>
</bit-card>
</bit-section>
<bit-section>
<popup-section-header title="Section 2">
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</popup-section-header>
<bit-card>
<h3 bitTypography="h3">Card 2 Content</h3>
</bit-card>
</bit-section>
</div>
`,
}),
};

View File

@ -46,7 +46,6 @@ import { PopupFooterComponent } from "../platform/popup/layout/popup-footer.comp
import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../platform/popup/layout/popup-page.component";
import { PopupTabNavigationComponent } from "../platform/popup/layout/popup-tab-navigation.component";
import { PopupSectionHeaderComponent } from "../platform/popup/popup-section-header/popup-section-header.component";
import { FilePopoutCalloutComponent } from "../tools/popup/components/file-popout-callout.component";
import { GeneratorComponent } from "../tools/popup/generator/generator.component";
import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/password-generator-history.component";
@ -119,7 +118,6 @@ import "../platform/popup/locales";
PopupFooterComponent,
PopupHeaderComponent,
UserVerificationDialogComponent,
PopupSectionHeaderComponent,
CurrentAccountComponent,
],
declarations: [

View File

@ -8,17 +8,19 @@
></app-vault-list-items-container>
<ng-container *ngIf="showEmptyAutofillTip$ | async">
<bit-section>
<popup-section-header [title]="'autofillSuggestions' | i18n">
<bit-section-header>
<h2 bitTypography="h6">
{{ "autofillSuggestions" | i18n }}
</h2>
<button
*ngIf="showRefresh"
bitIconButton="bwi-refresh"
size="small"
slot="title-suffix"
type="button"
[appA11yTitle]="'refresh' | i18n"
(click)="refreshCurrentTab()"
></button>
</popup-section-header>
</bit-section-header>
<span class="tw-text-muted tw-px-1" bitTypography="body2">{{
"autofillSuggestionsTip" | i18n
}}</span>

View File

@ -3,10 +3,14 @@ import { Component } from "@angular/core";
import { combineLatest, map, Observable } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { IconButtonModule, SectionComponent, TypographyModule } from "@bitwarden/components";
import {
IconButtonModule,
SectionComponent,
SectionHeaderComponent,
TypographyModule,
} from "@bitwarden/components";
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
import { PopupSectionHeaderComponent } from "../../../../../platform/popup/popup-section-header/popup-section-header.component";
import { VaultPopupItemsService } from "../../../services/vault-popup-items.service";
import { PopupCipherView } from "../../../views/popup-cipher.view";
import { VaultListItemsContainerComponent } from "../vault-list-items-container/vault-list-items-container.component";
@ -19,7 +23,7 @@ import { VaultListItemsContainerComponent } from "../vault-list-items-container/
TypographyModule,
VaultListItemsContainerComponent,
JslibModule,
PopupSectionHeaderComponent,
SectionHeaderComponent,
IconButtonModule,
],
selector: "app-autofill-vault-list-items",

View File

@ -1,16 +1,18 @@
<bit-section *ngIf="ciphers?.length > 0">
<popup-section-header [title]="title">
<span bitTypography="body2" slot="end">{{ ciphers.length }}</span>
<bit-section-header>
<h2 bitTypography="h6">
{{ title }}
</h2>
<button
*ngIf="showRefresh"
bitIconButton="bwi-refresh"
type="button"
size="small"
slot="title-suffix"
(click)="onRefresh.emit()"
[appA11yTitle]="'refresh' | i18n"
></button>
</popup-section-header>
<span bitTypography="body2" slot="end">{{ ciphers.length }}</span>
</bit-section-header>
<bit-item-group>
<bit-item *ngFor="let cipher of ciphers">
<a

View File

@ -10,10 +10,10 @@ import {
IconButtonModule,
ItemModule,
SectionComponent,
SectionHeaderComponent,
TypographyModule,
} from "@bitwarden/components";
import { PopupSectionHeaderComponent } from "../../../../../platform/popup/popup-section-header/popup-section-header.component";
import { PopupCipherView } from "../../../views/popup-cipher.view";
import { ItemCopyActionsComponent } from "../item-copy-action/item-copy-actions.component";
import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options.component";
@ -28,7 +28,7 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options
SectionComponent,
TypographyModule,
JslibModule,
PopupSectionHeaderComponent,
SectionHeaderComponent,
RouterLink,
ItemCopyActionsComponent,
ItemMoreOptionsComponent,

View File

@ -1 +1,2 @@
export * from "./section.component";
export * from "./section-header.component";

View File

@ -0,0 +1,8 @@
<div class="tw-flex tw-justify-between tw-items-end tw-gap-1">
<div class="[&>*]:tw-mb-0 [&>*]:tw-text-headers tw-flex tw-items-center tw-gap-1">
<ng-content></ng-content>
</div>
<div class="tw-text-muted has-[button]:-tw-mb-1">
<ng-content select="[slot=end]"></ng-content>
</div>
</div>

View File

@ -0,0 +1,16 @@
import { Component } from "@angular/core";
import { TypographyModule } from "../typography";
@Component({
standalone: true,
selector: "bit-section-header",
templateUrl: "./section-header.component.html",
imports: [TypographyModule],
host: {
class:
// apply bottom and x padding when a `bit-card` or `bit-item` is the immediate sibling, or nested in the immediate sibling
"tw-block has-[+_*_bit-card]:tw-pb-1 has-[+_bit-card]:tw-pb-1 has-[+_*_bit-item]:tw-pb-1 has-[+_bit-item]:tw-pb-1 has-[+_*_bit-card]:tw-px-1 has-[+_bit-card]:tw-px-1 has-[+_*_bit-item]:tw-px-1 has-[+_bit-item]:tw-px-1",
},
})
export class SectionHeaderComponent {}

View File

@ -0,0 +1,89 @@
import { Meta, Story, Primary, Controls, Canvas } from "@storybook/addon-docs";
import * as stories from "./section.stories";
<Meta of={stories} />
```ts
import { SectionComponent, SectionHeaderComponent } from "@bitwarden/components";
```
# Section
Sections are simple containers that apply a responsive bottom margin and utilize the semantic
`section` HTML element.
<Canvas>
<Story of={stories.Default} />
</Canvas>
## Section Header
Sections often contain a heading. Use `bit-section-header` inside of the `bit-section`.
```html
<bit-section>
<bit-section-header>
<h1 bitTypography="h1">I'm a section header</h2>
</bit-section-header>
<div>Section content here!</div>
</bit-section>
```
### Section Header Padding
When placed inside of a section with a `bit-card` or `bit-item` as the immediate next sibling (or
nested in the immediate next sibling), the section header will automatically apply bottom and x-axis
padding to align the header with the border radius of the card/item.
```html
<bit-section>
<bit-section-header>
<h2 bitTypography="h6">I'm a section header</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
<bit-card>
<h3 bitTypography="h3">I'm card content</h3>
</bit-card>
</bit-section>
```
<Canvas>
<Story of={stories.HeaderWithPadding} />
</Canvas>
If placed inside of a section without a `bit-card` or `bit-item`, or with a `bit-card`/`bit-item`
that is not a descendant of the immediate next sibling, the padding is not applied.
<Canvas>
<Story of={stories.HeaderWithoutPadding} />
</Canvas>
### Section Header Content Slots
`bit-section-header` contains the following slots to help position the content:
| Slot | Description |
| ------------ | ------------------------------- |
| default | title text of the header |
| `slot="end"` | placed at the end of the header |
#### Default slot
Anything passed to the default slot will display as part of the title. The title should be a
`bitTypography` element, usually an `h2` styled as an `h6`.
Title suffixes (typically an icon or icon button) can be added as well. A gap is automatically
applied between the children of the default slot.
<Canvas>
<Story of={stories.HeaderVariants} />
</Canvas>
#### End slot
The `end` slot will typically be used for text or an icon button.
<Canvas>
<Story of={stories.HeaderEndSlotVariants} />
</Canvas>

View File

@ -1,15 +1,24 @@
import { Meta, StoryObj, componentWrapperDecorator, moduleMetadata } from "@storybook/angular";
import { CardComponent } from "../card";
import { IconButtonModule } from "../icon-button";
import { ItemModule } from "../item";
import { TypographyModule } from "../typography";
import { SectionComponent } from "./section.component";
import { SectionComponent, SectionHeaderComponent } from "./";
export default {
title: "Component Library/Section",
component: SectionComponent,
decorators: [
moduleMetadata({
imports: [TypographyModule],
imports: [
TypographyModule,
SectionHeaderComponent,
CardComponent,
IconButtonModule,
ItemModule,
],
}),
componentWrapperDecorator((story) => `<div class="tw-text-main">${story}</div>`),
],
@ -17,19 +26,149 @@ export default {
type Story = StoryObj<SectionComponent>;
/** Sections are simple containers that apply a responsive bottom margin. They often contain a heading. */
export const Default: Story = {
render: (args) => ({
props: args,
template: `
render: () => ({
template: /*html*/ `
<bit-section>
<bit-section-header>
<h2 bitTypography="h2">Foo</h2>
<p bitTypography="body1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex. </p>
</bit-section-header>
<p bitTypography="body1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex. </p>
</bit-section>
<bit-section>
<bit-section-header>
<h2 bitTypography="h2">Bar</h2>
<p bitTypography="body1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex. </p>
</bit-section-header>
<p bitTypography="body1">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex. </p>
</bit-section>
`,
}),
};
export const HeaderVariants: Story = {
render: () => ({
template: /*html*/ `
<bit-section-header>
<h2 bitTypography="h6">
Title only
</h2>
</bit-section-header>
<bit-section-header>
<h2 bitTypography="h6">
Title with icon button suffix
</h2>
<button bitIconButton="bwi-refresh" size="small"></button>
</bit-section-header>
`,
}),
};
export const HeaderEndSlotVariants: Story = {
render: () => ({
template: /*html*/ `
<bit-section-header>
<h2 bitTypography="h6">
Title with end slot text
</h2>
<span bitTypography="body2" slot="end">13</span>
</bit-section-header>
<bit-section-header>
<h2 bitTypography="h6">
Title with end slot icon button
</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
`,
}),
};
export const HeaderWithPadding: Story = {
render: () => ({
template: /*html*/ `
<div class="tw-bg-background-alt tw-p-2">
<bit-section>
<bit-section-header>
<h2 bitTypography="h6">
Card as immediate sibling
</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
<bit-card>
<h3 bitTypography="h3">bit-section-header has padding</h3>
</bit-card>
</bit-section>
<bit-section>
<bit-section-header>
<h2 bitTypography="h6">
Card nested in immediate sibling
</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
<div>
<bit-card>
<h3 bitTypography="h3">bit-section-header has padding</h3>
</bit-card>
</div>
</bit-section>
<bit-section>
<bit-section-header>
<h2 bitTypography="h6">
Item as immediate sibling
</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
<bit-item>
<bit-item-content bitTypography="body1">bit-section-header has padding</bit-item-content>
</bit-item>
</bit-section>
<bit-section>
<bit-section-header>
<h2 bitTypography="h6">
Item nested in immediate sibling
</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
<bit-item-group>
<bit-item>
<bit-item-content bitTypography="body1">bit-section-header has padding</bit-item-content>
</bit-item>
</bit-item-group>
</bit-section>
</div>
`,
}),
};
export const HeaderWithoutPadding: Story = {
render: () => ({
template: /*html*/ `
<div class="tw-bg-background-alt tw-p-2">
<bit-section>
<bit-section-header>
<h2 bitTypography="h6">
No card or item used
</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
<div>
<h3 bitTypography="h3">just a div, so bit-section-header has no padding</h3>
</div>
</bit-section>
<bit-section>
<bit-section-header>
<h2 bitTypography="h6">
Card nested in non-immediate sibling
</h2>
<button bitIconButton="bwi-star" size="small" slot="end"></button>
</bit-section-header>
<div class="tw-text-main">
a div here
</div>
<bit-card>
<h3 bitTypography="h3">bit-section-header has no padding</h3>
</bit-card>
</bit-section>
</div>
`,
}),
};