]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: Add productive card component
authorAfreen Misbah <afreen@ibm.com>
Tue, 13 Jan 2026 20:47:40 +0000 (02:17 +0530)
committerAfreen Misbah <afreen@ibm.com>
Mon, 2 Feb 2026 12:42:25 +0000 (18:12 +0530)
- add generic productive card component
- based on carbon design system
- there are two versions of card - with shadow(tinted affect) and without.
- applies gray10 theme which is decided by new designs.

Fixes https://tracker.ceph.com/issues/74450

Signed-off-by: Afreen Misbah <afreen@ibm.com>
(cherry picked from commit aa2216d8d5e728b7ed7637e190a93717e86c8dc6)

 Conflicts:
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.scss
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts

src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/productive-card/productive-card.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/productive-card/productive-card.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/productive-card/productive-card.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/productive-card/productive-card.component.ts [new file with mode: 0644]

index 8b20898dbe8ea0d8915cbfc12cd06aa13d8a5625..9aff468cdc0fcce22b9e3fedf1d6b48d711725cb 100644 (file)
@@ -39,7 +39,9 @@ import {
   InlineLoadingModule,
   PanelModule,
   TagModule,
-  LinkModule
+  LinkModule,
+  LayerModule,
+  TilesModule
 } from 'carbon-components-angular';
 
 import { MotdComponent } from '~/app/shared/components/motd/motd.component';
@@ -92,6 +94,7 @@ import CopyIcon from '@carbon/icons/es/copy/32';
 import { IconComponent } from './icon/icon.component';
 import downloadIcon from '@carbon/icons/es/download/16';
 import { ChartsModule } from '@carbon/charts-angular';
+import { ProductiveCardComponent } from './productive-card/productive-card.component';
 
 @NgModule({
   imports: [
@@ -135,7 +138,9 @@ import { ChartsModule } from '@carbon/charts-angular';
     PanelModule,
     ChartsModule,
     TagModule,
-    LinkModule
+    LinkModule,
+    LayerModule,
+    TilesModule
   ],
   declarations: [
     SparklineComponent,
@@ -180,7 +185,8 @@ import { ChartsModule } from '@carbon/charts-angular';
     ProgressComponent,
     IconComponent,
     TearsheetComponent,
-    TearsheetStepComponent
+    TearsheetStepComponent,
+    ProductiveCardComponent
   ],
   providers: [provideCharts(withDefaultRegisterables())],
   exports: [
@@ -222,7 +228,8 @@ import { ChartsModule } from '@carbon/charts-angular';
     ProgressComponent,
     IconComponent,
     TearsheetComponent,
-    TearsheetStepComponent
+    TearsheetStepComponent,
+    ProductiveCardComponent
   ]
 })
 export class ComponentsModule {
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/productive-card/productive-card.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/productive-card/productive-card.component.html
new file mode 100644 (file)
index 0000000..67a4cbf
--- /dev/null
@@ -0,0 +1,31 @@
+<cds-tile class="productive-card"
+          [ngClass]="{'productive-card--shadow': applyShadow}"
+          [cdsLayer]="0">
+  <header
+    cdsGrid
+    [useCssGrid]="true"
+    [narrow]="true"
+    class="productive-card-header">
+    <h2 cdsCol
+        [columnNumbers]="{md: headerActionTemplate ? 12 : 16, lg: headerActionTemplate ? 12 : 16}"
+        class="cds--type-heading-compact-02 productive-card-header-title">
+      {{title}}
+    </h2>
+  @if(headerActionTemplate) {
+  <div cdsCol
+       [columnNumbers]="{md: 4, lg: 4}"
+       class="productive-card-header-actions">
+    <ng-container *ngTemplateOutlet="headerActionTemplate"></ng-container>
+  </div>
+  }
+  </header>
+  <section class="productive-card-section cds--type-body-compact-01"
+           [ngClass]="{'productive-card-section--footer': footerTemplate}">
+    <ng-content></ng-content>
+  </section>
+  @if(footerTemplate) {
+  <footer class="productive-card-footer">
+    <ng-container *ngTemplateOutlet="footerTemplate"></ng-container>
+  </footer>
+  }
+</cds-tile>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/productive-card/productive-card.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/productive-card/productive-card.component.scss
new file mode 100644 (file)
index 0000000..cdab967
--- /dev/null
@@ -0,0 +1,48 @@
+@use '@carbon/colors';
+
+.productive-card {
+  margin: 0;
+  padding: 0;
+
+  &-header {
+    margin: 0;
+    padding: var(--cds-spacing-05);
+
+    &-title {
+      margin: 0;
+    }
+
+    &-actions {
+      margin: 0;
+    }
+  }
+
+  &-section {
+    margin: 0;
+    padding: var(--cds-spacing-05);
+    padding-top: 0;
+
+    &--footer {
+      padding-bottom: 0;
+    }
+  }
+
+  &-footer {
+    height: var(--cds-spacing-08);
+    padding: var(--cds-spacing-05);
+  }
+
+  &--shadow {
+    background-color: var(--cds-layer);
+    background-image:
+      // cyan left edge
+      radial-gradient(120% 60% at -15% 100%, rgba(colors.$cyan-60, 0.13) 0%, transparent 65%),
+      // cyan right edge
+      radial-gradient(120% 60% at 115% 100%, rgba(colors.$cyan-60, 0.1) 0%, transparent 65%),
+      // pink center
+      radial-gradient(120% 60% at 50% 100%, rgba(colors.$magenta-60, 0.11) 0%, transparent 70%);
+    box-shadow: var(--cds-ai-drop-shadow), inset 0 0 0 1px var(--cds-ai-inner-shadow);
+    overflow: hidden; // ensures proper edge fade
+    position: relative;
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/productive-card/productive-card.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/productive-card/productive-card.component.spec.ts
new file mode 100644 (file)
index 0000000..0fe568b
--- /dev/null
@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ProductiveCardComponent } from './productive-card.component';
+import { GridModule, LayerModule, TilesModule } from 'carbon-components-angular';
+
+describe('ProductiveCardComponent', () => {
+  let component: ProductiveCardComponent;
+  let fixture: ComponentFixture<ProductiveCardComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ProductiveCardComponent],
+      imports: [GridModule, LayerModule, TilesModule]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(ProductiveCardComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/productive-card/productive-card.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/productive-card/productive-card.component.ts
new file mode 100644 (file)
index 0000000..7047a1a
--- /dev/null
@@ -0,0 +1,38 @@
+import { Component, ContentChild, Input, TemplateRef } from '@angular/core';
+
+/**
+ * A generic productive card component.
+ *
+ * @example
+ * <cd-productive-card title="Card Title"
+ *                     [applyShadow]="true">
+ *   <ng-template #headerAction>...</ng-template>
+ *   <ng-template #footer>...</ng-template>
+ *   <p>My card body content</p>
+ * </cd-productive-card>
+ */
+@Component({
+  selector: 'cd-productive-card',
+  standalone: false,
+  templateUrl: './productive-card.component.html',
+  styleUrl: './productive-card.component.scss'
+})
+export class ProductiveCardComponent {
+  /* Card Title */
+  @Input() title!: string;
+
+  /* Optional: Applies a tinted-colored background to card */
+  @Input() applyShadow: boolean = false;
+
+  /* Optional: Header action template, appears alongwith title in top-right corner */
+  @ContentChild('headerAction', {
+    read: TemplateRef
+  })
+  headerActionTemplate?: TemplateRef<any>;
+
+  /* Optional: Footer template , otherwise no footer will be used for card.*/
+  @ContentChild('footer', {
+    read: TemplateRef
+  })
+  footerTemplate?: TemplateRef<any>;
+}