From 5d793073ef21423c95d34b5b0dd91d7290e87cc1 Mon Sep 17 00:00:00 2001 From: edlu77 Date: Fri, 31 Oct 2025 16:43:14 -0400 Subject: [PATCH 01/13] Add more features to tune menu --- libs/design-system/tune-menu/ng-package.json | 5 + libs/design-system/tune-menu/src/index.ts | 1 + .../src/lib/tune-menu.component.html | 91 ++++++++++++ .../src/lib/tune-menu.component.scss | 121 ++++++++++++++++ .../src/lib/tune-menu.component.spec.ts | 21 +++ .../src/lib/tune-menu.component.stories.ts | 25 ++++ .../tune-menu/src/lib/tune-menu.component.ts | 134 ++++++++++++++++++ 7 files changed, 398 insertions(+) create mode 100644 libs/design-system/tune-menu/ng-package.json create mode 100644 libs/design-system/tune-menu/src/index.ts create mode 100644 libs/design-system/tune-menu/src/lib/tune-menu.component.html create mode 100644 libs/design-system/tune-menu/src/lib/tune-menu.component.scss create mode 100644 libs/design-system/tune-menu/src/lib/tune-menu.component.spec.ts create mode 100644 libs/design-system/tune-menu/src/lib/tune-menu.component.stories.ts create mode 100644 libs/design-system/tune-menu/src/lib/tune-menu.component.ts diff --git a/libs/design-system/tune-menu/ng-package.json b/libs/design-system/tune-menu/ng-package.json new file mode 100644 index 0000000000..c781f0df46 --- /dev/null +++ b/libs/design-system/tune-menu/ng-package.json @@ -0,0 +1,5 @@ +{ + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/libs/design-system/tune-menu/src/index.ts b/libs/design-system/tune-menu/src/index.ts new file mode 100644 index 0000000000..9da6681e2a --- /dev/null +++ b/libs/design-system/tune-menu/src/index.ts @@ -0,0 +1 @@ +export * from './lib/tune-menu.component'; diff --git a/libs/design-system/tune-menu/src/lib/tune-menu.component.html b/libs/design-system/tune-menu/src/lib/tune-menu.component.html new file mode 100644 index 0000000000..580f7e6211 --- /dev/null +++ b/libs/design-system/tune-menu/src/lib/tune-menu.component.html @@ -0,0 +1,91 @@ +@if (!menuOptions().hideHeaderSection) { +
+
+ {{ menuOptions().headline }} +
+ @if (!isWideScreen() || !menuOptions().hideCloseButton) { + + } +
+
{{ menuOptions().subtext }}
+
+} + +
+ @if (!menuOptions().hideCustomizeSection) { +
+
+ + Customize +
+ @let categories = menuOptions().toggleCategories; + @if (categories) { + + @for (category of categories; track category.id) { + + {{ category.label }} + + } + + } + @if (menuOptions().viewAsOptions) { + + table_view + View as + + @for (option of menuOptions().viewAsOptions; track option) { + {{ option.label }} + } + + + } + + + sort + Sort by + + @for (option of menuOptions().sortByOptions; track option) { + {{ option.label }} + } + + + + + category + Group by + + @for (option of menuOptions().groupByOptions; track option) { + {{ option.label }} + } + + +
+ } +
+
+ + Filters +
+ @for (value of formValues(); track $index) { + @if ($index > 0) { + + } +
{{ value[0] }}
+ } +
+ +
+
diff --git a/libs/design-system/tune-menu/src/lib/tune-menu.component.scss b/libs/design-system/tune-menu/src/lib/tune-menu.component.scss new file mode 100644 index 0000000000..a6eec7316e --- /dev/null +++ b/libs/design-system/tune-menu/src/lib/tune-menu.component.scss @@ -0,0 +1,121 @@ +@use '@angular/material' as mat; +@use '../../../styles/utils'; +@use '../../../styles/vars'; + +:host { + display: flex; + flex-direction: column; + color: vars.$secondary; + padding: 1.5rem; + height: 100%; + + .header-section { + display: flex; + flex-direction: column; + gap: 0.25rem; + padding-bottom: 1.5rem; + + .filler { + flex-grow: 1; + } + + .header-subtext { + @include utils.use-font(body, medium); + color: vars.$primary; + } + } + + .drawer-title, + .customize-title, + .filters-title { + @include utils.use-font(title, small); + padding: 0.5rem 0; + } + + .customize-header, + .drawer-header, + .filters-header { + display: flex; + align-items: center; + } + + .filters-header { + margin-bottom: 1rem; + } + + .filter-icon, + .customize-icon { + padding: 0.5rem; + } + + .menu-icon, + .menu-content { + transition: 0.3s; + } + + .menu-content { + display: flex; + flex-direction: column; + gap: 0.25rem; + opacity: 1; + } + + ng-scrollbar { + padding-top: 1rem; + } + + mat-button-toggle-group { + width: fit-content; + } + + .customize-section { + padding-bottom: 2.5rem; + display: flex; + flex-direction: column; + gap: 1rem; + } + + .select-icon { + padding-left: 0.75rem; + padding-right: 0.5rem; + } + + &.closed { + .menu-icon { + transform: rotate(180deg); + } + + .menu-content { + pointer-events: none; + opacity: 0; + } + + ng-scrollbar { + overflow-y: hidden; + } + + ::ng-deep scrollbars { + display: none; + } + + ::ng-deep .ng-scroll-content { + --_viewport-padding-right: 0.75rem; + } + } + + mat-label { + @include utils.use-font(label, medium); + } + + mat-divider { + border-top-color: vars.$outline-variant; + } + + .placeholder { + height: 2.5rem; + @include utils.use-font(label, medium); + padding: 0.25rem 0.75rem; + align-content: center; + pointer-events: none; + } +} diff --git a/libs/design-system/tune-menu/src/lib/tune-menu.component.spec.ts b/libs/design-system/tune-menu/src/lib/tune-menu.component.spec.ts new file mode 100644 index 0000000000..fce4d8c944 --- /dev/null +++ b/libs/design-system/tune-menu/src/lib/tune-menu.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TuneMenuComponent } from './tune-menu.component'; + +describe('TuneMenuComponent', () => { + let component: TuneMenuComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TuneMenuComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(TuneMenuComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/libs/design-system/tune-menu/src/lib/tune-menu.component.stories.ts b/libs/design-system/tune-menu/src/lib/tune-menu.component.stories.ts new file mode 100644 index 0000000000..fb5240f798 --- /dev/null +++ b/libs/design-system/tune-menu/src/lib/tune-menu.component.stories.ts @@ -0,0 +1,25 @@ +import { type Meta, type StoryObj } from '@storybook/angular'; + +import { TuneMenuComponent } from './tune-menu.component'; + +const meta: Meta = { + component: TuneMenuComponent, + title: 'Design System / Tune Menu', + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/design/gQEMLugLjweDvbsNNUVffD/HRA-Design-System-Repository?node-id=12494-166042&t=U6WxS9uCkPILoojn-4', + }, + }, +}; +export default meta; +export const Primary: StoryObj = { + render: () => ({ + styles: [ + `.hra-app { + margin: -1rem; + height: 100vh; + }`, + ], + }), +}; diff --git a/libs/design-system/tune-menu/src/lib/tune-menu.component.ts b/libs/design-system/tune-menu/src/lib/tune-menu.component.ts new file mode 100644 index 0000000000..ea0922780d --- /dev/null +++ b/libs/design-system/tune-menu/src/lib/tune-menu.component.ts @@ -0,0 +1,134 @@ +import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core'; +import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatSelectModule } from '@angular/material/select'; +import { watchBreakpoint } from '@hra-ui/cdk/breakpoints'; +import { HraCommonModule } from '@hra-ui/common'; +import { ButtonsModule } from '@hra-ui/design-system/buttons'; +import { IconsModule } from '@hra-ui/design-system/icons'; +import { ScrollingModule, ScrollOverflowFadeDirective } from '@hra-ui/design-system/scrolling'; + +/** Option interface */ +export interface Option { + /** Option id */ + id: string; + /** Option label */ + label: string; +} + +interface TuneMenuOptions { + hideCloseButton?: boolean; + hideHeaderSection?: boolean; + headline?: string; + subtext?: string; + hideCustomizeSection?: boolean; + /** Categories for button toggle */ + toggleCategories?: Option[]; + viewAsOptions?: Option[]; + sortByOptions: Option[]; + groupByOptions: Option[]; +} + +const TOGGLE_OPTIONS = [ + { id: 'option1', label: 'Option 1' }, + { id: 'option2', label: 'Option 2' }, + { id: 'option3', label: 'Option 3' }, +]; +const VIEW_AS_OPTIONS = [ + { id: 'table', label: 'Table' }, + { id: 'gallery', label: 'Gallery' }, + { id: 'list', label: 'List' }, +]; +const SORT_BY_OPTIONS = [ + { id: 'nameAsc', label: 'Name ascending' }, + { id: 'nameDesc', label: 'Name descending' }, + { id: 'oldest', label: 'Oldest' }, + { id: 'newest', label: 'Newest' }, + { id: 'hierachical', label: 'Hierarchical' }, +]; +const FILTER_CATEGORIES = [ + { id: 'category1', label: 'Category 1' }, + { id: 'category2', label: 'Category 2' }, + { id: 'category3', label: 'Category 3' }, + { id: 'category4', label: 'Category 4' }, + { id: 'category5', label: 'Category 5' }, + { id: 'category6', label: 'Category 6' }, + { id: 'category7', label: 'Category 7' }, + { id: 'category8', label: 'Category 8' }, + { id: 'category9', label: 'Category 9' }, + { id: 'category10', label: 'Category 10' }, +]; + +const DEFAULT_OPTIONS: TuneMenuOptions = { + headline: 'Database Headline', + subtext: 'Supporting text here, if needed, but make it short and straightforward', + toggleCategories: TOGGLE_OPTIONS, + viewAsOptions: VIEW_AS_OPTIONS, + sortByOptions: SORT_BY_OPTIONS, + groupByOptions: FILTER_CATEGORIES, +}; + +@Component({ + selector: 'hra-tune-menu', + imports: [ + HraCommonModule, + ScrollingModule, + ScrollOverflowFadeDirective, + ButtonsModule, + IconsModule, + MatButtonToggleModule, + FormsModule, + ReactiveFormsModule, + MatSelectModule, + MatDividerModule, + ], + templateUrl: './tune-menu.component.html', + styleUrl: './tune-menu.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + '[class.closed]': 'formClosed()', + }, +}) +export class TuneMenuComponent { + readonly filterCategories = computed(() => { + const result: Record> = {}; + for (const category of FILTER_CATEGORIES) { + result[category.id] = new FormControl([]); + } + return result; + }); + + readonly form = computed(() => { + return new FormGroup({ + customize: new FormGroup({ + toggle: new FormControl