@ticatec/uniface-element 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +273 -1
- package/README_CN.md +341 -0
- package/dist/common/MouseClickHandler.d.ts +1 -1
- package/dist/dialog/CommonDialog.svelte +6 -3
- package/dist/dialog/CommonDialog.svelte.d.ts +2 -1
- package/dist/dialog/Dialog.svelte +36 -10
- package/dist/dialog/Dialog.svelte.d.ts +4 -3
- package/dist/dialog/DialogBoard.svelte +11 -8
- package/dist/dialog/DialogWrapper.svelte +10 -0
- package/dist/dialog/DialogWrapper.svelte.d.ts +22 -0
- package/dist/dialog/Dialogs.js +1 -1
- package/dist/dialog/ModalResult.d.ts +5 -0
- package/dist/dialog/ModalResult.js +5 -0
- package/docs/buttons/cn/ActionBar.md +459 -0
- package/docs/buttons/cn/Button.md +228 -0
- package/docs/buttons/cn/IconButton.md +294 -0
- package/docs/buttons/cn/README.md +600 -0
- package/docs/buttons/cn/TextButton.md +163 -0
- package/docs/buttons/en/ActionBar.md +459 -0
- package/docs/buttons/en/Button.md +228 -0
- package/docs/buttons/en/IconButton.md +294 -0
- package/docs/buttons/en/README.md +600 -0
- package/docs/buttons/en/TextButton.md +163 -0
- package/docs/containers/cn/accordion.md +622 -0
- package/docs/containers/cn/box.md +445 -0
- package/docs/containers/cn/card.md +404 -0
- package/docs/containers/cn/page.md +576 -0
- package/docs/containers/cn/tabs.md +686 -0
- package/docs/containers/en/accordion.md +622 -0
- package/docs/containers/en/box.md +445 -0
- package/docs/containers/en/card.md +404 -0
- package/docs/containers/en/page.md +576 -0
- package/docs/containers/en/tabs.md +686 -0
- package/docs/data-display/cn/concise-data-table.md +731 -0
- package/docs/data-display/cn/listbox.md +786 -0
- package/docs/data-display/cn/treeview.md +863 -0
- package/docs/data-display/en/concise-data-table.md +732 -0
- package/docs/data-display/en/listbox.md +786 -0
- package/docs/data-display/en/treeview.md +863 -0
- package/docs/data-table/cn/README.md +596 -0
- package/docs/data-table/en/README.md +596 -0
- package/docs/dialog/cn/README.md +767 -0
- package/docs/dialog/en/README.md +767 -0
- package/docs/form/cn/README.md +847 -0
- package/docs/form/en/README.md +847 -0
- package/docs/form-components/cn/README.md +495 -0
- package/docs/form-components/cn/attachmentfilesfield.md +757 -0
- package/docs/form-components/cn/cascade-options-select.md +374 -0
- package/docs/form-components/cn/checkbox.md +58 -0
- package/docs/form-components/cn/date-picker.md +138 -0
- package/docs/form-components/cn/datetime-picker.md +228 -0
- package/docs/form-components/cn/group-checkbox.md +405 -0
- package/docs/form-components/cn/group-radiobox.md +525 -0
- package/docs/form-components/cn/imagefilesfield.md +651 -0
- package/docs/form-components/cn/input-options-select.md +250 -0
- package/docs/form-components/cn/lookup-editor.md +406 -0
- package/docs/form-components/cn/memoeditor.md +409 -0
- package/docs/form-components/cn/number-editor.md +261 -0
- package/docs/form-components/cn/options-multi-select.md +177 -0
- package/docs/form-components/cn/options-select.md +83 -0
- package/docs/form-components/cn/passwordeditor.md +538 -0
- package/docs/form-components/cn/prompts-text-editor.md +241 -0
- package/docs/form-components/cn/radiobutton.md +84 -0
- package/docs/form-components/cn/switch.md +178 -0
- package/docs/form-components/cn/text-editor.md +142 -0
- package/docs/form-components/cn/time-editor.md +159 -0
- package/docs/form-components/cn/unit-number-editor.md +164 -0
- package/docs/form-components/en/README.md +463 -0
- package/docs/form-components/en/attachmentfilesfield.md +757 -0
- package/docs/form-components/en/cascade-options-select.md +358 -0
- package/docs/form-components/en/checkbox.md +59 -0
- package/docs/form-components/en/date-picker.md +138 -0
- package/docs/form-components/en/datetime-picker.md +228 -0
- package/docs/form-components/en/group-checkbox.md +395 -0
- package/docs/form-components/en/group-radiobox.md +498 -0
- package/docs/form-components/en/imagefilesfield.md +651 -0
- package/docs/form-components/en/input-options-select.md +239 -0
- package/docs/form-components/en/lookup-editor.md +389 -0
- package/docs/form-components/en/memoeditor.md +409 -0
- package/docs/form-components/en/number-editor.md +249 -0
- package/docs/form-components/en/options-multi-select.md +177 -0
- package/docs/form-components/en/options-select.md +83 -0
- package/docs/form-components/en/passwordeditor.md +538 -0
- package/docs/form-components/en/prompts-text-editor.md +241 -0
- package/docs/form-components/en/radiobutton.md +84 -0
- package/docs/form-components/en/switch.md +180 -0
- package/docs/form-components/en/text-editor.md +142 -0
- package/docs/form-components/en/time-editor.md +159 -0
- package/docs/form-components/en/unit-number-editor.md +164 -0
- package/docs/form-layout/cn/criteria-field.md +652 -0
- package/docs/form-layout/cn/flex-form.md +454 -0
- package/docs/form-layout/cn/flex-row-form.md +542 -0
- package/docs/form-layout/cn/form-field.md +923 -0
- package/docs/form-layout/cn/grid-form.md +676 -0
- package/docs/form-layout/en/criteria-field.md +652 -0
- package/docs/form-layout/en/flex-form.md +454 -0
- package/docs/form-layout/en/flex-row-form.md +542 -0
- package/docs/form-layout/en/form-field.md +923 -0
- package/docs/form-layout/en/grid-form.md +676 -0
- package/docs/global/cn/README.md +534 -0
- package/docs/global/en/README.md +534 -0
- package/docs/layout/cn/classiclayout.md +460 -0
- package/docs/layout/cn/headerlayout.md +524 -0
- package/docs/layout/cn/sidebarlayout.md +263 -0
- package/docs/layout/en/classiclayout.md +951 -0
- package/docs/layout/en/headerlayout.md +524 -0
- package/docs/layout/en/sidebarlayout.md +262 -0
- package/docs/misc/cn/README.md +1217 -0
- package/docs/misc/en/README.md +1217 -0
- package/docs/navigation/cn/breadcrumb.md +291 -0
- package/docs/navigation/cn/navigator.md +322 -0
- package/docs/navigation/cn/navigatormenu.md +512 -0
- package/docs/navigation/cn/progressbar.md +199 -0
- package/docs/navigation/cn/progressstepbar.md +299 -0
- package/docs/navigation/en/breadcrumb.md +291 -0
- package/docs/navigation/en/navigator.md +322 -0
- package/docs/navigation/en/navigatormenu.md +512 -0
- package/docs/navigation/en/progressbar.md +199 -0
- package/docs/navigation/en/progressstepbar.md +299 -0
- package/docs/search-filters/cn/daterange.md +1156 -0
- package/docs/search-filters/cn/numberrange.md +688 -0
- package/docs/search-filters/cn/searchbox.md +1582 -0
- package/docs/search-filters/en/daterange.md +1156 -0
- package/docs/search-filters/en/numberrange.md +688 -0
- package/docs/search-filters/en/searchbox.md +1582 -0
- package/package.json +38 -5
|
@@ -0,0 +1,1582 @@
|
|
|
1
|
+
# SearchBox Component
|
|
2
|
+
|
|
3
|
+
A versatile search input component that provides a clean interface for text-based queries and filtering, featuring built-in search icons, clear functionality, and support for international text input methods.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The SearchBox component offers a polished search experience with Google Material Design icons, automatic clear functionality, and comprehensive input method support for international users. It provides a consistent interface for implementing search and filter functionality across applications, with built-in focus management and composition event handling for multi-byte character input.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import SearchBox from '@ticatec/uniface-element/SearchBox';
|
|
13
|
+
import type { OnChangeHandler } from '@ticatec/uniface-element/SearchBox';
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Basic Usage
|
|
17
|
+
|
|
18
|
+
```svelte
|
|
19
|
+
<script lang="ts">
|
|
20
|
+
import SearchBox from '@ticatec/uniface-element/SearchBox';
|
|
21
|
+
|
|
22
|
+
let searchQuery: string | null = null;
|
|
23
|
+
|
|
24
|
+
function handleSearch(query: string | null) {
|
|
25
|
+
console.log('Search query:', query);
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<SearchBox
|
|
30
|
+
bind:value={searchQuery}
|
|
31
|
+
onChange={handleSearch}
|
|
32
|
+
placeholder="Search..."
|
|
33
|
+
/>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Properties
|
|
37
|
+
|
|
38
|
+
| Property | Type | Default | Description |
|
|
39
|
+
|----------|------|---------|-------------|
|
|
40
|
+
| `variant` | `'' \| 'plain' \| 'outlined' \| 'filled'` | `''` | Visual styling variant |
|
|
41
|
+
| `compact` | `boolean` | `false` | Enable compact layout for dense forms |
|
|
42
|
+
| `style` | `string` | `''` | Custom inline styles |
|
|
43
|
+
| `value` | `string \| null` | `null` | Current search text value (bindable) |
|
|
44
|
+
| `disabled` | `boolean` | `false` | Disable input interaction |
|
|
45
|
+
| `onChange` | `OnChangeHandler<string \| null>` | `null` | Change event handler |
|
|
46
|
+
| `placeholder` | `string \| null` | `null` | Placeholder text |
|
|
47
|
+
|
|
48
|
+
## Methods
|
|
49
|
+
|
|
50
|
+
| Method | Description |
|
|
51
|
+
|--------|-------------|
|
|
52
|
+
| `focus()` | Programmatically focus the search input |
|
|
53
|
+
|
|
54
|
+
## Type Definitions
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
/**
|
|
58
|
+
* Change event handler type
|
|
59
|
+
*/
|
|
60
|
+
type OnChangeHandler<T> = (value: T) => void;
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Features
|
|
64
|
+
|
|
65
|
+
- **Built-in Search Icon**: Google Material Design search icon for clear visual indication
|
|
66
|
+
- **Auto-Clear Functionality**: Clear button appears when text is entered
|
|
67
|
+
- **International Support**: Input method composition support for multi-byte characters
|
|
68
|
+
- **Focus Management**: Programmatic focus control for better UX
|
|
69
|
+
- **Consistent Styling**: Matches other form components with variant support
|
|
70
|
+
- **Standard Input Behavior**: Standard HTML input functionality with basic keyboard support
|
|
71
|
+
|
|
72
|
+
## Usage Examples
|
|
73
|
+
|
|
74
|
+
### Product Search Interface
|
|
75
|
+
|
|
76
|
+
```svelte
|
|
77
|
+
<script lang="ts">
|
|
78
|
+
import SearchBox from '@ticatec/uniface-element/SearchBox';
|
|
79
|
+
|
|
80
|
+
let searchQuery: string | null = null;
|
|
81
|
+
|
|
82
|
+
// Mock product data
|
|
83
|
+
let allProducts = [
|
|
84
|
+
{ id: 1, name: 'Wireless Headphones', category: 'Electronics', price: 129.99, brand: 'AudioTech' },
|
|
85
|
+
{ id: 2, name: 'Smart Watch', category: 'Electronics', price: 299.99, brand: 'TechWear' },
|
|
86
|
+
{ id: 3, name: 'Laptop Stand', category: 'Accessories', price: 49.99, brand: 'DeskPro' },
|
|
87
|
+
{ id: 4, name: 'USB-C Cable', category: 'Accessories', price: 19.99, brand: 'ConnectPlus' },
|
|
88
|
+
{ id: 5, name: 'Bluetooth Speaker', category: 'Electronics', price: 79.99, brand: 'SoundWave' },
|
|
89
|
+
{ id: 6, name: 'Phone Case', category: 'Accessories', price: 24.99, brand: 'ProtectAll' },
|
|
90
|
+
{ id: 7, name: 'Wireless Charger', category: 'Electronics', price: 34.99, brand: 'PowerUp' }
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
let filteredProducts = [];
|
|
94
|
+
let isSearching = false;
|
|
95
|
+
|
|
96
|
+
// Debounced search function
|
|
97
|
+
let searchTimeout: number;
|
|
98
|
+
|
|
99
|
+
function performSearch(query: string | null) {
|
|
100
|
+
clearTimeout(searchTimeout);
|
|
101
|
+
isSearching = true;
|
|
102
|
+
|
|
103
|
+
searchTimeout = setTimeout(() => {
|
|
104
|
+
if (!query || query.trim() === '') {
|
|
105
|
+
filteredProducts = allProducts;
|
|
106
|
+
} else {
|
|
107
|
+
const searchTerm = query.toLowerCase().trim();
|
|
108
|
+
filteredProducts = allProducts.filter(product =>
|
|
109
|
+
product.name.toLowerCase().includes(searchTerm) ||
|
|
110
|
+
product.category.toLowerCase().includes(searchTerm) ||
|
|
111
|
+
product.brand.toLowerCase().includes(searchTerm)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
isSearching = false;
|
|
115
|
+
}, 300); // 300ms debounce delay
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Initial load
|
|
119
|
+
filteredProducts = allProducts;
|
|
120
|
+
|
|
121
|
+
// Reactive search
|
|
122
|
+
$: performSearch(searchQuery);
|
|
123
|
+
|
|
124
|
+
function clearSearch() {
|
|
125
|
+
searchQuery = null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function handleProductClick(product) {
|
|
129
|
+
console.log('Selected product:', product);
|
|
130
|
+
alert(`Selected: ${product.name} - $${product.price}`);
|
|
131
|
+
}
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
<div class="product-search">
|
|
135
|
+
<header class="search-header">
|
|
136
|
+
<h2>Product Catalog</h2>
|
|
137
|
+
<p>Search through our product inventory</p>
|
|
138
|
+
</header>
|
|
139
|
+
|
|
140
|
+
<div class="search-controls">
|
|
141
|
+
<SearchBox
|
|
142
|
+
bind:value={searchQuery}
|
|
143
|
+
placeholder="Search products, categories, or brands..."
|
|
144
|
+
variant="outlined"
|
|
145
|
+
style="width: 100%; max-width: 500px;"
|
|
146
|
+
/>
|
|
147
|
+
|
|
148
|
+
{#if searchQuery}
|
|
149
|
+
<div class="search-info">
|
|
150
|
+
<span class="search-results">
|
|
151
|
+
{isSearching ? 'Searching...' : `${filteredProducts.length} products found`}
|
|
152
|
+
</span>
|
|
153
|
+
<button class="clear-search-btn" on:click={clearSearch}>
|
|
154
|
+
Clear Search
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
{/if}
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<div class="products-grid">
|
|
161
|
+
{#if isSearching}
|
|
162
|
+
<div class="loading-state">
|
|
163
|
+
<div class="loading-spinner">🔍</div>
|
|
164
|
+
<p>Searching products...</p>
|
|
165
|
+
</div>
|
|
166
|
+
{:else if filteredProducts.length > 0}
|
|
167
|
+
{#each filteredProducts as product}
|
|
168
|
+
<div
|
|
169
|
+
class="product-card"
|
|
170
|
+
on:click={() => handleProductClick(product)}
|
|
171
|
+
tabindex="0"
|
|
172
|
+
role="button"
|
|
173
|
+
>
|
|
174
|
+
<div class="product-header">
|
|
175
|
+
<h3 class="product-name">{product.name}</h3>
|
|
176
|
+
<span class="product-price">${product.price}</span>
|
|
177
|
+
</div>
|
|
178
|
+
<div class="product-details">
|
|
179
|
+
<span class="product-category">{product.category}</span>
|
|
180
|
+
<span class="product-brand">{product.brand}</span>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
{/each}
|
|
184
|
+
{:else}
|
|
185
|
+
<div class="no-results">
|
|
186
|
+
<div class="no-results-icon">🔍</div>
|
|
187
|
+
<h3>No products found</h3>
|
|
188
|
+
<p>We couldn't find any products matching "{searchQuery}"</p>
|
|
189
|
+
<button class="reset-search-btn" on:click={clearSearch}>
|
|
190
|
+
View All Products
|
|
191
|
+
</button>
|
|
192
|
+
</div>
|
|
193
|
+
{/if}
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<style>
|
|
198
|
+
.product-search {
|
|
199
|
+
max-width: 1200px;
|
|
200
|
+
margin: 0 auto;
|
|
201
|
+
padding: 20px;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.search-header {
|
|
205
|
+
text-align: center;
|
|
206
|
+
margin-bottom: 30px;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.search-header h2 {
|
|
210
|
+
color: #333;
|
|
211
|
+
margin: 0 0 8px 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.search-header p {
|
|
215
|
+
color: #666;
|
|
216
|
+
margin: 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.search-controls {
|
|
220
|
+
display: flex;
|
|
221
|
+
flex-direction: column;
|
|
222
|
+
align-items: center;
|
|
223
|
+
gap: 16px;
|
|
224
|
+
margin-bottom: 30px;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.search-info {
|
|
228
|
+
display: flex;
|
|
229
|
+
align-items: center;
|
|
230
|
+
gap: 16px;
|
|
231
|
+
font-size: 0.9rem;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.search-results {
|
|
235
|
+
color: #666;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.clear-search-btn {
|
|
239
|
+
padding: 4px 8px;
|
|
240
|
+
background: #f8f9fa;
|
|
241
|
+
border: 1px solid #dee2e6;
|
|
242
|
+
border-radius: 4px;
|
|
243
|
+
cursor: pointer;
|
|
244
|
+
font-size: 0.8rem;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.clear-search-btn:hover {
|
|
248
|
+
background: #e9ecef;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.products-grid {
|
|
252
|
+
display: grid;
|
|
253
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
254
|
+
gap: 20px;
|
|
255
|
+
min-height: 200px;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.product-card {
|
|
259
|
+
background: white;
|
|
260
|
+
border: 1px solid #e9ecef;
|
|
261
|
+
border-radius: 8px;
|
|
262
|
+
padding: 20px;
|
|
263
|
+
cursor: pointer;
|
|
264
|
+
transition: all 0.2s;
|
|
265
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.product-card:hover,
|
|
269
|
+
.product-card:focus {
|
|
270
|
+
border-color: #007bff;
|
|
271
|
+
transform: translateY(-2px);
|
|
272
|
+
box-shadow: 0 4px 8px rgba(0,123,255,0.15);
|
|
273
|
+
outline: none;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.product-header {
|
|
277
|
+
display: flex;
|
|
278
|
+
justify-content: space-between;
|
|
279
|
+
align-items: flex-start;
|
|
280
|
+
margin-bottom: 12px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.product-name {
|
|
284
|
+
margin: 0;
|
|
285
|
+
color: #333;
|
|
286
|
+
font-size: 1.2rem;
|
|
287
|
+
font-weight: 600;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.product-price {
|
|
291
|
+
color: #007bff;
|
|
292
|
+
font-weight: 700;
|
|
293
|
+
font-size: 1.1rem;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.product-details {
|
|
297
|
+
display: flex;
|
|
298
|
+
justify-content: space-between;
|
|
299
|
+
align-items: center;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.product-category {
|
|
303
|
+
background: #e9ecef;
|
|
304
|
+
color: #495057;
|
|
305
|
+
padding: 4px 8px;
|
|
306
|
+
border-radius: 12px;
|
|
307
|
+
font-size: 0.75rem;
|
|
308
|
+
font-weight: 500;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.product-brand {
|
|
312
|
+
color: #666;
|
|
313
|
+
font-size: 0.9rem;
|
|
314
|
+
font-style: italic;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.loading-state,
|
|
318
|
+
.no-results {
|
|
319
|
+
grid-column: 1 / -1;
|
|
320
|
+
text-align: center;
|
|
321
|
+
padding: 60px 20px;
|
|
322
|
+
color: #666;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.loading-spinner {
|
|
326
|
+
font-size: 3rem;
|
|
327
|
+
animation: pulse 1.5s infinite;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.no-results-icon {
|
|
331
|
+
font-size: 4rem;
|
|
332
|
+
opacity: 0.5;
|
|
333
|
+
margin-bottom: 16px;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.no-results h3 {
|
|
337
|
+
color: #333;
|
|
338
|
+
margin: 0 0 8px 0;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.no-results p {
|
|
342
|
+
margin: 0 0 20px 0;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.reset-search-btn {
|
|
346
|
+
padding: 8px 16px;
|
|
347
|
+
background: #007bff;
|
|
348
|
+
color: white;
|
|
349
|
+
border: none;
|
|
350
|
+
border-radius: 4px;
|
|
351
|
+
cursor: pointer;
|
|
352
|
+
font-size: 0.9rem;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.reset-search-btn:hover {
|
|
356
|
+
background: #0056b3;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
@keyframes pulse {
|
|
360
|
+
0%, 100% { opacity: 1; }
|
|
361
|
+
50% { opacity: 0.5; }
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
@media (max-width: 768px) {
|
|
365
|
+
.products-grid {
|
|
366
|
+
grid-template-columns: 1fr;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.product-header {
|
|
370
|
+
flex-direction: column;
|
|
371
|
+
align-items: flex-start;
|
|
372
|
+
gap: 8px;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.product-details {
|
|
376
|
+
flex-direction: column;
|
|
377
|
+
align-items: flex-start;
|
|
378
|
+
gap: 8px;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
</style>
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### User Directory Search
|
|
385
|
+
|
|
386
|
+
```svelte
|
|
387
|
+
<script lang="ts">
|
|
388
|
+
import SearchBox from '@ticatec/uniface-element/SearchBox';
|
|
389
|
+
|
|
390
|
+
let searchQuery: string | null = null;
|
|
391
|
+
let searchBoxRef: any;
|
|
392
|
+
|
|
393
|
+
// Mock user data
|
|
394
|
+
let allUsers = [
|
|
395
|
+
{ id: 1, name: 'Alice Johnson', email: 'alice.johnson@company.com', department: 'Engineering', role: 'Senior Developer', status: 'Active' },
|
|
396
|
+
{ id: 2, name: 'Bob Smith', email: 'bob.smith@company.com', department: 'Marketing', role: 'Marketing Manager', status: 'Active' },
|
|
397
|
+
{ id: 3, name: 'Carol Davis', email: 'carol.davis@company.com', department: 'Sales', role: 'Sales Representative', status: 'On Leave' },
|
|
398
|
+
{ id: 4, name: 'David Wilson', email: 'david.wilson@company.com', department: 'HR', role: 'HR Specialist', status: 'Active' },
|
|
399
|
+
{ id: 5, name: 'Emma Brown', email: 'emma.brown@company.com', department: 'Engineering', role: 'Tech Lead', status: 'Remote' },
|
|
400
|
+
{ id: 6, name: 'Frank Miller', email: 'frank.miller@company.com', department: 'Finance', role: 'Accountant', status: 'Active' }
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
let filteredUsers = [];
|
|
404
|
+
let selectedUser = null;
|
|
405
|
+
let searchMode = 'all'; // 'all', 'name', 'email', 'department'
|
|
406
|
+
|
|
407
|
+
function performUserSearch(query: string | null) {
|
|
408
|
+
if (!query || query.trim() === '') {
|
|
409
|
+
filteredUsers = allUsers;
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const searchTerm = query.toLowerCase().trim();
|
|
414
|
+
filteredUsers = allUsers.filter(user => {
|
|
415
|
+
switch (searchMode) {
|
|
416
|
+
case 'name':
|
|
417
|
+
return user.name.toLowerCase().includes(searchTerm);
|
|
418
|
+
case 'email':
|
|
419
|
+
return user.email.toLowerCase().includes(searchTerm);
|
|
420
|
+
case 'department':
|
|
421
|
+
return user.department.toLowerCase().includes(searchTerm);
|
|
422
|
+
default: // 'all'
|
|
423
|
+
return user.name.toLowerCase().includes(searchTerm) ||
|
|
424
|
+
user.email.toLowerCase().includes(searchTerm) ||
|
|
425
|
+
user.department.toLowerCase().includes(searchTerm) ||
|
|
426
|
+
user.role.toLowerCase().includes(searchTerm);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Initial load
|
|
432
|
+
filteredUsers = allUsers;
|
|
433
|
+
|
|
434
|
+
// Reactive search
|
|
435
|
+
$: performUserSearch(searchQuery);
|
|
436
|
+
|
|
437
|
+
function selectUser(user) {
|
|
438
|
+
selectedUser = user;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function focusSearch() {
|
|
442
|
+
searchBoxRef?.focus();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function resetSearch() {
|
|
446
|
+
searchQuery = null;
|
|
447
|
+
selectedUser = null;
|
|
448
|
+
searchMode = 'all';
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Keyboard shortcut for search focus
|
|
452
|
+
function handleKeydown(event) {
|
|
453
|
+
if (event.ctrlKey && event.key === 'k') {
|
|
454
|
+
event.preventDefault();
|
|
455
|
+
focusSearch();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
</script>
|
|
459
|
+
|
|
460
|
+
<svelte:window on:keydown={handleKeydown} />
|
|
461
|
+
|
|
462
|
+
<div class="user-directory">
|
|
463
|
+
<header class="directory-header">
|
|
464
|
+
<h2>Employee Directory</h2>
|
|
465
|
+
<p>Search and browse company employees</p>
|
|
466
|
+
<div class="keyboard-hint">
|
|
467
|
+
<kbd>Ctrl</kbd> + <kbd>K</kbd> to focus search
|
|
468
|
+
</div>
|
|
469
|
+
</header>
|
|
470
|
+
|
|
471
|
+
<div class="search-section">
|
|
472
|
+
<div class="search-input-container">
|
|
473
|
+
<SearchBox
|
|
474
|
+
bind:this={searchBoxRef}
|
|
475
|
+
bind:value={searchQuery}
|
|
476
|
+
placeholder="Search employees..."
|
|
477
|
+
variant="outlined"
|
|
478
|
+
style="width: 100%;"
|
|
479
|
+
/>
|
|
480
|
+
</div>
|
|
481
|
+
|
|
482
|
+
<div class="search-options">
|
|
483
|
+
<label>Search in:</label>
|
|
484
|
+
<select bind:value={searchMode} class="search-mode-select">
|
|
485
|
+
<option value="all">All fields</option>
|
|
486
|
+
<option value="name">Name only</option>
|
|
487
|
+
<option value="email">Email only</option>
|
|
488
|
+
<option value="department">Department only</option>
|
|
489
|
+
</select>
|
|
490
|
+
|
|
491
|
+
{#if searchQuery || selectedUser}
|
|
492
|
+
<button class="reset-btn" on:click={resetSearch}>Reset</button>
|
|
493
|
+
{/if}
|
|
494
|
+
</div>
|
|
495
|
+
</div>
|
|
496
|
+
|
|
497
|
+
<div class="directory-content">
|
|
498
|
+
<div class="users-list">
|
|
499
|
+
<div class="list-header">
|
|
500
|
+
<h3>Search Results</h3>
|
|
501
|
+
<span class="result-count">{filteredUsers.length} user{filteredUsers.length === 1 ? '' : 's'}</span>
|
|
502
|
+
</div>
|
|
503
|
+
|
|
504
|
+
<div class="users-grid">
|
|
505
|
+
{#each filteredUsers as user}
|
|
506
|
+
<div
|
|
507
|
+
class="user-card"
|
|
508
|
+
class:selected={selectedUser?.id === user.id}
|
|
509
|
+
on:click={() => selectUser(user)}
|
|
510
|
+
tabindex="0"
|
|
511
|
+
role="button"
|
|
512
|
+
>
|
|
513
|
+
<div class="user-avatar">
|
|
514
|
+
{user.name.split(' ').map(n => n[0]).join('')}
|
|
515
|
+
</div>
|
|
516
|
+
<div class="user-info">
|
|
517
|
+
<div class="user-name">{user.name}</div>
|
|
518
|
+
<div class="user-email">{user.email}</div>
|
|
519
|
+
<div class="user-role">{user.role}</div>
|
|
520
|
+
<div class="user-department">{user.department}</div>
|
|
521
|
+
</div>
|
|
522
|
+
<div class="user-status">
|
|
523
|
+
<span class="status-badge status-{user.status.toLowerCase().replace(' ', '-')}">{user.status}</span>
|
|
524
|
+
</div>
|
|
525
|
+
</div>
|
|
526
|
+
{/each}
|
|
527
|
+
|
|
528
|
+
{#if filteredUsers.length === 0}
|
|
529
|
+
<div class="no-users-found">
|
|
530
|
+
<h4>No employees found</h4>
|
|
531
|
+
<p>
|
|
532
|
+
{#if searchQuery}
|
|
533
|
+
No employees match your search for "{searchQuery}"
|
|
534
|
+
{:else}
|
|
535
|
+
No employees in the directory
|
|
536
|
+
{/if}
|
|
537
|
+
</p>
|
|
538
|
+
{#if searchQuery}
|
|
539
|
+
<button class="clear-search-btn" on:click={() => searchQuery = null}>Clear Search</button>
|
|
540
|
+
{/if}
|
|
541
|
+
</div>
|
|
542
|
+
{/if}
|
|
543
|
+
</div>
|
|
544
|
+
</div>
|
|
545
|
+
|
|
546
|
+
{#if selectedUser}
|
|
547
|
+
<div class="user-details">
|
|
548
|
+
<div class="details-header">
|
|
549
|
+
<h3>Employee Details</h3>
|
|
550
|
+
<button class="close-details-btn" on:click={() => selectedUser = null}>×</button>
|
|
551
|
+
</div>
|
|
552
|
+
|
|
553
|
+
<div class="details-content">
|
|
554
|
+
<div class="detail-avatar">
|
|
555
|
+
{selectedUser.name.split(' ').map(n => n[0]).join('')}
|
|
556
|
+
</div>
|
|
557
|
+
|
|
558
|
+
<div class="detail-info">
|
|
559
|
+
<h4>{selectedUser.name}</h4>
|
|
560
|
+
<div class="detail-row">
|
|
561
|
+
<span class="detail-label">Email:</span>
|
|
562
|
+
<span class="detail-value">
|
|
563
|
+
<a href="mailto:{selectedUser.email}">{selectedUser.email}</a>
|
|
564
|
+
</span>
|
|
565
|
+
</div>
|
|
566
|
+
<div class="detail-row">
|
|
567
|
+
<span class="detail-label">Role:</span>
|
|
568
|
+
<span class="detail-value">{selectedUser.role}</span>
|
|
569
|
+
</div>
|
|
570
|
+
<div class="detail-row">
|
|
571
|
+
<span class="detail-label">Department:</span>
|
|
572
|
+
<span class="detail-value">{selectedUser.department}</span>
|
|
573
|
+
</div>
|
|
574
|
+
<div class="detail-row">
|
|
575
|
+
<span class="detail-label">Status:</span>
|
|
576
|
+
<span class="detail-value">
|
|
577
|
+
<span class="status-badge status-{selectedUser.status.toLowerCase().replace(' ', '-')}">{selectedUser.status}</span>
|
|
578
|
+
</span>
|
|
579
|
+
</div>
|
|
580
|
+
</div>
|
|
581
|
+
|
|
582
|
+
<div class="detail-actions">
|
|
583
|
+
<button class="action-btn primary">Send Message</button>
|
|
584
|
+
<button class="action-btn secondary">View Profile</button>
|
|
585
|
+
<button class="action-btn secondary">Schedule Meeting</button>
|
|
586
|
+
</div>
|
|
587
|
+
</div>
|
|
588
|
+
</div>
|
|
589
|
+
{/if}
|
|
590
|
+
</div>
|
|
591
|
+
</div>
|
|
592
|
+
|
|
593
|
+
<style>
|
|
594
|
+
.user-directory {
|
|
595
|
+
max-width: 1400px;
|
|
596
|
+
margin: 0 auto;
|
|
597
|
+
padding: 20px;
|
|
598
|
+
height: 100vh;
|
|
599
|
+
display: flex;
|
|
600
|
+
flex-direction: column;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.directory-header {
|
|
604
|
+
text-align: center;
|
|
605
|
+
margin-bottom: 30px;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.directory-header h2 {
|
|
609
|
+
color: #333;
|
|
610
|
+
margin: 0 0 8px 0;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.directory-header p {
|
|
614
|
+
color: #666;
|
|
615
|
+
margin: 0 0 12px 0;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.keyboard-hint {
|
|
619
|
+
font-size: 0.8rem;
|
|
620
|
+
color: #888;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.keyboard-hint kbd {
|
|
624
|
+
background: #f8f9fa;
|
|
625
|
+
border: 1px solid #dee2e6;
|
|
626
|
+
border-radius: 3px;
|
|
627
|
+
padding: 2px 6px;
|
|
628
|
+
font-size: 0.7rem;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.search-section {
|
|
632
|
+
margin-bottom: 30px;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
.search-input-container {
|
|
636
|
+
max-width: 600px;
|
|
637
|
+
margin: 0 auto 16px;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.search-options {
|
|
641
|
+
display: flex;
|
|
642
|
+
align-items: center;
|
|
643
|
+
justify-content: center;
|
|
644
|
+
gap: 16px;
|
|
645
|
+
font-size: 0.9rem;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.search-mode-select {
|
|
649
|
+
padding: 4px 8px;
|
|
650
|
+
border: 1px solid #dee2e6;
|
|
651
|
+
border-radius: 4px;
|
|
652
|
+
background: white;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.reset-btn {
|
|
656
|
+
padding: 4px 12px;
|
|
657
|
+
background: #6c757d;
|
|
658
|
+
color: white;
|
|
659
|
+
border: none;
|
|
660
|
+
border-radius: 4px;
|
|
661
|
+
cursor: pointer;
|
|
662
|
+
font-size: 0.8rem;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.reset-btn:hover {
|
|
666
|
+
background: #5a6268;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.directory-content {
|
|
670
|
+
flex: 1;
|
|
671
|
+
display: flex;
|
|
672
|
+
gap: 20px;
|
|
673
|
+
min-height: 0;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.users-list {
|
|
677
|
+
flex: 1;
|
|
678
|
+
display: flex;
|
|
679
|
+
flex-direction: column;
|
|
680
|
+
min-height: 0;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.list-header {
|
|
684
|
+
display: flex;
|
|
685
|
+
justify-content: space-between;
|
|
686
|
+
align-items: center;
|
|
687
|
+
margin-bottom: 16px;
|
|
688
|
+
padding-bottom: 8px;
|
|
689
|
+
border-bottom: 2px solid #e9ecef;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
.list-header h3 {
|
|
693
|
+
margin: 0;
|
|
694
|
+
color: #333;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.result-count {
|
|
698
|
+
color: #666;
|
|
699
|
+
font-size: 0.9rem;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.users-grid {
|
|
703
|
+
flex: 1;
|
|
704
|
+
overflow-y: auto;
|
|
705
|
+
display: grid;
|
|
706
|
+
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
|
707
|
+
gap: 16px;
|
|
708
|
+
padding-right: 8px;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.user-card {
|
|
712
|
+
background: white;
|
|
713
|
+
border: 2px solid #e9ecef;
|
|
714
|
+
border-radius: 8px;
|
|
715
|
+
padding: 16px;
|
|
716
|
+
cursor: pointer;
|
|
717
|
+
transition: all 0.2s;
|
|
718
|
+
display: flex;
|
|
719
|
+
align-items: center;
|
|
720
|
+
gap: 16px;
|
|
721
|
+
height: fit-content;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.user-card:hover,
|
|
725
|
+
.user-card:focus {
|
|
726
|
+
border-color: #007bff;
|
|
727
|
+
box-shadow: 0 2px 4px rgba(0,123,255,0.15);
|
|
728
|
+
outline: none;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
.user-card.selected {
|
|
732
|
+
border-color: #007bff;
|
|
733
|
+
background: #f0f8ff;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
.user-avatar {
|
|
737
|
+
width: 48px;
|
|
738
|
+
height: 48px;
|
|
739
|
+
border-radius: 50%;
|
|
740
|
+
background: #007bff;
|
|
741
|
+
color: white;
|
|
742
|
+
display: flex;
|
|
743
|
+
align-items: center;
|
|
744
|
+
justify-content: center;
|
|
745
|
+
font-weight: 600;
|
|
746
|
+
font-size: 1.1rem;
|
|
747
|
+
flex-shrink: 0;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
.user-info {
|
|
751
|
+
flex: 1;
|
|
752
|
+
min-width: 0;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
.user-name {
|
|
756
|
+
font-weight: 600;
|
|
757
|
+
color: #333;
|
|
758
|
+
margin-bottom: 4px;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
.user-email {
|
|
762
|
+
color: #666;
|
|
763
|
+
font-size: 0.9rem;
|
|
764
|
+
margin-bottom: 4px;
|
|
765
|
+
overflow: hidden;
|
|
766
|
+
text-overflow: ellipsis;
|
|
767
|
+
white-space: nowrap;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.user-role {
|
|
771
|
+
color: #495057;
|
|
772
|
+
font-size: 0.85rem;
|
|
773
|
+
font-weight: 500;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
.user-department {
|
|
777
|
+
color: #6c757d;
|
|
778
|
+
font-size: 0.8rem;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
.status-badge {
|
|
782
|
+
padding: 3px 8px;
|
|
783
|
+
border-radius: 12px;
|
|
784
|
+
font-size: 0.7rem;
|
|
785
|
+
font-weight: 500;
|
|
786
|
+
text-transform: uppercase;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
.status-active {
|
|
790
|
+
background: #d4edda;
|
|
791
|
+
color: #155724;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
.status-remote {
|
|
795
|
+
background: #cce5ff;
|
|
796
|
+
color: #004085;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
.status-on-leave {
|
|
800
|
+
background: #fff3cd;
|
|
801
|
+
color: #856404;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
.no-users-found {
|
|
805
|
+
grid-column: 1 / -1;
|
|
806
|
+
text-align: center;
|
|
807
|
+
padding: 60px 20px;
|
|
808
|
+
color: #666;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
.no-users-found h4 {
|
|
812
|
+
color: #333;
|
|
813
|
+
margin: 0 0 8px 0;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.clear-search-btn {
|
|
817
|
+
margin-top: 16px;
|
|
818
|
+
padding: 8px 16px;
|
|
819
|
+
background: #007bff;
|
|
820
|
+
color: white;
|
|
821
|
+
border: none;
|
|
822
|
+
border-radius: 4px;
|
|
823
|
+
cursor: pointer;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
.clear-search-btn:hover {
|
|
827
|
+
background: #0056b3;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
.user-details {
|
|
831
|
+
width: 400px;
|
|
832
|
+
background: white;
|
|
833
|
+
border-radius: 8px;
|
|
834
|
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
835
|
+
display: flex;
|
|
836
|
+
flex-direction: column;
|
|
837
|
+
max-height: 600px;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.details-header {
|
|
841
|
+
display: flex;
|
|
842
|
+
justify-content: space-between;
|
|
843
|
+
align-items: center;
|
|
844
|
+
padding: 20px 20px 0;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.details-header h3 {
|
|
848
|
+
margin: 0;
|
|
849
|
+
color: #333;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.close-details-btn {
|
|
853
|
+
background: none;
|
|
854
|
+
border: none;
|
|
855
|
+
font-size: 24px;
|
|
856
|
+
cursor: pointer;
|
|
857
|
+
color: #666;
|
|
858
|
+
padding: 0;
|
|
859
|
+
width: 30px;
|
|
860
|
+
height: 30px;
|
|
861
|
+
display: flex;
|
|
862
|
+
align-items: center;
|
|
863
|
+
justify-content: center;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
.close-details-btn:hover {
|
|
867
|
+
color: #333;
|
|
868
|
+
background: #f8f9fa;
|
|
869
|
+
border-radius: 50%;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
.details-content {
|
|
873
|
+
padding: 20px;
|
|
874
|
+
flex: 1;
|
|
875
|
+
overflow-y: auto;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
.detail-avatar {
|
|
879
|
+
width: 80px;
|
|
880
|
+
height: 80px;
|
|
881
|
+
border-radius: 50%;
|
|
882
|
+
background: #007bff;
|
|
883
|
+
color: white;
|
|
884
|
+
display: flex;
|
|
885
|
+
align-items: center;
|
|
886
|
+
justify-content: center;
|
|
887
|
+
font-weight: 600;
|
|
888
|
+
font-size: 1.8rem;
|
|
889
|
+
margin: 0 auto 20px;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
.detail-info h4 {
|
|
893
|
+
text-align: center;
|
|
894
|
+
color: #333;
|
|
895
|
+
margin: 0 0 20px 0;
|
|
896
|
+
font-size: 1.3rem;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
.detail-row {
|
|
900
|
+
display: flex;
|
|
901
|
+
justify-content: space-between;
|
|
902
|
+
align-items: center;
|
|
903
|
+
padding: 12px 0;
|
|
904
|
+
border-bottom: 1px solid #f0f0f0;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
.detail-label {
|
|
908
|
+
font-weight: 500;
|
|
909
|
+
color: #555;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
.detail-value a {
|
|
913
|
+
color: #007bff;
|
|
914
|
+
text-decoration: none;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
.detail-value a:hover {
|
|
918
|
+
text-decoration: underline;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
.detail-actions {
|
|
922
|
+
margin-top: 20px;
|
|
923
|
+
display: flex;
|
|
924
|
+
flex-direction: column;
|
|
925
|
+
gap: 8px;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
.action-btn {
|
|
929
|
+
padding: 8px 16px;
|
|
930
|
+
border: none;
|
|
931
|
+
border-radius: 4px;
|
|
932
|
+
cursor: pointer;
|
|
933
|
+
font-size: 0.9rem;
|
|
934
|
+
font-weight: 500;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
.action-btn.primary {
|
|
938
|
+
background: #007bff;
|
|
939
|
+
color: white;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
.action-btn.primary:hover {
|
|
943
|
+
background: #0056b3;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
.action-btn.secondary {
|
|
947
|
+
background: #f8f9fa;
|
|
948
|
+
color: #495057;
|
|
949
|
+
border: 1px solid #dee2e6;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
.action-btn.secondary:hover {
|
|
953
|
+
background: #e9ecef;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
@media (max-width: 768px) {
|
|
957
|
+
.directory-content {
|
|
958
|
+
flex-direction: column;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
.user-details {
|
|
962
|
+
width: 100%;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
.users-grid {
|
|
966
|
+
grid-template-columns: 1fr;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
.search-options {
|
|
970
|
+
flex-direction: column;
|
|
971
|
+
gap: 8px;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
</style>
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
### Advanced Filtering with Multiple Search Boxes
|
|
978
|
+
|
|
979
|
+
```svelte
|
|
980
|
+
<script lang="ts">
|
|
981
|
+
import SearchBox from '@ticatec/uniface-element/SearchBox';
|
|
982
|
+
|
|
983
|
+
// Multiple search criteria
|
|
984
|
+
let titleSearch: string | null = null;
|
|
985
|
+
let authorSearch: string | null = null;
|
|
986
|
+
let tagSearch: string | null = null;
|
|
987
|
+
|
|
988
|
+
// Mock article data
|
|
989
|
+
let allArticles = [
|
|
990
|
+
{
|
|
991
|
+
id: 1,
|
|
992
|
+
title: 'Getting Started with Svelte',
|
|
993
|
+
author: 'John Developer',
|
|
994
|
+
category: 'Tutorial',
|
|
995
|
+
tags: ['svelte', 'javascript', 'frontend'],
|
|
996
|
+
publishDate: '2024-03-01',
|
|
997
|
+
readTime: 8,
|
|
998
|
+
excerpt: 'Learn the basics of Svelte framework and build your first application.'
|
|
999
|
+
},
|
|
1000
|
+
{
|
|
1001
|
+
id: 2,
|
|
1002
|
+
title: 'Advanced TypeScript Patterns',
|
|
1003
|
+
author: 'Jane Coder',
|
|
1004
|
+
category: 'Advanced',
|
|
1005
|
+
tags: ['typescript', 'patterns', 'advanced'],
|
|
1006
|
+
publishDate: '2024-02-28',
|
|
1007
|
+
readTime: 12,
|
|
1008
|
+
excerpt: 'Explore advanced TypeScript patterns for better code organization.'
|
|
1009
|
+
},
|
|
1010
|
+
{
|
|
1011
|
+
id: 3,
|
|
1012
|
+
title: 'CSS Grid Layout Guide',
|
|
1013
|
+
author: 'Mike Designer',
|
|
1014
|
+
category: 'CSS',
|
|
1015
|
+
tags: ['css', 'grid', 'layout', 'responsive'],
|
|
1016
|
+
publishDate: '2024-03-05',
|
|
1017
|
+
readTime: 6,
|
|
1018
|
+
excerpt: 'Master CSS Grid for modern responsive layouts.'
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
id: 4,
|
|
1022
|
+
title: 'Database Optimization Tips',
|
|
1023
|
+
author: 'Sarah Admin',
|
|
1024
|
+
category: 'Backend',
|
|
1025
|
+
tags: ['database', 'optimization', 'performance'],
|
|
1026
|
+
publishDate: '2024-02-25',
|
|
1027
|
+
readTime: 15,
|
|
1028
|
+
excerpt: 'Improve your database performance with these optimization techniques.'
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
id: 5,
|
|
1032
|
+
title: 'React vs Svelte Comparison',
|
|
1033
|
+
author: 'Alex Framework',
|
|
1034
|
+
category: 'Comparison',
|
|
1035
|
+
tags: ['react', 'svelte', 'comparison', 'frontend'],
|
|
1036
|
+
publishDate: '2024-03-03',
|
|
1037
|
+
readTime: 10,
|
|
1038
|
+
excerpt: 'A detailed comparison between React and Svelte frameworks.'
|
|
1039
|
+
}
|
|
1040
|
+
];
|
|
1041
|
+
|
|
1042
|
+
let filteredArticles = [];
|
|
1043
|
+
let activeFiltersCount = 0;
|
|
1044
|
+
|
|
1045
|
+
function performSearch() {
|
|
1046
|
+
let results = allArticles;
|
|
1047
|
+
|
|
1048
|
+
// Filter by title
|
|
1049
|
+
if (titleSearch && titleSearch.trim()) {
|
|
1050
|
+
const titleTerm = titleSearch.toLowerCase().trim();
|
|
1051
|
+
results = results.filter(article =>
|
|
1052
|
+
article.title.toLowerCase().includes(titleTerm)
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// Filter by author
|
|
1057
|
+
if (authorSearch && authorSearch.trim()) {
|
|
1058
|
+
const authorTerm = authorSearch.toLowerCase().trim();
|
|
1059
|
+
results = results.filter(article =>
|
|
1060
|
+
article.author.toLowerCase().includes(authorTerm)
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// Filter by tags
|
|
1065
|
+
if (tagSearch && tagSearch.trim()) {
|
|
1066
|
+
const tagTerm = tagSearch.toLowerCase().trim();
|
|
1067
|
+
results = results.filter(article =>
|
|
1068
|
+
article.tags.some(tag => tag.toLowerCase().includes(tagTerm)) ||
|
|
1069
|
+
article.category.toLowerCase().includes(tagTerm)
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
filteredArticles = results;
|
|
1074
|
+
|
|
1075
|
+
// Count active filters
|
|
1076
|
+
activeFiltersCount = [titleSearch, authorSearch, tagSearch]
|
|
1077
|
+
.filter(search => search && search.trim()).length;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// Initial load
|
|
1081
|
+
filteredArticles = allArticles;
|
|
1082
|
+
|
|
1083
|
+
// Reactive search
|
|
1084
|
+
$: {
|
|
1085
|
+
titleSearch, authorSearch, tagSearch;
|
|
1086
|
+
performSearch();
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function clearAllFilters() {
|
|
1090
|
+
titleSearch = null;
|
|
1091
|
+
authorSearch = null;
|
|
1092
|
+
tagSearch = null;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function readArticle(article) {
|
|
1096
|
+
console.log('Reading article:', article.title);
|
|
1097
|
+
alert(`Opening: ${article.title}\nBy ${article.author} • ${article.readTime} min read`);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Get unique authors and tags for suggestions
|
|
1101
|
+
const allAuthors = [...new Set(allArticles.map(a => a.author))];
|
|
1102
|
+
const allTags = [...new Set(allArticles.flatMap(a => a.tags))];
|
|
1103
|
+
const allCategories = [...new Set(allArticles.map(a => a.category))];
|
|
1104
|
+
</script>
|
|
1105
|
+
|
|
1106
|
+
<div class="article-search">
|
|
1107
|
+
<header class="search-header">
|
|
1108
|
+
<h2>Article Library</h2>
|
|
1109
|
+
<p>Search and filter through our knowledge base</p>
|
|
1110
|
+
</header>
|
|
1111
|
+
|
|
1112
|
+
<div class="search-filters">
|
|
1113
|
+
<div class="filter-row">
|
|
1114
|
+
<div class="filter-group">
|
|
1115
|
+
<label>Search by Title</label>
|
|
1116
|
+
<SearchBox
|
|
1117
|
+
bind:value={titleSearch}
|
|
1118
|
+
placeholder="Enter article title..."
|
|
1119
|
+
variant="outlined"
|
|
1120
|
+
compact={true}
|
|
1121
|
+
/>
|
|
1122
|
+
</div>
|
|
1123
|
+
|
|
1124
|
+
<div class="filter-group">
|
|
1125
|
+
<label>Search by Author</label>
|
|
1126
|
+
<SearchBox
|
|
1127
|
+
bind:value={authorSearch}
|
|
1128
|
+
placeholder="Enter author name..."
|
|
1129
|
+
variant="outlined"
|
|
1130
|
+
compact={true}
|
|
1131
|
+
/>
|
|
1132
|
+
</div>
|
|
1133
|
+
|
|
1134
|
+
<div class="filter-group">
|
|
1135
|
+
<label>Search by Tags/Category</label>
|
|
1136
|
+
<SearchBox
|
|
1137
|
+
bind:value={tagSearch}
|
|
1138
|
+
placeholder="Enter tags or category..."
|
|
1139
|
+
variant="outlined"
|
|
1140
|
+
compact={true}
|
|
1141
|
+
/>
|
|
1142
|
+
</div>
|
|
1143
|
+
</div>
|
|
1144
|
+
|
|
1145
|
+
<div class="filter-actions">
|
|
1146
|
+
<div class="filter-info">
|
|
1147
|
+
{#if activeFiltersCount > 0}
|
|
1148
|
+
<span class="active-filters">{activeFiltersCount} filter{activeFiltersCount === 1 ? '' : 's'} active</span>
|
|
1149
|
+
<button class="clear-all-btn" on:click={clearAllFilters}>Clear All</button>
|
|
1150
|
+
{:else}
|
|
1151
|
+
<span class="no-filters">No filters applied</span>
|
|
1152
|
+
{/if}
|
|
1153
|
+
</div>
|
|
1154
|
+
|
|
1155
|
+
<div class="results-info">
|
|
1156
|
+
<span class="result-count">{filteredArticles.length} article{filteredArticles.length === 1 ? '' : 's'} found</span>
|
|
1157
|
+
</div>
|
|
1158
|
+
</div>
|
|
1159
|
+
|
|
1160
|
+
<!-- Quick filter suggestions -->
|
|
1161
|
+
<div class="quick-suggestions">
|
|
1162
|
+
<div class="suggestion-group">
|
|
1163
|
+
<span class="suggestion-label">Popular Authors:</span>
|
|
1164
|
+
{#each allAuthors.slice(0, 3) as author}
|
|
1165
|
+
<button
|
|
1166
|
+
class="suggestion-btn"
|
|
1167
|
+
on:click={() => authorSearch = author}
|
|
1168
|
+
class:active={authorSearch === author}
|
|
1169
|
+
>
|
|
1170
|
+
{author}
|
|
1171
|
+
</button>
|
|
1172
|
+
{/each}
|
|
1173
|
+
</div>
|
|
1174
|
+
|
|
1175
|
+
<div class="suggestion-group">
|
|
1176
|
+
<span class="suggestion-label">Popular Tags:</span>
|
|
1177
|
+
{#each allTags.slice(0, 5) as tag}
|
|
1178
|
+
<button
|
|
1179
|
+
class="suggestion-btn"
|
|
1180
|
+
on:click={() => tagSearch = tag}
|
|
1181
|
+
class:active={tagSearch === tag}
|
|
1182
|
+
>
|
|
1183
|
+
#{tag}
|
|
1184
|
+
</button>
|
|
1185
|
+
{/each}
|
|
1186
|
+
</div>
|
|
1187
|
+
</div>
|
|
1188
|
+
</div>
|
|
1189
|
+
|
|
1190
|
+
<div class="articles-section">
|
|
1191
|
+
{#if filteredArticles.length > 0}
|
|
1192
|
+
<div class="articles-grid">
|
|
1193
|
+
{#each filteredArticles as article}
|
|
1194
|
+
<div class="article-card" on:click={() => readArticle(article)}>
|
|
1195
|
+
<div class="article-header">
|
|
1196
|
+
<h3 class="article-title">{article.title}</h3>
|
|
1197
|
+
<span class="article-category">{article.category}</span>
|
|
1198
|
+
</div>
|
|
1199
|
+
|
|
1200
|
+
<div class="article-meta">
|
|
1201
|
+
<span class="article-author">By {article.author}</span>
|
|
1202
|
+
<span class="article-date">{new Date(article.publishDate).toLocaleDateString()}</span>
|
|
1203
|
+
<span class="article-read-time">{article.readTime} min read</span>
|
|
1204
|
+
</div>
|
|
1205
|
+
|
|
1206
|
+
<p class="article-excerpt">{article.excerpt}</p>
|
|
1207
|
+
|
|
1208
|
+
<div class="article-tags">
|
|
1209
|
+
{#each article.tags as tag}
|
|
1210
|
+
<span class="tag">#{tag}</span>
|
|
1211
|
+
{/each}
|
|
1212
|
+
</div>
|
|
1213
|
+
</div>
|
|
1214
|
+
{/each}
|
|
1215
|
+
</div>
|
|
1216
|
+
{:else}
|
|
1217
|
+
<div class="no-articles">
|
|
1218
|
+
<div class="no-articles-icon">📚</div>
|
|
1219
|
+
<h3>No articles found</h3>
|
|
1220
|
+
<p>
|
|
1221
|
+
{#if activeFiltersCount > 0}
|
|
1222
|
+
No articles match your current search criteria.
|
|
1223
|
+
<br>Try adjusting your filters or clearing some search terms.
|
|
1224
|
+
{:else}
|
|
1225
|
+
No articles available in the library.
|
|
1226
|
+
{/if}
|
|
1227
|
+
</p>
|
|
1228
|
+
{#if activeFiltersCount > 0}
|
|
1229
|
+
<button class="reset-btn" on:click={clearAllFilters}>Clear All Filters</button>
|
|
1230
|
+
{/if}
|
|
1231
|
+
</div>
|
|
1232
|
+
{/if}
|
|
1233
|
+
</div>
|
|
1234
|
+
</div>
|
|
1235
|
+
|
|
1236
|
+
<style>
|
|
1237
|
+
.article-search {
|
|
1238
|
+
max-width: 1200px;
|
|
1239
|
+
margin: 0 auto;
|
|
1240
|
+
padding: 20px;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
.search-header {
|
|
1244
|
+
text-align: center;
|
|
1245
|
+
margin-bottom: 30px;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
.search-header h2 {
|
|
1249
|
+
color: #333;
|
|
1250
|
+
margin: 0 0 8px 0;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
.search-header p {
|
|
1254
|
+
color: #666;
|
|
1255
|
+
margin: 0;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
.search-filters {
|
|
1259
|
+
background: #f8f9fa;
|
|
1260
|
+
padding: 25px;
|
|
1261
|
+
border-radius: 8px;
|
|
1262
|
+
margin-bottom: 30px;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
.filter-row {
|
|
1266
|
+
display: grid;
|
|
1267
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
1268
|
+
gap: 20px;
|
|
1269
|
+
margin-bottom: 20px;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
.filter-group {
|
|
1273
|
+
display: flex;
|
|
1274
|
+
flex-direction: column;
|
|
1275
|
+
gap: 8px;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
.filter-group label {
|
|
1279
|
+
font-weight: 500;
|
|
1280
|
+
color: #333;
|
|
1281
|
+
font-size: 0.9rem;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
.filter-actions {
|
|
1285
|
+
display: flex;
|
|
1286
|
+
justify-content: space-between;
|
|
1287
|
+
align-items: center;
|
|
1288
|
+
margin-bottom: 20px;
|
|
1289
|
+
padding-bottom: 15px;
|
|
1290
|
+
border-bottom: 1px solid #dee2e6;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
.filter-info {
|
|
1294
|
+
display: flex;
|
|
1295
|
+
align-items: center;
|
|
1296
|
+
gap: 12px;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
.active-filters {
|
|
1300
|
+
color: #007bff;
|
|
1301
|
+
font-weight: 500;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
.no-filters {
|
|
1305
|
+
color: #666;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
.clear-all-btn {
|
|
1309
|
+
padding: 4px 8px;
|
|
1310
|
+
background: #dc3545;
|
|
1311
|
+
color: white;
|
|
1312
|
+
border: none;
|
|
1313
|
+
border-radius: 4px;
|
|
1314
|
+
cursor: pointer;
|
|
1315
|
+
font-size: 0.8rem;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
.clear-all-btn:hover {
|
|
1319
|
+
background: #c82333;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
.result-count {
|
|
1323
|
+
color: #495057;
|
|
1324
|
+
font-weight: 500;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
.quick-suggestions {
|
|
1328
|
+
display: flex;
|
|
1329
|
+
flex-wrap: wrap;
|
|
1330
|
+
gap: 20px;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
.suggestion-group {
|
|
1334
|
+
display: flex;
|
|
1335
|
+
align-items: center;
|
|
1336
|
+
gap: 8px;
|
|
1337
|
+
flex-wrap: wrap;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
.suggestion-label {
|
|
1341
|
+
color: #666;
|
|
1342
|
+
font-size: 0.85rem;
|
|
1343
|
+
font-weight: 500;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
.suggestion-btn {
|
|
1347
|
+
padding: 4px 8px;
|
|
1348
|
+
background: white;
|
|
1349
|
+
border: 1px solid #dee2e6;
|
|
1350
|
+
border-radius: 12px;
|
|
1351
|
+
cursor: pointer;
|
|
1352
|
+
font-size: 0.75rem;
|
|
1353
|
+
transition: all 0.2s;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
.suggestion-btn:hover {
|
|
1357
|
+
border-color: #007bff;
|
|
1358
|
+
color: #007bff;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
.suggestion-btn.active {
|
|
1362
|
+
background: #007bff;
|
|
1363
|
+
color: white;
|
|
1364
|
+
border-color: #007bff;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
.articles-grid {
|
|
1368
|
+
display: grid;
|
|
1369
|
+
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
|
1370
|
+
gap: 24px;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
.article-card {
|
|
1374
|
+
background: white;
|
|
1375
|
+
border: 1px solid #e9ecef;
|
|
1376
|
+
border-radius: 8px;
|
|
1377
|
+
padding: 20px;
|
|
1378
|
+
cursor: pointer;
|
|
1379
|
+
transition: all 0.2s;
|
|
1380
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
.article-card:hover {
|
|
1384
|
+
border-color: #007bff;
|
|
1385
|
+
transform: translateY(-2px);
|
|
1386
|
+
box-shadow: 0 4px 8px rgba(0,123,255,0.15);
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
.article-header {
|
|
1390
|
+
display: flex;
|
|
1391
|
+
justify-content: space-between;
|
|
1392
|
+
align-items: flex-start;
|
|
1393
|
+
margin-bottom: 12px;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
.article-title {
|
|
1397
|
+
margin: 0;
|
|
1398
|
+
color: #333;
|
|
1399
|
+
font-size: 1.2rem;
|
|
1400
|
+
font-weight: 600;
|
|
1401
|
+
line-height: 1.3;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
.article-category {
|
|
1405
|
+
background: #e9ecef;
|
|
1406
|
+
color: #495057;
|
|
1407
|
+
padding: 4px 8px;
|
|
1408
|
+
border-radius: 12px;
|
|
1409
|
+
font-size: 0.7rem;
|
|
1410
|
+
font-weight: 500;
|
|
1411
|
+
text-transform: uppercase;
|
|
1412
|
+
flex-shrink: 0;
|
|
1413
|
+
margin-left: 12px;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
.article-meta {
|
|
1417
|
+
display: flex;
|
|
1418
|
+
flex-wrap: wrap;
|
|
1419
|
+
gap: 12px;
|
|
1420
|
+
margin-bottom: 12px;
|
|
1421
|
+
font-size: 0.85rem;
|
|
1422
|
+
color: #666;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
.article-author {
|
|
1426
|
+
font-weight: 500;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
.article-excerpt {
|
|
1430
|
+
color: #555;
|
|
1431
|
+
line-height: 1.5;
|
|
1432
|
+
margin: 0 0 16px 0;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
.article-tags {
|
|
1436
|
+
display: flex;
|
|
1437
|
+
flex-wrap: wrap;
|
|
1438
|
+
gap: 6px;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
.tag {
|
|
1442
|
+
background: #f8f9fa;
|
|
1443
|
+
color: #495057;
|
|
1444
|
+
padding: 3px 8px;
|
|
1445
|
+
border-radius: 10px;
|
|
1446
|
+
font-size: 0.7rem;
|
|
1447
|
+
border: 1px solid #e9ecef;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
.no-articles {
|
|
1451
|
+
text-align: center;
|
|
1452
|
+
padding: 80px 20px;
|
|
1453
|
+
color: #666;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
.no-articles-icon {
|
|
1457
|
+
font-size: 4rem;
|
|
1458
|
+
opacity: 0.5;
|
|
1459
|
+
margin-bottom: 16px;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
.no-articles h3 {
|
|
1463
|
+
color: #333;
|
|
1464
|
+
margin: 0 0 12px 0;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
.no-articles p {
|
|
1468
|
+
margin: 0 0 20px 0;
|
|
1469
|
+
line-height: 1.6;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
.reset-btn {
|
|
1473
|
+
padding: 10px 20px;
|
|
1474
|
+
background: #007bff;
|
|
1475
|
+
color: white;
|
|
1476
|
+
border: none;
|
|
1477
|
+
border-radius: 4px;
|
|
1478
|
+
cursor: pointer;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
.reset-btn:hover {
|
|
1482
|
+
background: #0056b3;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
@media (max-width: 768px) {
|
|
1486
|
+
.filter-row {
|
|
1487
|
+
grid-template-columns: 1fr;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
.filter-actions {
|
|
1491
|
+
flex-direction: column;
|
|
1492
|
+
gap: 12px;
|
|
1493
|
+
align-items: stretch;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
.quick-suggestions {
|
|
1497
|
+
flex-direction: column;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
.articles-grid {
|
|
1501
|
+
grid-template-columns: 1fr;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
.article-header {
|
|
1505
|
+
flex-direction: column;
|
|
1506
|
+
gap: 8px;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
.article-category {
|
|
1510
|
+
align-self: flex-start;
|
|
1511
|
+
margin-left: 0;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
</style>
|
|
1515
|
+
```
|
|
1516
|
+
|
|
1517
|
+
## Styling
|
|
1518
|
+
|
|
1519
|
+
The SearchBox component uses the following CSS classes:
|
|
1520
|
+
|
|
1521
|
+
```css
|
|
1522
|
+
.uniface-common-editor {
|
|
1523
|
+
/* Main input container */
|
|
1524
|
+
position: relative;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
.uniface-common-editor .leading-icon {
|
|
1528
|
+
/* Search icon container */
|
|
1529
|
+
display: flex;
|
|
1530
|
+
align-items: center;
|
|
1531
|
+
padding: 0 8px;
|
|
1532
|
+
color: var(--uniface-editor-icon-color, #6c757d);
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
.uniface-common-editor .action-icon {
|
|
1536
|
+
/* Clear button styling */
|
|
1537
|
+
cursor: pointer;
|
|
1538
|
+
opacity: 0.6;
|
|
1539
|
+
transition: opacity 0.2s;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
.uniface-common-editor .action-icon:hover {
|
|
1543
|
+
opacity: 1;
|
|
1544
|
+
}
|
|
1545
|
+
```
|
|
1546
|
+
|
|
1547
|
+
## API Reference
|
|
1548
|
+
|
|
1549
|
+
### SearchBox Component Props
|
|
1550
|
+
|
|
1551
|
+
```typescript
|
|
1552
|
+
interface SearchBoxProps {
|
|
1553
|
+
variant?: '' | 'plain' | 'outlined' | 'filled'; // Visual styling variant
|
|
1554
|
+
compact?: boolean; // Compact layout mode
|
|
1555
|
+
style?: string; // Custom inline styles
|
|
1556
|
+
value: string | null; // Search text value
|
|
1557
|
+
disabled?: boolean; // Disable input
|
|
1558
|
+
onChange?: OnChangeHandler<string | null>; // Change event handler
|
|
1559
|
+
placeholder?: string | null; // Placeholder text
|
|
1560
|
+
}
|
|
1561
|
+
```
|
|
1562
|
+
|
|
1563
|
+
### Methods
|
|
1564
|
+
|
|
1565
|
+
```typescript
|
|
1566
|
+
interface SearchBox {
|
|
1567
|
+
focus(): void; // Programmatically focus the input
|
|
1568
|
+
}
|
|
1569
|
+
```
|
|
1570
|
+
|
|
1571
|
+
## Best Practices
|
|
1572
|
+
|
|
1573
|
+
1. **Debouncing** - Implement debounced search to avoid excessive API calls
|
|
1574
|
+
2. **Loading States** - Show loading indicators during search operations
|
|
1575
|
+
3. **Empty States** - Provide helpful messages when no results are found
|
|
1576
|
+
4. **Keyboard Shortcuts** - Implement application-level keyboard shortcuts for search focus (like Ctrl+K)
|
|
1577
|
+
5. **Search Scope** - Clearly indicate what fields are being searched
|
|
1578
|
+
6. **Clear Functionality** - Always provide easy way to clear search (component has built-in clear button)
|
|
1579
|
+
7. **Mobile UX** - Ensure touch-friendly interaction on mobile devices
|
|
1580
|
+
8. **Performance** - Optimize search algorithms for large datasets
|
|
1581
|
+
9. **Input Composition** - Component handles international text input (Chinese, Japanese, etc.) correctly
|
|
1582
|
+
10. **Visual Feedback** - Highlight matching terms in search results when possible
|