Skip to content

Commit bd6df5b

Browse files
committed
ux: better buttons toggle mechansim, new toast for no products selected
1 parent 4316b21 commit bd6df5b

3 files changed

Lines changed: 150 additions & 33 deletions

File tree

angular/src/app/search-panel/search-panel.component.html

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
1-
<p-toast [baseZIndex]="5000">
1+
<p-toast
2+
[baseZIndex]="5000"
3+
[key]="toastKey"
4+
position="top-right"
5+
styleClass="product-toast"
6+
>
27
<ng-template let-message pTemplate="message">
3-
<div style="display: flex; align-items: center; margin: 5px">
4-
<i class="pi pi-info-circle" style="font-size: 30px; padding: 0 10px"></i>
5-
<div>{{ message.detail }}</div>
8+
<div class="product-toast__card">
9+
<span class="product-toast__icon">
10+
<i class="pi pi-info-circle" aria-hidden="true"></i>
11+
</span>
12+
<div class="product-toast__text">
13+
{{ message.detail }}
14+
</div>
15+
<button
16+
type="button"
17+
class="product-toast__close"
18+
aria-label="Close notification"
19+
(click)="messageService.clear(toastKey)"
20+
>
21+
<i class="pi pi-times" aria-hidden="true"></i>
22+
</button>
623
</div>
724
</ng-template>
825
</p-toast>
@@ -160,20 +177,11 @@ <h3 *ngIf="title" class="heading title has-text-black">{{ title }}</h3>
160177
class="product-type-chip"
161178
*ngFor="let product of visibleProductTypes"
162179
[class.product-type-chip--active]="isProductTypeActive(product.key)"
163-
[class.product-type-chip--disabled]="product.comingSoon"
164180
(click)="onProductTypeToggle(product)"
165181
[attr.aria-pressed]="isProductTypeActive(product.key)"
166-
[disabled]="product.comingSoon"
167-
[attr.title]="product.comingSoon ? 'Coming soon' : null"
168182
>
169183
<i [ngClass]="product.icon" aria-hidden="true"></i>
170184
<span class="product-type-chip__label">{{ product.label }}</span>
171-
<span
172-
*ngIf="product.comingSoon"
173-
class="product-type-chip__tag"
174-
aria-hidden="true"
175-
>soon</span
176-
>
177185
</button>
178186
</div>
179187
</div>

angular/src/app/search-panel/search-panel.component.scss

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -335,24 +335,91 @@ body .p-highlight {
335335
opacity: 1;
336336
}
337337

338-
.product-type-chip--disabled {
339-
opacity: 0.55;
340-
cursor: not-allowed;
341-
border-style: dashed;
342-
}
343-
344338
.product-type-chip__label {
345339
white-space: nowrap;
346340
}
347341

348-
.product-type-chip__tag {
349-
background: rgba(15, 23, 42, 0.9);
342+
/* Toast styling */
343+
:host ::ng-deep .product-toast.p-toast {
344+
width: auto;
345+
max-width: none;
346+
}
347+
348+
:host ::ng-deep .product-toast .p-toast-message {
349+
padding: 0;
350+
background: transparent !important;
351+
border: none !important;
352+
border-left: none !important;
353+
box-shadow: none !important;
354+
width: auto;
355+
max-width: none;
356+
}
357+
358+
:host ::ng-deep .product-toast .p-toast-message-warn {
359+
background: transparent !important;
360+
border: none !important;
361+
border-left: none !important;
362+
box-shadow: none !important;
363+
}
364+
365+
:host ::ng-deep .product-toast .p-toast-message-content {
366+
padding: 0;
367+
border: none;
368+
background: transparent;
369+
}
370+
371+
:host ::ng-deep .product-toast .p-toast-icon-close {
372+
display: none;
373+
}
374+
375+
.product-toast__card {
376+
display: grid;
377+
grid-template-columns: auto 1fr auto;
378+
align-items: center;
379+
gap: 0.75rem;
380+
background: #0a0a0a;
350381
color: #ffffff;
351-
font-size: 0.68rem;
352-
letter-spacing: 0.05em;
353-
padding: 0.1rem 0.45rem;
382+
border-radius: 14px;
383+
border: 1px solid #1f1f1f;
384+
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
385+
padding: 0.8rem 1rem 0.8rem 0.85rem;
386+
}
387+
388+
.product-toast__icon {
389+
width: 40px;
390+
height: 40px;
391+
border-radius: 12px;
392+
display: inline-flex;
393+
align-items: center;
394+
justify-content: center;
395+
background: linear-gradient(135deg, #ffcc4d, #ffb200);
396+
color: #0a0a0a;
397+
font-size: 1.2rem;
398+
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
399+
}
400+
401+
.product-toast__text {
402+
font-size: 1rem;
403+
font-weight: 500;
404+
color: #ffffff;
405+
line-height: 1.45;
406+
}
407+
408+
.product-toast__close {
409+
background: transparent;
410+
border: none;
411+
color: #ffffff;
412+
cursor: pointer;
413+
padding: 0.2rem;
354414
border-radius: 999px;
355-
text-transform: uppercase;
415+
transition: background 0.15s ease, color 0.15s ease;
416+
}
417+
418+
.product-toast__close:hover,
419+
.product-toast__close:focus {
420+
background: rgba(255, 255, 255, 0.12);
421+
color: #ffffff;
422+
outline: none;
356423
}
357424

358425

angular/src/app/search-panel/search-panel.component.ts

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ type ProductTypeOption = {
4848
label: string;
4949
icon: string;
5050
external?: boolean;
51-
comingSoon?: boolean;
5251
description?: string;
5352
};
5453

@@ -129,14 +128,12 @@ export class SearchPanelComponent implements OnInit, OnDestroy {
129128
label: "Papers",
130129
icon: "pi pi-book",
131130
external: true,
132-
comingSoon: true,
133131
},
134132
{
135133
key: "patents",
136134
label: "Patents",
137135
icon: "pi pi-briefcase",
138136
external: true,
139-
comingSoon: true,
140137
},
141138
];
142139
private productTypesSub?: Subscription;
@@ -145,6 +142,7 @@ export class SearchPanelComponent implements OnInit, OnDestroy {
145142
"Toggle to include external products like open-source code (patents & papers coming soon). Results and filters will blend with NIST data.";
146143
externalInfoVisible: boolean = false;
147144
examplesDialogVisible = false;
145+
toastKey = "productToast";
148146
placeHolderText: string[] = [
149147
"Artificial Intelligence",
150148
"Kinetics database",
@@ -345,6 +343,22 @@ export class SearchPanelComponent implements OnInit, OnDestroy {
345343
onExternalToggle(enabled: boolean) {
346344
this.includeExternalProducts = enabled;
347345
this.searchService.setExternalProducts(enabled);
346+
if (enabled) {
347+
// Turn on all external products; keep data as-is.
348+
this.searchService.setProductTypes({
349+
code: true,
350+
papers: true,
351+
patents: true,
352+
});
353+
} else {
354+
// Disable external products and ensure data stays on.
355+
this.searchService.setProductTypes({
356+
code: false,
357+
papers: false,
358+
patents: false,
359+
data: true,
360+
});
361+
}
348362
}
349363

350364
openExternalInfo() {
@@ -356,20 +370,37 @@ export class SearchPanelComponent implements OnInit, OnDestroy {
356370
}
357371

358372
get visibleProductTypes(): ProductTypeOption[] {
359-
return this.productTypeOptions.filter(
360-
(option) => this.includeExternalProducts || !option.external
361-
);
373+
return this.productTypeOptions;
362374
}
363375

364376
isProductTypeActive(key: ProductTypeKey): boolean {
365377
return !!this.productTypes && !!this.productTypes[key];
366378
}
367379

368380
onProductTypeToggle(option: ProductTypeOption) {
369-
if (option.comingSoon) return;
370-
if (option.external && !this.includeExternalProducts) return;
381+
// If external toggle is off and user taps an external product, turn external on first.
382+
if (option.external && !this.includeExternalProducts) {
383+
this.includeExternalProducts = true;
384+
this.searchService.setExternalProducts(true);
385+
}
386+
// When external is off, data is locked on.
387+
if (!this.includeExternalProducts && option.key === "data") {
388+
return;
389+
}
371390
const nextState = !this.isProductTypeActive(option.key);
372391
this.searchService.setProductTypeEnabled(option.key, nextState);
392+
393+
// If external is on but no external products remain active, switch external off.
394+
if (this.includeExternalProducts && !this.hasActiveExternalProducts()) {
395+
this.includeExternalProducts = false;
396+
this.searchService.setExternalProducts(false);
397+
}
398+
}
399+
400+
private hasActiveExternalProducts(): boolean {
401+
const state: ProductTypeState =
402+
this.productTypes || { ...DEFAULT_PRODUCT_TYPES };
403+
return !!(state.code || state.papers || state.patents);
373404
}
374405

375406
/**
@@ -530,6 +561,17 @@ export class SearchPanelComponent implements OnInit, OnDestroy {
530561
* @param searchTaxonomyKey
531562
*/
532563
search(searchValue: string, searchTaxonomyKey: string) {
564+
const activeProducts = this.searchService.getActiveProductTypes();
565+
if (!activeProducts || activeProducts.length === 0) {
566+
this.messageService.add({
567+
severity: "warn",
568+
summary: "Select a product",
569+
detail: "Choose at least one product type before searching.",
570+
life: 3500,
571+
key: this.toastKey,
572+
});
573+
return;
574+
}
533575
this.searchTaxonomyKey = searchTaxonomyKey;
534576

535577
this.searchService.search(searchValue, this.router.url);

0 commit comments

Comments
 (0)