From 42b343f0331325844fc7d6d67c4675c3ef343308 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 1 Apr 2026 16:12:24 +0300 Subject: [PATCH] Redesign IoT Hub browse page to match design - Two-column layout: left sidebar filters (280px, own scroll) + right card grid - Filters use mat-expansion-panel accordions with checkbox lists - Type, Category, Use Case filter sections with expand/collapse - Top bar: results count + search field, sort button with mat-menu - Active filter chips row with clear all button - 4-column card grid (responsive: 3 at 1200px, 2 at 900px) - Card subtype label: @Input showSubtype, 12px Medium uppercase above name - Tabs use plural item type translations - Remove old header, filter dropdowns, and footer sections --- .../iot-hub/iot-hub-browse.component.html | 332 +++++++------- .../iot-hub/iot-hub-browse.component.scss | 412 ++++++------------ .../pages/iot-hub/iot-hub-browse.component.ts | 12 + .../iot-hub/iot-hub-item-card.component.html | 3 + .../iot-hub/iot-hub-item-card.component.scss | 10 + .../iot-hub/iot-hub-item-card.component.ts | 1 + 6 files changed, 320 insertions(+), 450 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.html b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.html index 05bf3d72ad..94b1ce7940 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.html +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.html @@ -15,214 +15,210 @@ limitations under the License. --> -
- - @if (!embedded) { -
-
-

{{ getTitle() | translate }}

-

{{ getSubtitle() | translate }}

-
- -
- } - +
-
diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.scss b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.scss index deef2d98bc..d228a5f2e6 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.scss +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.scss @@ -18,249 +18,147 @@ display: flex; flex-direction: column; height: 100%; - padding: 16px 24px; - overflow: auto; +} - &.embedded { - padding: 0; - overflow: visible; - height: auto; - } +.tb-iot-hub-browse { + display: flex; + flex-direction: column; + height: 100%; } -.tb-iot-hub-browse-container { - max-width: 1400px; - margin: 0 auto; - width: 100%; - background: white; - padding: 24px; - border-radius: 4px; - box-shadow: 0 0 1px rgba(0, 0, 0, 0.14), 0 1px 1px rgba(0, 0, 0, 0.12); - - &.embedded { - max-width: none; - padding: 0; - border-radius: 0; - box-shadow: none; - background: transparent; - } +// Tabs — Design: px-40, border-bottom +.tb-iot-hub-tabs { + padding: 0 40px; + flex-shrink: 0; } -.tb-iot-hub-browse-header { +// Two-column layout below tabs +.tb-iot-hub-content { display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 24px; - - h1 { - font-size: 28px; - font-weight: 700; - margin: 0 0 4px; - color: rgba(0, 0, 0, 0.87); - } - - p { - font-size: 14px; - color: rgba(0, 0, 0, 0.54); - margin: 0; - } + flex: 1; + min-height: 0; } -.tb-iot-hub-type-tabs { - margin-bottom: 16px; - border-bottom: 1px solid rgba(0, 0, 0, 0.12); - - ::ng-deep { - .mat-mdc-tab-link-container { - flex-grow: 0; - } - - .mat-mdc-tab-link { - text-transform: none; - font-weight: 400; - min-width: auto; - padding: 0 16px; - letter-spacing: normal; - height: 40px; - flex: 0 0 auto; - } - - --mat-tab-active-indicator-color: #1976d2; - - .mdc-tab__text-label { - color: rgba(0, 0, 0, 0.6); - } - - .mdc-tab--active .mdc-tab__text-label { - font-weight: 500; - } +// Left sidebar filters — Design: 280px wide, right border, own scroll +.tb-iot-hub-filters { + width: 280px; + flex-shrink: 0; + border-right: 1px solid rgba(0, 0, 0, 0.12); + overflow-y: auto; +} - --mat-tab-active-label-text-color: #1976d2; - --mat-tab-active-focus-label-text-color: #1976d2; - --mat-tab-active-hover-label-text-color: #1976d2; - --mat-tab-active-ripple-color: #1976d2; - --mat-tab-inactive-ripple-color: #1976d2; - --mat-tab-active-focus-indicator-color: #1976d2; - } +.tb-iot-hub-filters-inner { + display: flex; + flex-direction: column; + gap: 8px; + padding: 24px 0; } -// Shared field styles for search/filter/sort dropdowns -%tb-iot-hub-field-base { +// Filter expansion panels — Design: no border, no shadow, pl-40 for content +.tb-iot-hub-filter-panel { ::ng-deep { - .mat-mdc-text-field-wrapper { - height: 42px; - padding: 0 12px; + &.mat-expansion-panel { + box-shadow: none !important; + border-radius: 0 !important; + background: transparent; } - .mat-mdc-form-field-flex { - height: 42px; - align-items: center; + .mat-expansion-panel-header { + padding: 8px 16px 8px 40px; } - .mdc-notched-outline__leading, - .mdc-notched-outline__notch, - .mdc-notched-outline__trailing { - border-color: #d5dbe3; - } - - .mdc-notched-outline__leading { - border-radius: 8px 0 0 8px; - } - - .mdc-notched-outline__trailing { - border-radius: 0 8px 8px 0; - } - - .mat-mdc-form-field-infix { - padding-top: 0; - padding-bottom: 0; - min-height: unset; - } - - .mdc-notched-outline__notch { - display: none; - } - - .mat-mdc-floating-label { - display: none; - } - - .mat-mdc-form-field-subscript-wrapper { - display: none; - } - - input.mat-mdc-input-element { + .mat-expansion-panel-header-title { font-size: 14px; - color: #3e4a59; - - &::placeholder { - color: #8b95a2; - opacity: 1; - } + font-weight: 500; + color: rgba(0, 0, 0, 0.87); } - .mat-mdc-select-value-text { - font-size: 14px; - color: #3e4a59; + .mat-expansion-panel-body { + padding: 0; } + } - .mat-mdc-select-arrow-wrapper { - padding-left: 4px; - } + mat-checkbox { + display: block; + padding: 8px 16px 8px 40px; - .mat-icon { - color: #8b95a2; - font-size: 20px; - width: 20px; - height: 20px; + &:hover { + background: rgba(0, 0, 0, 0.04); } } +} - &.mat-focused ::ng-deep { - .mdc-notched-outline__leading, - .mdc-notched-outline__notch, - .mdc-notched-outline__trailing { - border-color: #8b95a2; - border-width: 1px; - } - } +// Right content area — Design: flex-1, pr-40, scrollable +.tb-iot-hub-results { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + overflow-y: auto; + padding: 0 40px 0 20px; } -.tb-iot-hub-search-row { +// Top bar: results count + search +.tb-iot-hub-results-top { display: flex; - gap: 12px; - margin-bottom: 12px; align-items: center; + justify-content: space-between; + padding: 16px 0 0; + flex-shrink: 0; +} - .tb-iot-hub-search-field, - .tb-iot-hub-sort-field { - @extend %tb-iot-hub-field-base; - } - - .tb-iot-hub-search-field { - flex: 1; - - ::ng-deep { - .mat-mdc-form-field-icon-prefix { - padding: 0 8px 0 0; - - > .mat-icon { - padding: 0; - } - } - - .mat-mdc-form-field-icon-suffix { - padding: 0; +.tb-iot-hub-results-count { + font-size: 14px; + color: rgba(0, 0, 0, 0.54); +} - .mat-mdc-icon-button { - width: 32px; - height: 32px; - padding: 4px; - } - } - } - } +.tb-iot-hub-search-field { + width: 240px; - .tb-iot-hub-sort-field { - width: 140px; - flex-shrink: 0; + .mat-mdc-form-field-subscript-wrapper { + display: none; } } -.tb-iot-hub-filter-row { +// Sort row + active filters +.tb-iot-hub-sort-row { display: flex; - gap: 12px; - margin-bottom: 16px; - padding-right: 152px; - - .tb-iot-hub-filter-select { - @extend %tb-iot-hub-field-base; - flex: 1; - min-width: 0; - } + align-items: center; + gap: 8px; + padding: 8px 0; + flex-shrink: 0; + flex-wrap: wrap; +} + +.tb-iot-hub-sort-spacer { + flex: 1; } +.tb-iot-hub-sort-btn { + color: rgba(0, 0, 0, 0.54); + flex-shrink: 0; +} + +// Active filter chips .tb-iot-hub-active-filters { display: flex; align-items: center; - gap: 6px; + gap: 8px; flex-wrap: wrap; - margin-bottom: 12px; } .tb-iot-hub-active-chip { display: inline-flex; align-items: center; gap: 4px; - padding: 4px 8px 4px 12px; - border-radius: 16px; + padding: 4px 8px; + border-radius: 4px; font-size: 12px; font-weight: 500; - background: #eef4ff; - color: #305ECA; + background: rgba(0, 0, 0, 0.06); + color: rgba(0, 0, 0, 0.76); cursor: pointer; white-space: nowrap; - transition: background-color 0.15s; mat-icon { font-size: 14px; @@ -269,135 +167,85 @@ } &:hover { - background: #dde8fc; + background: rgba(0, 0, 0, 0.1); } } -.tb-iot-hub-clear-filters { - border: none; - background: none; +.tb-iot-hub-clear-btn { font-size: 12px; - color: #8b95a2; + color: rgba(0, 0, 0, 0.54); cursor: pointer; + background: none; + border: none; padding: 4px 8px; - white-space: nowrap; + font-family: inherit; &:hover { - color: #3e4a59; - text-decoration: underline; + color: rgba(0, 0, 0, 0.87); } } -.tb-iot-hub-results-count { - font-size: 14px; - color: rgba(0, 0, 0, 0.54); - margin-bottom: 16px; +// Card grid — Design: 4 columns, gap 20px +.tb-iot-hub-card-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 20px; + padding: 8px 0 24px; } +// Loading / Empty states .tb-iot-hub-loading { display: flex; justify-content: center; - align-items: center; - min-height: 200px; + padding: 80px 0; } .tb-iot-hub-empty-state { display: flex; flex-direction: column; align-items: center; - justify-content: center; - min-height: 300px; + padding: 80px 0; text-align: center; - color: rgba(0, 0, 0, 0.54); - - .tb-iot-hub-empty-icon { - font-size: 64px; - width: 64px; - height: 64px; - margin-bottom: 16px; - opacity: 0.4; - } h3 { - margin: 0 0 8px 0; + font-size: 18px; + font-weight: 500; color: rgba(0, 0, 0, 0.87); + margin: 16px 0 8px; } p { - margin: 0 0 16px 0; + font-size: 14px; + color: rgba(0, 0, 0, 0.54); + margin: 0 0 24px; } } -.tb-iot-hub-card-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: 16px; - margin-bottom: 16px; +.tb-iot-hub-empty-icon { + font-size: 48px; + width: 48px; + height: 48px; + color: rgba(0, 0, 0, 0.2); } -::ng-deep mat-paginator { - .mat-mdc-form-field { - .mat-mdc-text-field-wrapper { - height: 42px; - padding: 0 12px; - border: 1px solid #d5dbe3; - border-radius: 8px; - background: white; - } - - .mat-mdc-form-field-flex { - height: 42px; - align-items: center; - } - - .mdc-notched-outline { - display: none; - } - - .mat-mdc-form-field-infix { - padding-top: 0; - padding-bottom: 0; - min-height: unset; - border-top: none; - } - - .mat-mdc-floating-label { - display: none; - } - - .mat-mdc-form-field-subscript-wrapper { - display: none; - } - - .mat-mdc-select-value-text { - font-size: 14px; - color: #3e4a59; - } - - .mat-mdc-select-arrow-wrapper { - padding-left: 4px; - } +// Pagination +mat-paginator { + flex-shrink: 0; +} - &.mat-focused .mat-mdc-text-field-wrapper { - border-color: #8b95a2; - } +// Responsive +@media (max-width: 1200px) { + .tb-iot-hub-card-grid { + grid-template-columns: repeat(3, 1fr); } } -.tb-iot-hub-browse-footer { - text-align: center; - padding: 24px 0; - border-top: 1px solid rgba(0, 0, 0, 0.08); - margin-top: 16px; - - .tb-iot-hub-become-creator { - color: rgba(0, 0, 0, 0.38); - font-size: 13px; - text-decoration: none; +@media (max-width: 900px) { + .tb-iot-hub-filters { + width: 220px; + } - &:hover { - color: rgba(0, 0, 0, 0.54); - text-decoration: underline; - } + .tb-iot-hub-card-grid { + grid-template-columns: repeat(2, 1fr); } } diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts index 083b858c70..f753f0da27 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-browse.component.ts @@ -159,6 +159,18 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { this.loadItems(); } + isSubtypeActive(key: string): boolean { + return this.getActiveSubtypes().has(key); + } + + isCategoryActive(key: string): boolean { + return this.activeCategories.has(key); + } + + isUseCaseActive(key: string): boolean { + return this.activeUseCases.has(key); + } + onCategoryToggle(category: string): void { if (this.activeCategories.has(category)) { this.activeCategories.delete(category); diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.html b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.html index cbebb07387..532e9a2770 100644 --- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.html +++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.html @@ -54,6 +54,9 @@ }
+ @if (showSubtype && getSubtypeLabel(); as subtypeLabel) { + {{ subtypeLabel }} + }

{{ item.name }}