1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 13:23:34 +00:00

[CL-847] Card consolidation (#16952)

* created shared card directive

* WIP

* use base card in anon layout

* use bit-card for pricing card component

* add base card to integration cards

* add base card to reports cards

* add base card to integration card

* use card content on report card

* use base card directive on base component

* update dirt card to use bit-card

* run prettier. fix whitespace

* add missing imports to report list stories

* add base card story and docs
This commit is contained in:
Bryan Cunningham
2025-10-27 11:14:42 -04:00
committed by GitHub
parent af6e19335d
commit f452f39f3c
21 changed files with 184 additions and 57 deletions

View File

@@ -48,11 +48,11 @@
<ng-container *ngTemplateOutlet="defaultContent"></ng-container>
</div>
} @else {
<div
class="tw-rounded-2xl tw-mb-6 sm:tw-mb-10 tw-mx-auto tw-w-full sm:tw-bg-background sm:tw-border sm:tw-border-solid sm:tw-border-secondary-300 sm:tw-p-8"
<bit-base-card
class="!tw-rounded-2xl tw-mb-6 sm:tw-mb-10 tw-mx-auto tw-w-full tw-bg-transparent tw-border-none tw-shadow-none sm:tw-bg-background sm:tw-border sm:tw-border-solid sm:tw-border-secondary-100 sm:tw-shadow sm:tw-p-8"
>
<ng-container *ngTemplateOutlet="defaultContent"></ng-container>
</div>
</bit-base-card>
}
<ng-content select="[slot=secondary]"></ng-content>
</div>

View File

@@ -21,6 +21,7 @@ import { ClientType } from "@bitwarden/common/enums";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { BaseCardComponent } from "../card";
import { IconModule } from "../icon";
import { SharedModule } from "../shared";
import { TypographyModule } from "../typography";
@@ -32,7 +33,14 @@ export type AnonLayoutMaxWidth = "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl";
@Component({
selector: "auth-anon-layout",
templateUrl: "./anon-layout.component.html",
imports: [IconModule, CommonModule, TypographyModule, SharedModule, RouterModule],
imports: [
IconModule,
CommonModule,
TypographyModule,
SharedModule,
RouterModule,
BaseCardComponent,
],
})
export class AnonLayoutComponent implements OnInit, OnChanges {
@HostBinding("class")

View File

@@ -0,0 +1,14 @@
import { Component } from "@angular/core";
import { BaseCardDirective } from "./base-card.directive";
/**
* The base card component is a container that applies our standard card border and box-shadow.
* In most cases using our `<bit-card>` component should suffice.
*/
@Component({
selector: "bit-base-card",
template: `<ng-content></ng-content>`,
hostDirectives: [BaseCardDirective],
})
export class BaseCardComponent {}

View File

@@ -0,0 +1,9 @@
import { Directive } from "@angular/core";
@Directive({
host: {
class:
"tw-box-border tw-block tw-bg-background tw-text-main tw-border tw-border-solid tw-border-secondary-100 tw-shadow tw-rounded-xl",
},
})
export class BaseCardDirective {}

View File

@@ -0,0 +1,23 @@
import { Meta, Primary, Controls, Canvas, Title, Description } from "@storybook/addon-docs";
import * as stories from "./base-card.stories";
<Meta of={stories} />
```ts
import { BaseCardComponent } from "@bitwarden/components";
```
<Title />
<Description />
<Canvas of={stories.Default} />
## BaseCardDirective
There is also a `BaseCardDirective` available for use as a hostDirective if need be. But, most
likely using `<bit-base-card>` in your template will do.
```ts
import { BaseCardDirective } from "@bitwarden/components";
```

View File

@@ -0,0 +1,41 @@
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { AnchorLinkDirective } from "../../link";
import { TypographyModule } from "../../typography";
import { BaseCardComponent } from "./base-card.component";
export default {
title: "Component Library/Cards/BaseCard",
component: BaseCardComponent,
decorators: [
moduleMetadata({
imports: [AnchorLinkDirective, TypographyModule],
}),
],
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-28355&t=b5tDKylm5sWm2yKo-4",
},
},
} as Meta;
type Story = StoryObj<BaseCardComponent>;
/** Cards are presentational containers. */
export const Default: Story = {
render: (args) => ({
props: args,
template: /*html*/ `
<bit-base-card>
<p bitTypography="body1" class="!tw-mb-0">
The <code>&lt;bit-base-card&gt;</code> component is a container that applies our standard border and box-shadow. In most cases, <code>&lt;bit-card&gt;</code> should be used for consistency
</p>
<p bitTypography="body1" class="!tw-mb-0">
<code>&lt;bit-base-card&gt;</code> is used in the <a bitLink href="/?path=/story/web-reports-card--enabled">ReportCardComponent</a> and <strong>IntegrationsCardComponent</strong> since they have custom padding requirements
</p>
</bit-base-card>
`,
}),
};

View File

@@ -0,0 +1,2 @@
export * from "./base-card.component";
export * from "./base-card.directive";

View File

@@ -0,0 +1,7 @@
import { Component } from "@angular/core";
@Component({
selector: "bit-card-content",
template: `<div class="tw-p-4 [@media(min-width:650px)]:tw-p-6"><ng-content></ng-content></div>`,
})
export class CardContentComponent {}

View File

@@ -1,12 +1,14 @@
import { ChangeDetectionStrategy, Component } from "@angular/core";
import { BaseCardDirective } from "./base-card/base-card.directive";
@Component({
selector: "bit-card",
template: `<ng-content></ng-content>`,
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
class:
"tw-box-border tw-block tw-bg-background tw-text-main tw-border-solid tw-border-b tw-border-0 tw-border-b-secondary-300 [&:not(bit-layout_*)]:tw-rounded-lg [&:not(bit-layout_*)]:tw-border-b-shadow tw-py-4 bit-compact:tw-py-3 tw-px-3 bit-compact:tw-px-2",
class: "tw-p-4 [@media(min-width:650px)]:tw-p-6",
},
hostDirectives: [BaseCardDirective],
})
export class CardComponent {}

View File

@@ -11,7 +11,7 @@ import { I18nMockService } from "../utils/i18n-mock.service";
import { CardComponent } from "./card.component";
export default {
title: "Component Library/Card",
title: "Component Library/Cards/Card",
component: CardComponent,
decorators: [
moduleMetadata({
@@ -84,16 +84,3 @@ export const WithinSections: Story = {
`,
}),
};
export const WithoutBorderRadius: Story = {
render: (args) => ({
props: args,
template: /*html*/ `
<bit-layout>
<bit-card>
<p bitTypography="body1" class="!tw-mb-0">Cards used in <code class="tw-text-danger-700">bit-layout</code> will not have a border radius</p>
</bit-card>
</bit-layout>
`,
}),
};

View File

@@ -1 +1,3 @@
export * from "./base-card";
export * from "./card.component";
export * from "./card-content.component";

View File

@@ -1,7 +1,9 @@
<div class="tw-flex-col">
<span bitTypography="body2" class="tw-flex tw-text-muted">{{ title }}</span>
<div class="tw-flex tw-items-baseline tw-gap-2">
<span bitTypography="h1">{{ value }}</span>
<span bitTypography="body2">{{ "cardMetrics" | i18n: maxValue }}</span>
<bit-card>
<div class="tw-flex tw-flex-col tw-gap-1.5">
<span bitTypography="body2" class="tw-flex tw-text-muted">{{ title }}</span>
<div class="tw-flex tw-items-baseline tw-gap-2">
<span bitTypography="h1" class="!tw-mb-0">{{ value }}</span>
<span bitTypography="body2">{{ "cardMetrics" | i18n: maxValue }}</span>
</div>
</div>
</div>
</bit-card>

View File

@@ -4,18 +4,14 @@ import { CommonModule } from "@angular/common";
import { Component, Input } from "@angular/core";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { TypographyModule } from "@bitwarden/components";
import { TypographyModule, CardComponent as BitCardComponent } from "@bitwarden/components";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "dirt-card",
templateUrl: "./card.component.html",
imports: [CommonModule, TypographyModule, JslibModule],
host: {
class:
"tw-box-border tw-bg-background tw-block tw-text-main tw-border-solid tw-border tw-border-secondary-300 tw-border [&:not(bit-layout_*)]:tw-rounded-lg tw-rounded-lg tw-p-6",
},
imports: [CommonModule, TypographyModule, JslibModule, BitCardComponent],
})
export class CardComponent {
/**

View File

@@ -1,6 +1,4 @@
<div
class="tw-box-border tw-bg-background tw-text-main tw-border tw-border-secondary-100 tw-rounded-3xl tw-p-8 tw-shadow-sm tw-size-full tw-flex tw-flex-col"
>
<bit-card class="tw-size-full tw-flex tw-flex-col">
<!-- Title Section with Active Badge -->
<div class="tw-flex tw-items-center tw-justify-between tw-mb-2">
<ng-content select="[slot=title]"></ng-content>
@@ -82,4 +80,4 @@
}
}
</div>
</div>
</bit-card>

View File

@@ -6,6 +6,7 @@ import {
BadgeVariant,
ButtonModule,
ButtonType,
CardComponent,
IconModule,
TypographyModule,
} from "@bitwarden/components";
@@ -20,7 +21,7 @@ import {
@Component({
selector: "billing-pricing-card",
templateUrl: "./pricing-card.component.html",
imports: [BadgeModule, ButtonModule, IconModule, TypographyModule, CurrencyPipe],
imports: [BadgeModule, ButtonModule, IconModule, TypographyModule, CurrencyPipe, CardComponent],
})
export class PricingCardComponent {
readonly tagline = input.required<string>();