Browse Source

feat(iot-hub): unified page-level search field across browse/search/installed

Redesigned the top search input on iot-hub-browse, iot-hub-search, and
the installed-items page to share a single page-level field style:
56px tall, 8px radius, 1px light border, white background, 16px input
text, with a 40px round trailing action that shows a non-clickable
search icon when empty and switches to a clickable close icon-button
when text is entered.

Layout updates:
- Browse: search input + sort button now share a single row at the top
  (sort moved out of the filter-chips row); the filter-toggle button
  moved to the filter-chips row right-aligned (mobile only). Sort row
  is 32px tall with 16/12px vertical margin and zero chip margins.
- Search: input fills the row up to the sort button; placeholder is
  'Search published items...' on creator profiles, 'Search...' otherwise.
- Browse search input placeholder is per-type ('Search widgets...',
  'Search solution templates...', etc.) via searchPlaceholderKey getter.
- Installed items: filter button + chips on a top row, search input on
  a dedicated row below; chips margin-top/bottom zeroed.

Common SCSS:
- Added tb-iot-hub-page-search-container / -input / -action mixins to
  _iot-hub-common.scss; browse, search, and installed-items pull them
  via @include.
- Locale: new iot-hub.search-published-items key.
pull/15615/head
Igor Kulikov 6 months ago
parent
commit
793c0f9e4c
  1. 62
      ui-ngx/src/app/modules/home/components/iot-hub/_iot-hub-common.scss
  2. 40
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html
  3. 46
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.scss
  4. 13
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts
  5. 13
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-search.component.html
  6. 19
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-search.component.scss
  7. 4
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-search.component.ts
  8. 21
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.html
  9. 80
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.scss
  10. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

62
ui-ngx/src/app/modules/home/components/iot-hub/_iot-hub-common.scss

@ -70,3 +70,65 @@
height: 18px;
}
}
// Page-level search field — used by iot-hub-browse and iot-hub-search.
// Larger than the filter inputs above: 56px tall, 16px text, with a 40px
// trailing action area (search icon when empty, close icon-button when
// typing).
@mixin tb-iot-hub-page-search-container {
display: flex;
align-items: center;
width: 0;
height: 56px;
padding: 8px 4px 8px 16px;
gap: 8px;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 8px;
background: white;
}
@mixin tb-iot-hub-page-search-input {
flex: 1;
min-width: 0;
border: none;
outline: none;
padding: 0;
font-family: inherit;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0.15px;
color: rgba(0, 0, 0, 0.87);
background: transparent;
&::placeholder {
color: rgba(0, 0, 0, 0.54);
}
}
@mixin tb-iot-hub-page-search-action {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
flex-shrink: 0;
border-radius: 50%;
color: rgba(0, 0, 0, 0.54);
mat-icon {
font-size: 24px;
width: 24px;
height: 24px;
}
&.mat-mdc-icon-button {
padding: 8px;
--mdc-icon-button-state-layer-size: 40px;
}
&-static {
pointer-events: none;
}
}

40
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html

@ -322,28 +322,36 @@
<!-- Right content area -->
<div class="tb-iot-hub-results">
<!-- Top bar: search + filter button (mobile) -->
<div class="tb-iot-hub-results-top flex shrink-0 items-center justify-between pt-6">
<div class="tb-iot-hub-search-field flex items-center gap-2">
<mat-icon class="tb-iot-hub-search-icon">search</mat-icon>
<!-- Top bar: search + sort + filter button (mobile) -->
<div class="tb-iot-hub-results-top flex shrink-0 items-center gap-4 pt-6">
<div class="tb-iot-hub-search-field flex flex-1 items-center">
<input class="tb-iot-hub-search-input"
[placeholder]="'iot-hub.search' | translate"
[placeholder]="searchPlaceholderKey | translate"
[value]="textSearch"
(input)="onSearchChange($any($event.target).value)">
@if (textSearch) {
<button mat-icon-button class="tb-iot-hub-search-clear" (click)="onSearchChange('')">
<button mat-icon-button class="tb-iot-hub-search-action" (click)="onSearchChange('')">
<mat-icon>close</mat-icon>
</button>
} @else {
<span class="tb-iot-hub-search-action tb-iot-hub-search-action-static" aria-hidden="true">
<mat-icon>search</mat-icon>
</span>
}
</div>
<button mat-stroked-button color="primary" class="tb-iot-hub-filter-toggle-btn" (click)="filterDrawerOpened = true">
<mat-icon>filter_list</mat-icon>
{{ 'iot-hub.filter' | translate }}@if (activeFilterCount) {: {{ activeFilterCount }}}
<button mat-button color="primary" class="tb-iot-hub-sort-btn" [matMenuTriggerFor]="sortMenu">
{{ sortOptions[selectedSortIndex].label | translate }}
<mat-icon iconPositionEnd>arrow_drop_down</mat-icon>
</button>
<mat-menu #sortMenu="matMenu">
@for (option of sortOptions; track option.value; let i = $index) {
<button mat-menu-item (click)="onSortChange(i)">{{ option.label | translate }}</button>
}
</mat-menu>
</div>
<!-- Sort row: results count + filter chips + sort -->
<div class="tb-iot-hub-sort-row flex shrink-0 flex-wrap items-center gap-2 py-2">
<!-- Filter chips row: results count + filter chips -->
<div class="tb-iot-hub-sort-row flex shrink-0 flex-wrap items-center gap-2">
<span class="tb-iot-hub-results-count">
{{ 'iot-hub.results-count' | translate:{ count: totalElements } }}
</span>
@ -391,16 +399,10 @@
{{ 'iot-hub.clear-all' | translate }}
</button>
}
<span class="flex-1"></span>
<button mat-button color="primary" class="tb-iot-hub-sort-btn" [matMenuTriggerFor]="sortMenu">
<button mat-stroked-button color="primary" class="tb-iot-hub-filter-toggle-btn ml-auto" (click)="filterDrawerOpened = true">
<mat-icon>filter_list</mat-icon>
{{ sortOptions[selectedSortIndex].label | translate }}
{{ 'iot-hub.filter' | translate }}@if (activeFilterCount) {: {{ activeFilterCount }}}
</button>
<mat-menu #sortMenu="matMenu">
@for (option of sortOptions; track option.value; let i = $index) {
<button mat-menu-item (click)="onSortChange(i)">{{ option.label | translate }}</button>
}
</mat-menu>
</div>
<!-- Loading -->

46
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.scss

@ -325,27 +325,49 @@
}
.tb-iot-hub-search-field {
@include tb-iot-hub-search-container;
width: 320px;
}
.tb-iot-hub-search-icon {
@include tb-iot-hub-search-icon;
@include tb-iot-hub-page-search-container;
}
.tb-iot-hub-search-input {
@include tb-iot-hub-search-input-text;
@include tb-iot-hub-page-search-input;
}
.tb-iot-hub-search-clear.mat-mdc-icon-button {
@include tb-iot-hub-search-clear;
.tb-iot-hub-search-action {
@include tb-iot-hub-page-search-action;
}
// Sort button — Design: 14px Medium, primary color, with filter_list icon
// Sort button — Design: same row as search, 40px h, 88px min-w, 4px radius,
// 14px Medium primary color, trailing arrow_drop_down 24px icon
.tb-iot-hub-sort-btn {
flex-shrink: 0;
height: 40px;
min-width: 88px;
padding: 8px;
font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: 0.25px;
border-radius: 4px;
.mat-icon {
font-size: 24px;
width: 24px;
height: 24px;
}
}
// Filter chips row — Design: 32px tall, no padding, centered
.tb-iot-hub-sort-row {
height: 32px;
align-items: center;
padding: 0;
margin-top: 16px;
margin-bottom: 12px;
.mat-mdc-chip {
margin-top: 0;
margin-bottom: 0;
}
}
// Active filter chips — Design: mat-chip-set, gap-8, chip bg rgba(0,0,0,0.04)
@ -557,10 +579,6 @@
width: 100%;
max-width: 100%;
}
.tb-iot-hub-search-field {
width: 200px;
}
}
@media #{$mat-xs} {

13
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts

@ -93,6 +93,19 @@ export class TbIotHubBrowseComponent implements OnInit, AfterViewInit, OnDestroy
|| this._activeType === ItemType.RULE_CHAIN;
}
get searchPlaceholderKey(): string {
switch (this._activeType) {
case ItemType.WIDGET: return 'iot-hub.search-widgets';
case ItemType.DASHBOARD: return 'iot-hub.search-dashboards';
case ItemType.SOLUTION_TEMPLATE: return 'iot-hub.search-solution-templates';
case ItemType.CALCULATED_FIELD: return 'iot-hub.search-calculated-fields';
case ItemType.ALARM_RULE: return 'iot-hub.search-alarm-rules';
case ItemType.RULE_CHAIN: return 'iot-hub.search-rule-chains';
case ItemType.DEVICE: return 'iot-hub.search-devices';
default: return 'iot-hub.search';
}
}
items: MpItemVersionView[] = [];
totalElements = 0;
pageSize = 12;

13
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-search.component.html

@ -25,22 +25,25 @@
</div>
<!-- Search + Sort row -->
<div class="flex items-center gap-4">
<div class="tb-search-input-field">
<mat-icon class="tb-search-input-icon">search</mat-icon>
<div class="tb-search-input-field flex flex-1 items-center">
<input class="tb-search-input"
[placeholder]="'iot-hub.search' | translate"
[placeholder]="searchPlaceholderKey | translate"
[(ngModel)]="searchText"
(input)="onSearchInput()"
(keyup.enter)="onSearchEnter()">
@if (searchText) {
<button mat-icon-button class="tb-search-input-clear" (click)="clearSearch()">
<button mat-icon-button class="tb-search-input-action" (click)="clearSearch()">
<mat-icon>close</mat-icon>
</button>
} @else {
<span class="tb-search-input-action tb-search-input-action-static" aria-hidden="true">
<mat-icon>search</mat-icon>
</span>
}
</div>
<button mat-button color="primary" class="tb-search-sort-btn tb-search-sort-btn-full" [matMenuTriggerFor]="sortMenu">
<mat-icon>filter_list</mat-icon>
{{ sortOptions[selectedSortIndex].label | translate }}
<mat-icon iconPositionEnd>arrow_drop_down</mat-icon>
</button>
<button mat-icon-button color="primary" class="tb-search-sort-btn tb-search-sort-btn-icon" [matMenuTriggerFor]="sortMenu">
<mat-icon>filter_list</mat-icon>

19
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-search.component.scss

@ -29,21 +29,15 @@
}
.tb-search-input-field {
@include tb-iot-hub-search-container;
width: 320px;
min-width: 0;
}
.tb-search-input-icon {
@include tb-iot-hub-search-icon;
@include tb-iot-hub-page-search-container;
}
.tb-search-input {
@include tb-iot-hub-search-input-text;
@include tb-iot-hub-page-search-input;
}
.tb-search-input-clear.mat-mdc-icon-button {
@include tb-iot-hub-search-clear;
.tb-search-input-action {
@include tb-iot-hub-page-search-action;
}
.tb-search-sort-btn {
@ -272,11 +266,6 @@
}
@media #{$mat-lt-sm} {
.tb-search-input-field {
width: 100%;
flex: 1;
}
.tb-search-sort-btn-full {
display: none;
}

4
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-search.component.ts

@ -58,6 +58,10 @@ export class TbIotHubSearchComponent implements OnInit, OnDestroy {
@Input() showCreator = true;
@Output() searchTextChange = new EventEmitter<string>();
get searchPlaceholderKey(): string {
return this.creatorId ? 'iot-hub.search-published-items' : 'iot-hub.search';
}
resultGroups: SearchResultGroup[] = [];
totalElements = 0;
isLoading = false;

21
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.html

@ -37,8 +37,8 @@
}
</div>
<!-- Filter + Search row -->
<div class="tb-installed-toolbar">
<!-- Filter + chips row -->
<div class="tb-installed-filter-row">
<button mat-stroked-button color="primary" class="tb-installed-filter-btn" [matMenuTriggerFor]="filterMenu">
<mat-icon>filter_list</mat-icon>
{{ 'iot-hub.filter' | translate }}
@ -71,13 +71,24 @@
{{ 'iot-hub.clear-all' | translate }}
</button>
}
<span class="flex-1"></span>
<div class="tb-installed-search">
<mat-icon class="tb-installed-search-icon">search</mat-icon>
</div>
<!-- Search row -->
<div class="tb-installed-search-row">
<div class="tb-installed-search flex flex-1 items-center">
<input class="tb-installed-search-input"
[placeholder]="'iot-hub.search' | translate"
[value]="textSearch"
(input)="onSearchChange($any($event.target).value)">
@if (textSearch) {
<button mat-icon-button class="tb-installed-search-action" (click)="onSearchChange('')">
<mat-icon>close</mat-icon>
</button>
} @else {
<span class="tb-installed-search-action tb-installed-search-action-static" aria-hidden="true">
<mat-icon>search</mat-icon>
</span>
}
</div>
</div>

80
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.scss

@ -15,6 +15,7 @@
*/
@import "../../../../../scss/constants";
@import "../../components/iot-hub/iot-hub-common";
:host {
display: block;
@ -49,12 +50,25 @@
margin: 0;
}
// Toolbar: filter + search
.tb-installed-toolbar {
// Filter row — filter button + active chips on top
.tb-installed-filter-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-bottom: 16px;
.mat-mdc-chip {
margin-top: 0;
margin-bottom: 0;
}
}
// Search row — full-width search input below the filter row
.tb-installed-search-row {
display: flex;
align-items: center;
margin-bottom: 16px;
}
.tb-installed-filter-btn {
@ -104,67 +118,19 @@
}
}
// Search field
.tb-installed-search {
width: 320px;
height: 40px;
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-radius: 8px;
border: 1px solid rgba(0, 0, 0, 0.12);
background: white;
}
.tb-installed-search-icon {
font-size: 24px;
width: 24px;
height: 24px;
color: rgba(0, 0, 0, 0.38);
flex-shrink: 0;
@include tb-iot-hub-page-search-container;
}
.tb-installed-search-input {
flex: 1;
border: none;
outline: none;
font-size: 14px;
font-weight: 400;
color: rgba(0, 0, 0, 0.87);
background: transparent;
font-family: inherit;
&::placeholder {
color: rgba(0, 0, 0, 0.38);
}
@include tb-iot-hub-page-search-input;
}
// Responsive
@media #{$mat-lt-lg} {
.tb-installed-toolbar {
flex-wrap: wrap;
}
.tb-installed-search {
order: 1;
}
.tb-installed-filter-chips {
order: 10;
}
.tb-installed-clear-btn {
order: 11;
}
.tb-installed-toolbar::before {
content: '';
width: 100%;
order: 9;
}
.tb-installed-search-action {
@include tb-iot-hub-page-search-action;
}
// Responsive
@media #{$mat-lt-md} {
.tb-installed-items-page {
padding: 40px 24px;
@ -182,8 +148,4 @@
align-items: flex-start;
gap: 16px;
}
.tb-installed-search {
width: 200px;
}
}

1
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3706,6 +3706,7 @@
"and-devices": "& Devices",
"search-placeholder": "Search in IoT Hub...",
"search": "Search...",
"search-published-items": "Search published items...",
"see-all-results": "See all results",
"no-search-results": "No results found for '{{ search }}'",
"no-items-available": "No items available",

Loading…
Cancel
Save