mertani-web-toolkit 0.1.49 → 0.1.51

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.
@@ -72,6 +72,77 @@
72
72
  border-color: var(--color-text-error-ti);
73
73
  }
74
74
 
75
+ .search-by-wrapper {
76
+ position: relative;
77
+ display: flex;
78
+ align-items: center;
79
+ border-right: 1px solid var(--color-border-form);
80
+ background: var(--color-bg-disabled);
81
+ flex-shrink: 0;
82
+ border-radius: 6px 0 0 6px;
83
+ }
84
+
85
+ .search-by-trigger {
86
+ border: none;
87
+ outline: none;
88
+ background: transparent;
89
+ font-size: 12px;
90
+ font-weight: 600;
91
+ color: var(--color-text-primary);
92
+ cursor: pointer;
93
+ padding: 0 10px;
94
+ height: 100%;
95
+ display: inline-flex;
96
+ align-items: center;
97
+ gap: 8px;
98
+ }
99
+
100
+ .search-by-trigger:disabled {
101
+ cursor: not-allowed;
102
+ opacity: 0.7;
103
+ }
104
+
105
+ .search-by-label {
106
+ white-space: nowrap;
107
+ }
108
+
109
+ .search-by-menu {
110
+ position: absolute;
111
+ top: calc(100% + 4px);
112
+ min-width: 140px;
113
+ background: var(--color-bg-surface);
114
+ border: 1px solid var(--color-border-form);
115
+ border-radius: 4px;
116
+ box-shadow:
117
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
118
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
119
+ overflow: hidden;
120
+ z-index: 99999;
121
+ }
122
+
123
+ .search-by-item {
124
+ width: 100%;
125
+ padding: 10px 12px;
126
+ text-align: left;
127
+ background: transparent;
128
+ border: none;
129
+ font-size: 13px;
130
+ color: var(--color-text-primary);
131
+ cursor: pointer;
132
+ transition:
133
+ background-color 0.15s,
134
+ color 0.15s;
135
+ }
136
+
137
+ .search-by-item + .search-by-item {
138
+ border-top: 1px solid color-mix(in srgb, var(--color-border-form) 45%, transparent);
139
+ }
140
+
141
+ .search-by-item:hover,
142
+ .search-by-item.active {
143
+ background: var(--color-bg-disabled);
144
+ }
145
+
75
146
  .prefix-wrapper {
76
147
  display: flex;
77
148
  align-items: center;
@@ -239,7 +310,7 @@
239
310
 
240
311
  .suggestion-item:hover,
241
312
  .suggestion-item.active {
242
- background: var(--color-bg-surface-hover, #f6f8fa);
313
+ background: var(--color-bg-disabled);
243
314
  }
244
315
 
245
316
  .suggestion-empty {
@@ -12,6 +12,10 @@
12
12
  value?: string;
13
13
  detail?: unknown;
14
14
  };
15
+ type SearchByOption = {
16
+ label: string;
17
+ value: string;
18
+ };
15
19
 
16
20
  interface Props extends HTMLInputAttributes {
17
21
  // ===Styles===
@@ -45,6 +49,8 @@
45
49
 
46
50
  // Suggestions
47
51
  suggestions?: SuggestionOption[];
52
+ searchByOptions?: SearchByOption[];
53
+ searchBy?: string;
48
54
  minChars?: number;
49
55
  maxSuggestions?: number;
50
56
  showSuggestionsOnFocus?: boolean;
@@ -112,6 +118,8 @@
112
118
 
113
119
  // Suggestions
114
120
  suggestions = [],
121
+ searchByOptions = [],
122
+ searchBy = $bindable(''),
115
123
  minChars = 1,
116
124
  maxSuggestions = 8,
117
125
  showSuggestionsOnFocus = true,
@@ -153,11 +161,24 @@
153
161
  let showList = $state(false);
154
162
  let activeIndex = $state(-1);
155
163
  let wrapperEl: HTMLDivElement | null = $state(null);
164
+ let searchByValue = $state(searchBy);
165
+ let searchByOpen = $state(false);
156
166
 
157
167
  $effect(() => {
158
168
  inputValue = value;
159
169
  });
160
170
 
171
+ $effect(() => {
172
+ searchByValue = searchBy;
173
+ });
174
+
175
+ $effect(() => {
176
+ if (!searchByValue && searchByOptions.length) {
177
+ searchByValue = searchByOptions[0].value;
178
+ searchBy = searchByValue;
179
+ }
180
+ });
181
+
161
182
  const sizeConfig = $derived(() => {
162
183
  switch (size) {
163
184
  case 48:
@@ -365,6 +386,23 @@
365
386
  oninput?.(e);
366
387
  }
367
388
 
389
+ function handleSearchByChange(e: Event) {
390
+ const target = e.target as HTMLSelectElement;
391
+ searchByValue = target.value;
392
+ searchBy = target.value;
393
+ }
394
+
395
+ function selectSearchBy(option: SearchByOption) {
396
+ searchByValue = option.value;
397
+ searchBy = option.value;
398
+ searchByOpen = false;
399
+ }
400
+
401
+ function getSearchByLabel() {
402
+ const current = searchByOptions.find((opt) => opt.value === searchByValue);
403
+ return current?.label || 'Search';
404
+ }
405
+
368
406
  function handleChange(e: Event) {
369
407
  const target = e.target as HTMLInputElement;
370
408
  const value = target.value;
@@ -415,17 +453,13 @@
415
453
  }
416
454
 
417
455
  function selectSuggestion(item: { label: string; value: string; raw: SuggestionOption }) {
418
- inputValue = item.value;
419
- value = item.value;
420
456
  onSelectSuggestion?.(item.value, item.raw);
421
- if (closeOnSelect) {
422
- closeSuggestions();
423
- }
424
457
  }
425
458
 
426
459
  function handleClickOutside(e: MouseEvent) {
427
460
  if (wrapperEl && !wrapperEl.contains(e.target as Node)) {
428
461
  closeSuggestions();
462
+ searchByOpen = false;
429
463
  }
430
464
  }
431
465
 
@@ -459,6 +493,41 @@
459
493
  </label>
460
494
  {/if}
461
495
  <div class={wrapperClasses()} style={wrapperStyles()} bind:this={wrapperEl}>
496
+ {#if searchByOptions.length}
497
+ <div class="search-by-wrapper">
498
+ <button
499
+ type="button"
500
+ class="search-by-trigger"
501
+ onclick={() => !disabled && !isLoading && (searchByOpen = !searchByOpen)}
502
+ disabled={disabled || isLoading}
503
+ >
504
+ <span class="search-by-label">{getSearchByLabel()}</span>
505
+ <Icon
506
+ name="bs-chevron-down"
507
+ style="transform-origin: center; transition: transform 0.25s; transform: rotate({searchByOpen
508
+ ? '180deg'
509
+ : '0deg'});"
510
+ />
511
+ </button>
512
+ {#if searchByOpen}
513
+ <div class="search-by-menu">
514
+ {#each searchByOptions as option (option.value)}
515
+ <button
516
+ type="button"
517
+ class="search-by-item"
518
+ class:active={option.value === searchByValue}
519
+ onmousedown={(e) => {
520
+ e.preventDefault();
521
+ selectSearchBy(option);
522
+ }}
523
+ >
524
+ {option.label}
525
+ </button>
526
+ {/each}
527
+ </div>
528
+ {/if}
529
+ </div>
530
+ {/if}
462
531
  {#if prefix}
463
532
  <div class="prefix-wrapper" style={prefixStyles()}>
464
533
  <span class="prefix-text">{prefix}</span>
@@ -7,6 +7,10 @@ type SuggestionOption = string | {
7
7
  value?: string;
8
8
  detail?: unknown;
9
9
  };
10
+ type SearchByOption = {
11
+ label: string;
12
+ value: string;
13
+ };
10
14
  interface Props extends HTMLInputAttributes {
11
15
  labelColor?: string;
12
16
  aligment?: 'side' | 'top';
@@ -30,6 +34,8 @@ interface Props extends HTMLInputAttributes {
30
34
  prefix?: string;
31
35
  suffix?: string;
32
36
  suggestions?: SuggestionOption[];
37
+ searchByOptions?: SearchByOption[];
38
+ searchBy?: string;
33
39
  minChars?: number;
34
40
  maxSuggestions?: number;
35
41
  showSuggestionsOnFocus?: boolean;
@@ -56,6 +62,6 @@ interface Props extends HTMLInputAttributes {
56
62
  class?: string;
57
63
  style?: string;
58
64
  }
59
- declare const TextInputSuggestion: import("svelte").Component<Props, {}, "value">;
65
+ declare const TextInputSuggestion: import("svelte").Component<Props, {}, "value" | "searchBy">;
60
66
  type TextInputSuggestion = ReturnType<typeof TextInputSuggestion>;
61
67
  export default TextInputSuggestion;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mertani-web-toolkit",
3
- "version": "0.1.49",
3
+ "version": "0.1.51",
4
4
  "homepage": "https://storybook.mertani.com/",
5
5
  "scripts": {
6
6
  "dev": "vite dev",