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:
parent
bf51469404
commit
d3f1992ad5
@ -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>
|
@ -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;
|
||||
}
|
@ -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>
|
||||
`,
|
||||
}),
|
||||
};
|
@ -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: [
|
||||
|
@ -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>
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -1 +1,2 @@
|
||||
export * from "./section.component";
|
||||
export * from "./section-header.component";
|
||||
|
@ -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>
|
16
libs/components/src/section/section-header.component.ts
Normal file
16
libs/components/src/section/section-header.component.ts
Normal 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 {}
|
89
libs/components/src/section/section.mdx
Normal file
89
libs/components/src/section/section.mdx
Normal 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>
|
@ -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>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user