Added extensive component library with feedback components (empty state, loading spinner, skeleton loader), enhanced form components (autocomplete, date picker, file upload, form field, time picker), navigation components (pagination), and overlay components (backdrop, drawer, modal, overlay container). Updated demo application with comprehensive showcase components and enhanced styling throughout the project. Excluded font files from repository to reduce size. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
550 lines
18 KiB
TypeScript
550 lines
18 KiB
TypeScript
import { Component, ChangeDetectionStrategy, signal } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { FormsModule } from '@angular/forms';
|
|
import { SearchBarComponent, SearchSuggestion } from '../../../../../ui-essentials/src/lib/components/forms/search';
|
|
import {
|
|
faSearch,
|
|
faMicrophone,
|
|
faCamera,
|
|
faFilter,
|
|
faUser,
|
|
faFile,
|
|
faFolder,
|
|
faTag,
|
|
faHeart,
|
|
faStar
|
|
} from '@fortawesome/free-solid-svg-icons';
|
|
|
|
@Component({
|
|
selector: 'ui-search-demo',
|
|
standalone: true,
|
|
imports: [
|
|
CommonModule,
|
|
FormsModule,
|
|
SearchBarComponent
|
|
],
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
template: `
|
|
<div style="padding: 2rem;">
|
|
<h2>Search Bar Component Showcase</h2>
|
|
|
|
<!-- Basic Search Bars -->
|
|
<section style="margin-bottom: 3rem;">
|
|
<h3>Basic Search Bars</h3>
|
|
<div style="display: flex; flex-direction: column; gap: 1rem; margin-bottom: 2rem;">
|
|
<ui-search-bar
|
|
placeholder="Search anything..."
|
|
[(ngModel)]="basicSearch1"
|
|
(searchChange)="onSearchChange('basic-1', $event)"
|
|
(searchSubmit)="onSearchSubmit('basic-1', $event)"
|
|
/>
|
|
<ui-search-bar
|
|
placeholder="Search with label..."
|
|
label="Search"
|
|
[(ngModel)]="basicSearch2"
|
|
(searchChange)="onSearchChange('basic-2', $event)"
|
|
/>
|
|
<ui-search-bar
|
|
placeholder="Required search field..."
|
|
label="Search (Required)"
|
|
[required]="true"
|
|
[(ngModel)]="basicSearch3"
|
|
(searchChange)="onSearchChange('basic-3', $event)"
|
|
/>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Size Variants -->
|
|
<section style="margin-bottom: 3rem;">
|
|
<h3>Size Variants</h3>
|
|
<div style="display: flex; flex-direction: column; gap: 1rem; margin-bottom: 2rem;">
|
|
<ui-search-bar
|
|
placeholder="Small search..."
|
|
size="sm"
|
|
[(ngModel)]="sizeSmall"
|
|
(searchChange)="onSearchChange('size-sm', $event)"
|
|
/>
|
|
<ui-search-bar
|
|
placeholder="Medium search (default)..."
|
|
size="md"
|
|
[(ngModel)]="sizeMedium"
|
|
(searchChange)="onSearchChange('size-md', $event)"
|
|
/>
|
|
<ui-search-bar
|
|
placeholder="Large search..."
|
|
size="lg"
|
|
[(ngModel)]="sizeLarge"
|
|
(searchChange)="onSearchChange('size-lg', $event)"
|
|
/>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Variant Styles -->
|
|
<section style="margin-bottom: 3rem;">
|
|
<h3>Style Variants</h3>
|
|
<div style="display: flex; flex-direction: column; gap: 1rem; margin-bottom: 2rem;">
|
|
<ui-search-bar
|
|
placeholder="Outlined search (default)..."
|
|
variant="outlined"
|
|
[(ngModel)]="variantOutlined"
|
|
(searchChange)="onSearchChange('variant-outlined', $event)"
|
|
/>
|
|
<ui-search-bar
|
|
placeholder="Filled search..."
|
|
variant="filled"
|
|
[(ngModel)]="variantFilled"
|
|
(searchChange)="onSearchChange('variant-filled', $event)"
|
|
/>
|
|
<ui-search-bar
|
|
placeholder="Elevated search..."
|
|
variant="elevated"
|
|
[(ngModel)]="variantElevated"
|
|
(searchChange)="onSearchChange('variant-elevated', $event)"
|
|
/>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- With Icons -->
|
|
<section style="margin-bottom: 3rem;">
|
|
<h3>With Icons</h3>
|
|
<div style="display: flex; flex-direction: column; gap: 1rem; margin-bottom: 2rem;">
|
|
<ui-search-bar
|
|
placeholder="Custom leading icon..."
|
|
[leadingIcon]="faUser"
|
|
[(ngModel)]="iconSearch1"
|
|
(searchChange)="onSearchChange('icon-1', $event)"
|
|
/>
|
|
<ui-search-bar
|
|
placeholder="With trailing icons..."
|
|
[trailingIcons]="basicTrailingIcons"
|
|
[(ngModel)]="iconSearch2"
|
|
(searchChange)="onSearchChange('icon-2', $event)"
|
|
(trailingIconClick)="onTrailingIconClick($event)"
|
|
/>
|
|
<ui-search-bar
|
|
placeholder="Multiple trailing icons..."
|
|
[trailingIcons]="multipleTrailingIcons"
|
|
[(ngModel)]="iconSearch3"
|
|
(searchChange)="onSearchChange('icon-3', $event)"
|
|
(trailingIconClick)="onTrailingIconClick($event)"
|
|
/>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- With Suggestions -->
|
|
<section style="margin-bottom: 3rem;">
|
|
<h3>With Suggestions</h3>
|
|
<div style="display: flex; flex-direction: column; gap: 1rem; margin-bottom: 2rem;">
|
|
<ui-search-bar
|
|
placeholder="Search with suggestions..."
|
|
[suggestions]="suggestions"
|
|
[(ngModel)]="suggestionSearch1"
|
|
(searchChange)="onSearchChange('suggestions-1', $event)"
|
|
(suggestionSelect)="onSuggestionSelect($event)"
|
|
/>
|
|
<ui-search-bar
|
|
placeholder="Search with categorized suggestions..."
|
|
[suggestions]="categorizedSuggestions"
|
|
[(ngModel)]="suggestionSearch2"
|
|
(searchChange)="onSearchChange('suggestions-2', $event)"
|
|
(suggestionSelect)="onSuggestionSelect($event)"
|
|
/>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- States -->
|
|
<section style="margin-bottom: 3rem;">
|
|
<h3>States</h3>
|
|
<div style="display: flex; flex-direction: column; gap: 1rem; margin-bottom: 2rem;">
|
|
<ui-search-bar
|
|
placeholder="Disabled search..."
|
|
[disabled]="true"
|
|
[(ngModel)]="stateDisabled"
|
|
/>
|
|
<ui-search-bar
|
|
placeholder="Readonly search..."
|
|
[readonly]="true"
|
|
[(ngModel)]="stateReadonly"
|
|
/>
|
|
<ui-search-bar
|
|
placeholder="Error state..."
|
|
state="error"
|
|
errorMessage="Please enter a valid search term"
|
|
[(ngModel)]="stateError"
|
|
(searchChange)="onSearchChange('state-error', $event)"
|
|
/>
|
|
<ui-search-bar
|
|
placeholder="With helper text..."
|
|
helperText="Enter at least 3 characters to search"
|
|
[(ngModel)]="stateHelper"
|
|
(searchChange)="onSearchChange('state-helper', $event)"
|
|
/>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Advanced Features -->
|
|
<section style="margin-bottom: 3rem;">
|
|
<h3>Advanced Features</h3>
|
|
<div style="display: flex; flex-direction: column; gap: 1rem; margin-bottom: 2rem;">
|
|
<ui-search-bar
|
|
placeholder="Non-clearable search..."
|
|
[clearable]="false"
|
|
[(ngModel)]="advancedSearch1"
|
|
(searchChange)="onSearchChange('advanced-1', $event)"
|
|
/>
|
|
<ui-search-bar
|
|
placeholder="Auto-focus search..."
|
|
[autofocus]="true"
|
|
[(ngModel)]="advancedSearch2"
|
|
(searchChange)="onSearchChange('advanced-2', $event)"
|
|
/>
|
|
<ui-search-bar
|
|
placeholder="Search with max suggestions..."
|
|
[suggestions]="suggestions"
|
|
[maxSuggestions]="3"
|
|
[(ngModel)]="advancedSearch3"
|
|
(searchChange)="onSearchChange('advanced-3', $event)"
|
|
/>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Interactive Controls -->
|
|
<section style="margin-bottom: 3rem;">
|
|
<h3>Interactive Controls</h3>
|
|
<div style="display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem;">
|
|
<button
|
|
style="padding: 0.5rem 1rem; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer;"
|
|
(click)="clearAllSearches()"
|
|
>
|
|
Clear All Searches
|
|
</button>
|
|
<button
|
|
style="padding: 0.5rem 1rem; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer;"
|
|
(click)="fillSampleSearches()"
|
|
>
|
|
Fill Sample Searches
|
|
</button>
|
|
<button
|
|
style="padding: 0.5rem 1rem; background: #6f42c1; color: white; border: none; border-radius: 4px; cursor: pointer;"
|
|
(click)="toggleStates()"
|
|
>
|
|
{{ globalDisabled() ? 'Enable All' : 'Disable All' }}
|
|
</button>
|
|
</div>
|
|
|
|
@if (lastAction()) {
|
|
<div style="margin-top: 1rem; padding: 1rem; background: #e3f2fd; border-radius: 4px;">
|
|
<strong>Last action:</strong> {{ lastAction() }}
|
|
</div>
|
|
}
|
|
</section>
|
|
|
|
<!-- Code Examples -->
|
|
<section style="margin-bottom: 3rem;">
|
|
<h3>Code Examples</h3>
|
|
<div style="background: #f8f9fa; padding: 1rem; border-radius: 4px; margin-bottom: 1rem;">
|
|
<h4>Basic Usage:</h4>
|
|
<pre style="background: #fff; padding: 1rem; border-radius: 4px; overflow-x: auto;"><code><ui-search-bar
|
|
placeholder="Search..."
|
|
[(ngModel)]="searchValue"
|
|
(searchChange)="onSearchChange($event)"
|
|
(searchSubmit)="onSearchSubmit($event)">
|
|
</ui-search-bar></code></pre>
|
|
|
|
<h4>With Suggestions:</h4>
|
|
<pre style="background: #fff; padding: 1rem; border-radius: 4px; overflow-x: auto;"><code><ui-search-bar
|
|
placeholder="Search with suggestions..."
|
|
[suggestions]="suggestions"
|
|
[(ngModel)]="searchValue"
|
|
(suggestionSelect)="onSuggestionSelect($event)">
|
|
</ui-search-bar></code></pre>
|
|
|
|
<h4>With Icons:</h4>
|
|
<pre style="background: #fff; padding: 1rem; border-radius: 4px; overflow-x: auto;"><code><ui-search-bar
|
|
placeholder="Search with icons..."
|
|
[leadingIcon]="faSearch"
|
|
[trailingIcons]="iconButtons"
|
|
variant="filled"
|
|
size="lg"
|
|
(trailingIconClick)="onIconClick($event)">
|
|
</ui-search-bar></code></pre>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Current Values -->
|
|
<section style="margin-bottom: 3rem;">
|
|
<h3>Current Values</h3>
|
|
<div style="background: #f8f9fa; padding: 1rem; border-radius: 4px; max-height: 400px; overflow-y: auto;">
|
|
<pre>{{ getCurrentValues() }}</pre>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
`,
|
|
styles: [`
|
|
h2 {
|
|
color: hsl(279, 14%, 11%);
|
|
font-size: 2rem;
|
|
margin-bottom: 2rem;
|
|
border-bottom: 2px solid hsl(258, 100%, 47%);
|
|
padding-bottom: 0.5rem;
|
|
}
|
|
|
|
h3 {
|
|
color: hsl(279, 14%, 25%);
|
|
font-size: 1.5rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
h4 {
|
|
color: hsl(279, 14%, 35%);
|
|
font-size: 1.2rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
pre {
|
|
margin: 0;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 0.9rem;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
code {
|
|
color: hsl(279, 14%, 15%);
|
|
}
|
|
|
|
section {
|
|
border: 1px solid #e9ecef;
|
|
padding: 1.5rem;
|
|
border-radius: 8px;
|
|
background: #ffffff;
|
|
}
|
|
|
|
button:hover {
|
|
opacity: 0.9;
|
|
transform: translateY(-1px);
|
|
}
|
|
`]
|
|
})
|
|
export class SearchDemoComponent {
|
|
// FontAwesome icons
|
|
protected readonly faSearch = faSearch;
|
|
protected readonly faMicrophone = faMicrophone;
|
|
protected readonly faCamera = faCamera;
|
|
protected readonly faFilter = faFilter;
|
|
protected readonly faUser = faUser;
|
|
protected readonly faFile = faFile;
|
|
protected readonly faFolder = faFolder;
|
|
protected readonly faTag = faTag;
|
|
protected readonly faHeart = faHeart;
|
|
protected readonly faStar = faStar;
|
|
|
|
// Basic search values
|
|
basicSearch1 = signal<string>('');
|
|
basicSearch2 = signal<string>('');
|
|
basicSearch3 = signal<string>('');
|
|
|
|
// Size variants
|
|
sizeSmall = signal<string>('');
|
|
sizeMedium = signal<string>('');
|
|
sizeLarge = signal<string>('');
|
|
|
|
// Variant styles
|
|
variantOutlined = signal<string>('');
|
|
variantFilled = signal<string>('');
|
|
variantElevated = signal<string>('');
|
|
|
|
// Icon searches
|
|
iconSearch1 = signal<string>('');
|
|
iconSearch2 = signal<string>('');
|
|
iconSearch3 = signal<string>('');
|
|
|
|
// Suggestion searches
|
|
suggestionSearch1 = signal<string>('');
|
|
suggestionSearch2 = signal<string>('');
|
|
|
|
// State searches
|
|
stateDisabled = signal<string>('Disabled value');
|
|
stateReadonly = signal<string>('Readonly value');
|
|
stateError = signal<string>('');
|
|
stateHelper = signal<string>('');
|
|
|
|
// Advanced searches
|
|
advancedSearch1 = signal<string>('');
|
|
advancedSearch2 = signal<string>('');
|
|
advancedSearch3 = signal<string>('');
|
|
|
|
// Control states
|
|
globalDisabled = signal<boolean>(false);
|
|
lastAction = signal<string>('');
|
|
|
|
// Icon configurations
|
|
basicTrailingIcons = [
|
|
{
|
|
id: 'mic',
|
|
icon: faMicrophone,
|
|
label: 'Voice search',
|
|
disabled: false
|
|
}
|
|
];
|
|
|
|
multipleTrailingIcons = [
|
|
{
|
|
id: 'mic',
|
|
icon: faMicrophone,
|
|
label: 'Voice search',
|
|
disabled: false
|
|
},
|
|
{
|
|
id: 'camera',
|
|
icon: faCamera,
|
|
label: 'Image search',
|
|
disabled: false
|
|
},
|
|
{
|
|
id: 'filter',
|
|
icon: faFilter,
|
|
label: 'Filter results',
|
|
disabled: false
|
|
}
|
|
];
|
|
|
|
// Search suggestions
|
|
suggestions: SearchSuggestion[] = [
|
|
{ id: '1', text: 'Angular components', icon: faFile },
|
|
{ id: '2', text: 'TypeScript interfaces', icon: faFile },
|
|
{ id: '3', text: 'SCSS mixins', icon: faFile },
|
|
{ id: '4', text: 'Design tokens', icon: faTag },
|
|
{ id: '5', text: 'Material Design', icon: faHeart },
|
|
{ id: '6', text: 'Component library', icon: faFolder },
|
|
{ id: '7', text: 'UI patterns', icon: faStar },
|
|
{ id: '8', text: 'Accessibility guidelines', icon: faFile }
|
|
];
|
|
|
|
categorizedSuggestions: SearchSuggestion[] = [
|
|
{ id: '1', text: 'User profile', category: 'Users', icon: faUser },
|
|
{ id: '2', text: 'Project documentation', category: 'Files', icon: faFile },
|
|
{ id: '3', text: 'Design system', category: 'Files', icon: faFolder },
|
|
{ id: '4', text: 'Component library', category: 'Code', icon: faTag },
|
|
{ id: '5', text: 'UI components', category: 'Code', icon: faTag },
|
|
{ id: '6', text: 'Admin settings', category: 'Settings', icon: faFilter }
|
|
];
|
|
|
|
onSearchChange(searchId: string, value: string): void {
|
|
this.lastAction.set(`Search changed: ${searchId} = "${value}"`);
|
|
console.log(`Search ${searchId} changed:`, value);
|
|
}
|
|
|
|
onSearchSubmit(searchId: string, value: string): void {
|
|
this.lastAction.set(`Search submitted: ${searchId} = "${value}"`);
|
|
console.log(`Search ${searchId} submitted:`, value);
|
|
}
|
|
|
|
onSuggestionSelect(suggestion: SearchSuggestion): void {
|
|
this.lastAction.set(`Suggestion selected: "${suggestion.text}" (${suggestion.id})`);
|
|
console.log('Suggestion selected:', suggestion);
|
|
}
|
|
|
|
onTrailingIconClick(iconData: {id: string, icon: any}): void {
|
|
this.lastAction.set(`Icon clicked: ${iconData.id}`);
|
|
console.log('Trailing icon clicked:', iconData);
|
|
}
|
|
|
|
clearAllSearches(): void {
|
|
this.basicSearch1.set('');
|
|
this.basicSearch2.set('');
|
|
this.basicSearch3.set('');
|
|
this.sizeSmall.set('');
|
|
this.sizeMedium.set('');
|
|
this.sizeLarge.set('');
|
|
this.variantOutlined.set('');
|
|
this.variantFilled.set('');
|
|
this.variantElevated.set('');
|
|
this.iconSearch1.set('');
|
|
this.iconSearch2.set('');
|
|
this.iconSearch3.set('');
|
|
this.suggestionSearch1.set('');
|
|
this.suggestionSearch2.set('');
|
|
this.stateError.set('');
|
|
this.stateHelper.set('');
|
|
this.advancedSearch1.set('');
|
|
this.advancedSearch2.set('');
|
|
this.advancedSearch3.set('');
|
|
|
|
this.lastAction.set('All searches cleared');
|
|
}
|
|
|
|
fillSampleSearches(): void {
|
|
this.basicSearch1.set('Sample search');
|
|
this.basicSearch2.set('Labeled search');
|
|
this.basicSearch3.set('Required search');
|
|
this.sizeSmall.set('Small');
|
|
this.sizeMedium.set('Medium');
|
|
this.sizeLarge.set('Large');
|
|
this.variantOutlined.set('Outlined');
|
|
this.variantFilled.set('Filled');
|
|
this.variantElevated.set('Elevated');
|
|
this.iconSearch1.set('User search');
|
|
this.iconSearch2.set('Voice search');
|
|
this.iconSearch3.set('Multi-icon search');
|
|
this.suggestionSearch1.set('Angular');
|
|
this.suggestionSearch2.set('Design');
|
|
this.stateError.set('Error text');
|
|
this.stateHelper.set('Helper text');
|
|
this.advancedSearch1.set('Advanced');
|
|
this.advancedSearch2.set('Focused');
|
|
this.advancedSearch3.set('Limited');
|
|
|
|
this.lastAction.set('All searches filled with sample data');
|
|
}
|
|
|
|
toggleStates(): void {
|
|
this.globalDisabled.update(disabled => !disabled);
|
|
this.lastAction.set(`Global state: ${this.globalDisabled() ? 'disabled' : 'enabled'}`);
|
|
}
|
|
|
|
getCurrentValues(): string {
|
|
const values = {
|
|
basic: {
|
|
search1: this.basicSearch1(),
|
|
search2: this.basicSearch2(),
|
|
search3: this.basicSearch3()
|
|
},
|
|
sizes: {
|
|
small: this.sizeSmall(),
|
|
medium: this.sizeMedium(),
|
|
large: this.sizeLarge()
|
|
},
|
|
variants: {
|
|
outlined: this.variantOutlined(),
|
|
filled: this.variantFilled(),
|
|
elevated: this.variantElevated()
|
|
},
|
|
icons: {
|
|
icon1: this.iconSearch1(),
|
|
icon2: this.iconSearch2(),
|
|
icon3: this.iconSearch3()
|
|
},
|
|
suggestions: {
|
|
suggestion1: this.suggestionSearch1(),
|
|
suggestion2: this.suggestionSearch2()
|
|
},
|
|
states: {
|
|
disabled: this.stateDisabled(),
|
|
readonly: this.stateReadonly(),
|
|
error: this.stateError(),
|
|
helper: this.stateHelper()
|
|
},
|
|
advanced: {
|
|
advanced1: this.advancedSearch1(),
|
|
advanced2: this.advancedSearch2(),
|
|
advanced3: this.advancedSearch3()
|
|
},
|
|
controls: {
|
|
globalDisabled: this.globalDisabled(),
|
|
lastAction: this.lastAction()
|
|
}
|
|
};
|
|
|
|
return JSON.stringify(values, null, 2);
|
|
}
|
|
} |