Skip to content

Commit 21e23a2

Browse files
fix(visualization): replace angular material accordion with DaisyUI collapse component. (#4392)
* fix(visualization):replace angular material accordion with DaisyUI collapse component. * fix(visualization): delete unused import * fix(visualization): remove unnecessary scss styling
1 parent 58c8d71 commit 21e23a2

9 files changed

+199
-104
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
---
2+
name: Fix Custom Views expansion panel borders and migrate to DaisyUI
3+
issue: #4293
4+
state: complete
5+
version: N/A
6+
---
7+
8+
## Goal
9+
10+
Migrate the Custom Views dialog from Angular Material to DaisyUI components while fixing the border-radius issue and maintaining all existing functionality.
11+
12+
## Tasks
13+
14+
### 1. Replace Angular Material components with DaisyUI in customConfigList
15+
- [x] Replace `<mat-toolbar>` with custom styled header using `bg-primary`
16+
- [x] Replace `<mat-form-field>` with DaisyUI `input input-bordered`
17+
- [x] Remove Material accordion wrapper components
18+
- [x] Remove all Material imports from TypeScript file
19+
20+
### 2. Replace Angular Material accordion with DaisyUI collapse in customConfigItemGroup
21+
- [x] Replace `<mat-expansion-panel>` with DaisyUI `<div class="collapse collapse-arrow">`
22+
- [x] Change to checkbox-based expansion control
23+
- [x] Replace `<mat-list>` with Tailwind flexbox layout
24+
- [x] Keep `@Input()` decorator approach (not signals) to avoid infinite loop
25+
- [x] Remove Material expansion panel imports
26+
- [x] Keep `MatDialogClose` for mat-dialog-close directive
27+
28+
### 3. Update SCSS styling
29+
- [x] Remove Material variable dependencies from component SCSS files
30+
- [x] Add `.collapse { border-radius: 0; }` to remove rounded borders
31+
- [x] Create custom toolbar class with `background-color: #1b9cfc`
32+
- [x] Set Material dialog container color to `#1b9cfc` in matCustomConfigList.scss
33+
34+
### 4. Update tests to work with DaisyUI components
35+
- [x] Replace Material element selectors with DaisyUI/Tailwind class selectors
36+
- [x] Change button click tests to checkbox click tests for accordion expansion
37+
- [x] Update element queries from `mat-expansion-panel-header` to `.collapse-title`
38+
- [x] Update element queries from `mat-list-item` to `.border-b.border-black.py-2`
39+
40+
### 5. Verify everything works correctly
41+
- [x] Clear Angular build cache to ensure fresh build
42+
- [x] Test visual appearance in the Custom Views dialog
43+
- [x] Run all unit tests (350 test suites, 1868 tests passed)
44+
- [x] Build for production successfully
45+
46+
## Steps
47+
48+
- [x] Complete Task 1: Migrate customConfigList to DaisyUI
49+
- [x] Complete Task 2: Migrate customConfigItemGroup to DaisyUI
50+
- [x] Complete Task 3: Update SCSS styling
51+
- [x] Complete Task 4: Update tests
52+
- [x] Complete Task 5: Verify and test
53+
54+
## Review Feedback Addressed
55+
56+
- User requested to use only DaisyUI components, not mix with Material
57+
- User requested to revert to original @Input() approach after signals caused infinite loop
58+
- User requested toolbar color to be #1b9cfc instead of default purple
59+
- User requested to move Material overrides from tailwind.css to Material SCSS files
60+
61+
## Notes
62+
63+
### Implementation Details
64+
- Replaced all Angular Material components with DaisyUI equivalents
65+
- Used `@Input()` decorators instead of signals to avoid infinite loop in effect()
66+
- DaisyUI collapse components work independently without wrapper
67+
- Dialog container background color set in `matCustomConfigList.scss` for proper organization
68+
- Toolbar uses custom class `.custom-config-toolbar` with explicit `#1b9cfc` background
69+
70+
### Files Modified
71+
- `customConfigList.component.html` - Converted to DaisyUI
72+
- `customConfigList.component.ts` - Removed Material imports
73+
- `customConfigList.component.scss` - Added custom toolbar styling
74+
- `customConfigItemGroup.component.html` - Converted to DaisyUI collapse
75+
- `customConfigItemGroup.component.ts` - Kept @Input() approach
76+
- `customConfigItemGroup.component.scss` - Removed Material dependencies, added border-radius override
77+
- `matCustomConfigList.scss` - Added dialog container color override
78+
- `customConfigList.component.spec.ts` - Updated test selectors
79+
- `customConfigItemGroup.component.spec.ts` - Updated test selectors
80+
81+
### Test Results
82+
- All 350 test suites passed
83+
- 1868 tests passed (6 todo)
84+
- Production build successful
85+
- No TypeScript or SCSS errors
Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,49 @@
11
@for (customConfigItemGroup of customConfigItemGroups | keyvalue; track customConfigItemGroup.key) {
2-
<mat-expansion-panel
3-
#matExpansionPanel
4-
class="custom-config-item-group"
5-
[expanded]="isGroupExpanded(customConfigItemGroup.key) || searchTerm.length > 0"
6-
>
7-
<mat-expansion-panel-header (click)="toggleGroupExpansion(customConfigItemGroup.key)">
8-
<mat-panel-title class="custom-config-item-group-title">
2+
<div class="collapse collapse-arrow bg-base-100 border-b border-black">
3+
<input
4+
type="checkbox"
5+
[checked]="isGroupExpanded(customConfigItemGroup.key) || searchTerm.length > 0"
6+
(change)="toggleGroupExpansion(customConfigItemGroup.key)"
7+
/>
8+
<div class="collapse-title text-base font-normal px-1 py-4">
9+
<span class="custom-config-item-group-title">
910
Custom View(s) in
1011
<strong> {{ customConfigItemGroup.value.mapSelectionMode | titlecase }} </strong>
1112
mode for
1213
{{ customConfigItemGroup.value.mapNames }}
13-
</mat-panel-title>
14-
</mat-expansion-panel-header>
15-
@if (customConfigItemGroup.value.customConfigItems | filterCustomConfigDataBySearchTerm: searchTerm; as filteredCustomConfigs) {
14+
</span>
15+
</div>
16+
<div class="collapse-content px-4">
17+
@if (customConfigItemGroup.value.customConfigItems | filterCustomConfigDataBySearchTerm: searchTerm; as filteredCustomConfigs) {
1618
@if (filteredCustomConfigs.length > 0) {
17-
<mat-list>
19+
<div class="flex flex-col">
1820
@for (customConfig of filteredCustomConfigs; track customConfig) {
19-
<mat-list-item title="{{ customConfig | customConfig2ApplicableMessage }}">
21+
<div
22+
class="border-b border-black py-2 px-1 hover:bg-black/5 cursor-pointer transition-colors"
23+
title="{{ customConfig | customConfig2ApplicableMessage }}"
24+
>
2025
<div class="metrics-box">
2126
<p class="config-item-name" title="{{ customConfig.name }}">
2227
<strong>
23-
<span (click)="applyCustomConfig(customConfig.id)" mat-dialog-close>
28+
<button
29+
type="button"
30+
class="text-button"
31+
(click)="applyCustomConfig(customConfig.id)"
32+
mat-dialog-close>
2433
{{ customConfig.name | truncateText: 75 }}
25-
</span>
34+
</button>
2635
</strong>
2736
</p>
2837
</div>
2938
<div class="custom-config-note">
3039
<p class="custom-config-note-content">
31-
<span (click)="applyCustomConfig(customConfig.id)" mat-dialog-close>
40+
<button
41+
type="button"
42+
class="text-button"
43+
(click)="applyCustomConfig(customConfig.id)"
44+
mat-dialog-close>
3245
{{ customConfig.note ? (customConfig.note | truncateText: 95) : "Add Note" }}
33-
</span>
46+
</button>
3447
</p>
3548
<cc-custom-config-note-dialog-button
3649
[customConfigItem]="customConfig"
@@ -46,15 +59,16 @@
4659
<i class="fa fa-trash"></i>
4760
</button>
4861
</div>
49-
</mat-list-item>
62+
</div>
5063
}
51-
</mat-list>
64+
</div>
5265
}
5366
@if (filteredCustomConfigs.length === 0) {
5467
<div class="no-configs-found-message">
5568
<p>No configurations found.</p>
5669
</div>
5770
}
58-
}
59-
</mat-expansion-panel>
71+
}
72+
</div>
73+
</div>
6074
}

visualization/app/codeCharta/ui/customConfigs/customConfigList/customConfigItemGroup/customConfigItemGroup.component.scss

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
1-
@use "variables";
1+
.collapse {
2+
border-radius: 0;
3+
}
4+
5+
.text-button {
6+
background: none;
7+
border: none;
8+
padding: 0;
9+
font: inherit;
10+
color: inherit;
11+
cursor: pointer;
12+
text-align: inherit;
13+
14+
&:hover {
15+
text-decoration: underline;
16+
}
17+
}
218

319
.custom-config-item-group-title {
4-
color: variables.$cc-font-color;
5-
display: inline-block;
20+
color: rgba(0, 0, 0, 0.87);
621
}
722

823
.metrics-box {
@@ -33,18 +48,15 @@ p {
3348
}
3449
}
3550

36-
button {
51+
.remove-button {
52+
font-size: 16px;
53+
padding: 8px 10px;
54+
margin: 0;
3755
background-color: transparent;
3856

39-
&.remove-button {
40-
font-size: 16px;
41-
padding: 8px 10px;
42-
margin: 0;
43-
44-
&:hover {
45-
background-color: variables.$cc-hovered-button-background-color;
46-
color: variables.$cc-primary-color;
47-
border-radius: 50%;
48-
}
57+
&:hover {
58+
background-color: rgba(0, 0, 0, 0.05);
59+
color: #1b9cfc;
60+
border-radius: 50%;
4961
}
5062
}

visualization/app/codeCharta/ui/customConfigs/customConfigList/customConfigItemGroup/customConfigItemGroup.component.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { MatDialog, MatDialogRef } from "@angular/material/dialog"
33
import { expect } from "@jest/globals"
44
import { State } from "@ngrx/store"
55
import { MockStore, provideMockStore } from "@ngrx/store/testing"
6-
import { queryByRole, queryByText, render, screen, waitFor } from "@testing-library/angular"
6+
import { queryByText, render, screen, waitFor } from "@testing-library/angular"
77
import userEvent from "@testing-library/user-event"
88
import { CustomConfigMapSelectionMode } from "../../../../model/customConfig/customConfig.api.model"
99
import { defaultState } from "../../../../state/store/state.manager"
@@ -84,7 +84,7 @@ describe("customConfigItemGroupComponent", () => {
8484
})
8585

8686
CustomConfigHelper.applyCustomConfig = jest.fn()
87-
const applyCustomConfigButton = screen.getByText("SampleMap View #1").closest("span") as HTMLElement
87+
const applyCustomConfigButton = screen.getByText("SampleMap View #1").closest("button") as HTMLElement
8888

8989
await userEvent.click(applyCustomConfigButton)
9090

@@ -161,12 +161,12 @@ describe("customConfigItemGroupComponent", () => {
161161

162162
expect(fixture.componentInstance.isGroupExpanded("Custom View(s) in Standard mode for fileB fileC")).toBeFalsy()
163163

164-
const header = queryByRole(container as HTMLElement, "button")
165-
expect(header).not.toBeNull()
164+
const checkbox = container.querySelector('input[type="checkbox"]') as HTMLInputElement
165+
expect(checkbox).not.toBeNull()
166166

167167
const toggleGroupExpansionSpy = jest.spyOn(fixture.componentInstance, "toggleGroupExpansion")
168168

169-
await userEvent.click(header)
169+
await userEvent.click(checkbox)
170170

171171
expect(toggleGroupExpansionSpy).toHaveBeenCalledTimes(1)
172172
waitFor(() => {

visualization/app/codeCharta/ui/customConfigs/customConfigList/customConfigItemGroup/customConfigItemGroup.component.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import { Component, Input, OnChanges, SimpleChanges, ViewChild } from "@angular/core"
1+
import { Component, Input, OnChanges, SimpleChanges } from "@angular/core"
22
import { CustomConfigHelper } from "../../../../util/customConfigHelper"
33
import { CustomConfigItemGroup } from "../../customConfigs.component"
44
import { ThreeCameraService } from "../../../codeMap/threeViewer/threeCamera.service"
55
import { ThreeMapControlsService } from "../../../codeMap/threeViewer/threeMapControls.service"
66
import { Store } from "@ngrx/store"
7-
import { MatExpansionPanel, MatExpansionPanelHeader, MatExpansionPanelTitle } from "@angular/material/expansion"
87
import { ThreeRendererService } from "../../../codeMap/threeViewer/threeRenderer.service"
9-
import { MatList, MatListItem } from "@angular/material/list"
108
import { MatDialogClose } from "@angular/material/dialog"
119
import { CustomConfigNoteDialogButtonComponent } from "../../customConfigNoteDialogButton/customConfigNoteDialogButton.component"
1210
import { ApplyCustomConfigButtonComponent } from "./customConfigDescription/applyCustomConfigButton.component"
@@ -20,11 +18,6 @@ import { FilterCustomConfigDataBySearchTermPipe } from "./customConfigDescriptio
2018
templateUrl: "./customConfigItemGroup.component.html",
2119
styleUrls: ["./customConfigItemGroup.component.scss"],
2220
imports: [
23-
MatExpansionPanel,
24-
MatExpansionPanelHeader,
25-
MatExpansionPanelTitle,
26-
MatList,
27-
MatListItem,
2821
MatDialogClose,
2922
CustomConfigNoteDialogButtonComponent,
3023
ApplyCustomConfigButtonComponent,
@@ -37,16 +30,15 @@ import { FilterCustomConfigDataBySearchTermPipe } from "./customConfigDescriptio
3730
})
3831
export class CustomConfigItemGroupComponent implements OnChanges {
3932
@Input() customConfigItemGroups: Map<string, CustomConfigItemGroup>
40-
@ViewChild("matExpansionPanel") matExpansionPanel: MatExpansionPanel
4133
@Input() searchTerm = ""
4234
expandedStates: { [key: string]: boolean } = {}
4335
manuallyToggled: Set<string> = new Set()
4436

4537
constructor(
46-
private store: Store,
47-
private threeCameraService: ThreeCameraService,
48-
private threeOrbitControlsService: ThreeMapControlsService,
49-
private threeRendererService: ThreeRendererService
38+
private readonly store: Store,
39+
private readonly threeCameraService: ThreeCameraService,
40+
private readonly threeOrbitControlsService: ThreeMapControlsService,
41+
private readonly threeRendererService: ThreeRendererService
5042
) {}
5143

5244
ngOnChanges(changes: SimpleChanges): void {
Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,37 @@
1-
<mat-toolbar color="primary"
2-
>Custom Views
3-
<div class="row">
4-
<div class="action-buttons">
5-
<cc-upload-custom-config-button></cc-upload-custom-config-button>
6-
<cc-download-custom-configs-button></cc-download-custom-configs-button>
7-
<cc-add-custom-config-button class="custom-configs-button-in-custom-views"></cc-add-custom-config-button>
8-
</div>
9-
</div
10-
>
11-
</mat-toolbar>
1+
<div class="custom-config-toolbar px-4 py-3 flex items-center justify-between">
2+
<h2 class="text-xl font-semibold">Custom Views</h2>
3+
<div class="flex gap-2">
4+
<cc-upload-custom-config-button></cc-upload-custom-config-button>
5+
<cc-download-custom-configs-button></cc-download-custom-configs-button>
6+
<cc-add-custom-config-button class="custom-configs-button-in-custom-views"></cc-add-custom-config-button>
7+
</div>
8+
</div>
129

1310
@if (customConfigService.customConfigItemGroups$ | async; as dropDownCustomConfigItemGroups) {
14-
<mat-dialog-content class="content">
15-
<p class="custom-config-documentation-hint">
11+
<div class="content p-4">
12+
<p class="custom-config-documentation-hint text-sm mb-4">
1613
Custom Views allow you to save and upload your individual configurations for certain maps. Find out more about Custom Views in
1714
the
18-
<a href="https://codecharta.com/docs/visualization/custom-views" target="_blank" rel="noopener noreferrer">documentation</a
15+
<a href="https://codecharta.com/docs/visualization/custom-views" target="_blank" rel="noopener noreferrer" class="link link-primary">documentation</a
1916
>.
2017
</p>
2118
@if (dropDownCustomConfigItemGroups.applicableItems.size === 0 && dropDownCustomConfigItemGroups.nonApplicableItems.size === 0) {
22-
<div class="no-custom-configs-box">It is time to add your first Custom View!</div>
19+
<div class="no-custom-configs-box text-center py-8 text-gray-600">It is time to add your first Custom View!</div>
2320
}
2421
@if (dropDownCustomConfigItemGroups.applicableItems.size !== 0 || dropDownCustomConfigItemGroups.nonApplicableItems.size !== 0) {
25-
<mat-accordion class="custom-config-container" [multi]="true">
26-
<mat-form-field class="cc-custom-configs-search-field">
27-
<i matPrefix class="fa fa-search custom-config-search-icon"></i>
28-
<mat-label>{{ searchPlaceholder }}</mat-label>
29-
<input matInput type="text" (input)="setSearchTermDebounced($event)" [value]="searchTerm" />
30-
</mat-form-field>
22+
<div class="custom-config-container">
23+
<div class="cc-custom-configs-search-field mb-4">
24+
<label class="input input-bordered flex items-center gap-2">
25+
<i class="fa fa-search text-gray-500" aria-hidden="true"></i>
26+
<input
27+
type="text"
28+
class="grow"
29+
placeholder="{{ searchPlaceholder }}"
30+
aria-label="Search custom configurations"
31+
(input)="setSearchTermDebounced($event)"
32+
[value]="searchTerm" />
33+
</label>
34+
</div>
3135
<cc-custom-config-item-group
3236
[customConfigItemGroups]="dropDownCustomConfigItemGroups.applicableItems"
3337
[searchTerm]="searchTerm"
@@ -39,12 +43,12 @@
3943
></cc-custom-config-item-group>
4044
}
4145
@if (dropDownCustomConfigItemGroups.nonApplicableItems.size > 0) {
42-
<button class="toggle-non-applicable-configs-button" (click)="toggleNonApplicableCustomConfigsList()">
46+
<button class="toggle-non-applicable-configs-button btn btn-ghost w-full mt-4" (click)="toggleNonApplicableCustomConfigsList()">
4347
{{ isNonApplicableListCollapsed ? "Show non-applicable Custom Views" : "Hide non-applicable Custom Views" }}
4448
<i [ngClass]="isNonApplicableListCollapsed ? 'fa fa-angle-down' : 'fa fa-angle-up'"></i>
4549
</button>
4650
}
47-
</mat-accordion>
51+
</div>
4852
}
49-
</mat-dialog-content>
53+
</div>
5054
}

0 commit comments

Comments
 (0)