@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.
Files changed (126) hide show
  1. package/README.md +273 -1
  2. package/README_CN.md +341 -0
  3. package/dist/common/MouseClickHandler.d.ts +1 -1
  4. package/dist/dialog/CommonDialog.svelte +6 -3
  5. package/dist/dialog/CommonDialog.svelte.d.ts +2 -1
  6. package/dist/dialog/Dialog.svelte +36 -10
  7. package/dist/dialog/Dialog.svelte.d.ts +4 -3
  8. package/dist/dialog/DialogBoard.svelte +11 -8
  9. package/dist/dialog/DialogWrapper.svelte +10 -0
  10. package/dist/dialog/DialogWrapper.svelte.d.ts +22 -0
  11. package/dist/dialog/Dialogs.js +1 -1
  12. package/dist/dialog/ModalResult.d.ts +5 -0
  13. package/dist/dialog/ModalResult.js +5 -0
  14. package/docs/buttons/cn/ActionBar.md +459 -0
  15. package/docs/buttons/cn/Button.md +228 -0
  16. package/docs/buttons/cn/IconButton.md +294 -0
  17. package/docs/buttons/cn/README.md +600 -0
  18. package/docs/buttons/cn/TextButton.md +163 -0
  19. package/docs/buttons/en/ActionBar.md +459 -0
  20. package/docs/buttons/en/Button.md +228 -0
  21. package/docs/buttons/en/IconButton.md +294 -0
  22. package/docs/buttons/en/README.md +600 -0
  23. package/docs/buttons/en/TextButton.md +163 -0
  24. package/docs/containers/cn/accordion.md +622 -0
  25. package/docs/containers/cn/box.md +445 -0
  26. package/docs/containers/cn/card.md +404 -0
  27. package/docs/containers/cn/page.md +576 -0
  28. package/docs/containers/cn/tabs.md +686 -0
  29. package/docs/containers/en/accordion.md +622 -0
  30. package/docs/containers/en/box.md +445 -0
  31. package/docs/containers/en/card.md +404 -0
  32. package/docs/containers/en/page.md +576 -0
  33. package/docs/containers/en/tabs.md +686 -0
  34. package/docs/data-display/cn/concise-data-table.md +731 -0
  35. package/docs/data-display/cn/listbox.md +786 -0
  36. package/docs/data-display/cn/treeview.md +863 -0
  37. package/docs/data-display/en/concise-data-table.md +732 -0
  38. package/docs/data-display/en/listbox.md +786 -0
  39. package/docs/data-display/en/treeview.md +863 -0
  40. package/docs/data-table/cn/README.md +596 -0
  41. package/docs/data-table/en/README.md +596 -0
  42. package/docs/dialog/cn/README.md +767 -0
  43. package/docs/dialog/en/README.md +767 -0
  44. package/docs/form/cn/README.md +847 -0
  45. package/docs/form/en/README.md +847 -0
  46. package/docs/form-components/cn/README.md +495 -0
  47. package/docs/form-components/cn/attachmentfilesfield.md +757 -0
  48. package/docs/form-components/cn/cascade-options-select.md +374 -0
  49. package/docs/form-components/cn/checkbox.md +58 -0
  50. package/docs/form-components/cn/date-picker.md +138 -0
  51. package/docs/form-components/cn/datetime-picker.md +228 -0
  52. package/docs/form-components/cn/group-checkbox.md +405 -0
  53. package/docs/form-components/cn/group-radiobox.md +525 -0
  54. package/docs/form-components/cn/imagefilesfield.md +651 -0
  55. package/docs/form-components/cn/input-options-select.md +250 -0
  56. package/docs/form-components/cn/lookup-editor.md +406 -0
  57. package/docs/form-components/cn/memoeditor.md +409 -0
  58. package/docs/form-components/cn/number-editor.md +261 -0
  59. package/docs/form-components/cn/options-multi-select.md +177 -0
  60. package/docs/form-components/cn/options-select.md +83 -0
  61. package/docs/form-components/cn/passwordeditor.md +538 -0
  62. package/docs/form-components/cn/prompts-text-editor.md +241 -0
  63. package/docs/form-components/cn/radiobutton.md +84 -0
  64. package/docs/form-components/cn/switch.md +178 -0
  65. package/docs/form-components/cn/text-editor.md +142 -0
  66. package/docs/form-components/cn/time-editor.md +159 -0
  67. package/docs/form-components/cn/unit-number-editor.md +164 -0
  68. package/docs/form-components/en/README.md +463 -0
  69. package/docs/form-components/en/attachmentfilesfield.md +757 -0
  70. package/docs/form-components/en/cascade-options-select.md +358 -0
  71. package/docs/form-components/en/checkbox.md +59 -0
  72. package/docs/form-components/en/date-picker.md +138 -0
  73. package/docs/form-components/en/datetime-picker.md +228 -0
  74. package/docs/form-components/en/group-checkbox.md +395 -0
  75. package/docs/form-components/en/group-radiobox.md +498 -0
  76. package/docs/form-components/en/imagefilesfield.md +651 -0
  77. package/docs/form-components/en/input-options-select.md +239 -0
  78. package/docs/form-components/en/lookup-editor.md +389 -0
  79. package/docs/form-components/en/memoeditor.md +409 -0
  80. package/docs/form-components/en/number-editor.md +249 -0
  81. package/docs/form-components/en/options-multi-select.md +177 -0
  82. package/docs/form-components/en/options-select.md +83 -0
  83. package/docs/form-components/en/passwordeditor.md +538 -0
  84. package/docs/form-components/en/prompts-text-editor.md +241 -0
  85. package/docs/form-components/en/radiobutton.md +84 -0
  86. package/docs/form-components/en/switch.md +180 -0
  87. package/docs/form-components/en/text-editor.md +142 -0
  88. package/docs/form-components/en/time-editor.md +159 -0
  89. package/docs/form-components/en/unit-number-editor.md +164 -0
  90. package/docs/form-layout/cn/criteria-field.md +652 -0
  91. package/docs/form-layout/cn/flex-form.md +454 -0
  92. package/docs/form-layout/cn/flex-row-form.md +542 -0
  93. package/docs/form-layout/cn/form-field.md +923 -0
  94. package/docs/form-layout/cn/grid-form.md +676 -0
  95. package/docs/form-layout/en/criteria-field.md +652 -0
  96. package/docs/form-layout/en/flex-form.md +454 -0
  97. package/docs/form-layout/en/flex-row-form.md +542 -0
  98. package/docs/form-layout/en/form-field.md +923 -0
  99. package/docs/form-layout/en/grid-form.md +676 -0
  100. package/docs/global/cn/README.md +534 -0
  101. package/docs/global/en/README.md +534 -0
  102. package/docs/layout/cn/classiclayout.md +460 -0
  103. package/docs/layout/cn/headerlayout.md +524 -0
  104. package/docs/layout/cn/sidebarlayout.md +263 -0
  105. package/docs/layout/en/classiclayout.md +951 -0
  106. package/docs/layout/en/headerlayout.md +524 -0
  107. package/docs/layout/en/sidebarlayout.md +262 -0
  108. package/docs/misc/cn/README.md +1217 -0
  109. package/docs/misc/en/README.md +1217 -0
  110. package/docs/navigation/cn/breadcrumb.md +291 -0
  111. package/docs/navigation/cn/navigator.md +322 -0
  112. package/docs/navigation/cn/navigatormenu.md +512 -0
  113. package/docs/navigation/cn/progressbar.md +199 -0
  114. package/docs/navigation/cn/progressstepbar.md +299 -0
  115. package/docs/navigation/en/breadcrumb.md +291 -0
  116. package/docs/navigation/en/navigator.md +322 -0
  117. package/docs/navigation/en/navigatormenu.md +512 -0
  118. package/docs/navigation/en/progressbar.md +199 -0
  119. package/docs/navigation/en/progressstepbar.md +299 -0
  120. package/docs/search-filters/cn/daterange.md +1156 -0
  121. package/docs/search-filters/cn/numberrange.md +688 -0
  122. package/docs/search-filters/cn/searchbox.md +1582 -0
  123. package/docs/search-filters/en/daterange.md +1156 -0
  124. package/docs/search-filters/en/numberrange.md +688 -0
  125. package/docs/search-filters/en/searchbox.md +1582 -0
  126. 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