mertani-web-toolkit 0.1.48 → 0.1.49

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.
@@ -0,0 +1,255 @@
1
+ .input-container {
2
+ display: flex;
3
+ flex-direction: column;
4
+ width: 100%;
5
+ }
6
+
7
+ .input-container.input-side {
8
+ flex-direction: row;
9
+ align-items: center;
10
+ gap: 12px;
11
+ }
12
+
13
+ .input-label {
14
+ font-size: 14px;
15
+ font-weight: 400;
16
+ color: var(--color-text-primary);
17
+ margin-bottom: 4px;
18
+ display: block;
19
+ }
20
+
21
+ .input-container.input-side .input-label {
22
+ margin-bottom: 0;
23
+ min-width: 120px;
24
+ }
25
+
26
+ .input-label.label-left {
27
+ text-align: left;
28
+ }
29
+
30
+ .input-label.label-right {
31
+ text-align: right;
32
+ }
33
+
34
+ .label-subLabel {
35
+ font-weight: 400;
36
+ color: var(--color-text-tertiary);
37
+ margin-left: 4px;
38
+ }
39
+
40
+ .label-required {
41
+ color: var(--color-text-error-ti);
42
+ margin-left: 4px;
43
+ }
44
+
45
+ .input-wrapper {
46
+ position: relative;
47
+ display: flex;
48
+ align-items: stretch;
49
+ width: 100%;
50
+ border: 1px solid var(--color-border-form);
51
+ border-radius: 6px;
52
+ transition:
53
+ border-color 0.2s,
54
+ box-shadow 0.2s;
55
+ }
56
+
57
+ .input-wrapper:has(input:disabled) {
58
+ background: var(--color-bg-disabled);
59
+ border-color: var(--color-border-disabled);
60
+ }
61
+
62
+ .input-wrapper.input-loading {
63
+ cursor: wait;
64
+ }
65
+
66
+ .input-wrapper.input-disabled {
67
+ opacity: 0.6;
68
+ cursor: not-allowed;
69
+ }
70
+
71
+ .input-wrapper.input-error {
72
+ border-color: var(--color-text-error-ti);
73
+ }
74
+
75
+ .prefix-wrapper {
76
+ display: flex;
77
+ align-items: center;
78
+ padding: 0.625rem 0.75rem;
79
+ background: var(--color-bg-disabled);
80
+ border-right: 1px solid var(--color-border-form);
81
+ flex-shrink: 0;
82
+ }
83
+
84
+ .prefix-text {
85
+ font-size: 0.75rem;
86
+ font-weight: 500;
87
+ color: var(--color-text-primary);
88
+ white-space: nowrap;
89
+ }
90
+
91
+ .suffix-wrapper {
92
+ display: flex;
93
+ align-items: center;
94
+ padding: 0.625rem 0.75rem;
95
+ background: var(--color-bg-disabled);
96
+ border-left: 1px solid var(--color-border-form);
97
+ flex-shrink: 0;
98
+ }
99
+
100
+ .suffix-text {
101
+ font-size: 0.75rem;
102
+ font-weight: 500;
103
+ color: var(--color-text-primary);
104
+ white-space: nowrap;
105
+ }
106
+
107
+ .input-field {
108
+ flex: 1;
109
+ border: none;
110
+ outline: none;
111
+ background: transparent;
112
+ padding: 0.625rem 1rem;
113
+ font-size: 14px;
114
+ color: var(--color-text-primary);
115
+ width: 100%;
116
+ }
117
+
118
+ .input-wrapper:has(.prefix-wrapper) .input-field {
119
+ padding-left: 0.75rem;
120
+ }
121
+
122
+ .input-wrapper:has(.suffix-wrapper) .input-field {
123
+ padding-right: 0.75rem;
124
+ }
125
+
126
+ .input-wrapper:has(.input-icon-right):not(:has(.suffix-wrapper)) .input-field {
127
+ padding-right: 2.5rem;
128
+ }
129
+
130
+ .input-wrapper:has(.input-loader.input-icon-right):not(:has(.suffix-wrapper)) .input-field {
131
+ padding-right: 2.5rem;
132
+ }
133
+
134
+ .input-wrapper:has(.input-icon-left):not(:has(.prefix-wrapper)) .input-field {
135
+ padding-left: 2.5rem;
136
+ }
137
+
138
+ .input-wrapper:has(.input-loader.input-icon-left):not(:has(.prefix-wrapper)) .input-field {
139
+ padding-left: 2.5rem;
140
+ }
141
+
142
+ .input-field:disabled {
143
+ cursor: not-allowed;
144
+ }
145
+
146
+ .input-field::placeholder {
147
+ color: var(--color-text-tertiary);
148
+ }
149
+
150
+ .input-icon {
151
+ position: absolute;
152
+ top: 50%;
153
+ transform: translateY(-50%);
154
+ display: flex;
155
+ align-items: center;
156
+ justify-content: center;
157
+ pointer-events: none;
158
+ }
159
+
160
+ .input-icon-left {
161
+ left: 12px;
162
+ }
163
+
164
+ .input-icon-right {
165
+ right: 12px;
166
+ }
167
+
168
+ .input-loader {
169
+ position: absolute;
170
+ top: 50%;
171
+ transform: translateY(-50%);
172
+ display: flex;
173
+ align-items: center;
174
+ justify-content: center;
175
+ pointer-events: none;
176
+ }
177
+
178
+ .input-loader.input-icon-left {
179
+ left: 12px;
180
+ }
181
+
182
+ .input-loader.input-icon-right {
183
+ right: 12px;
184
+ }
185
+
186
+ .loader-spinner {
187
+ display: inline-block;
188
+ width: 16px;
189
+ height: 16px;
190
+ border: 2px solid transparent;
191
+ border-top-color: currentColor;
192
+ border-right-color: currentColor;
193
+ border-radius: 50%;
194
+ animation: spinner-rotate 0.8s linear infinite;
195
+ }
196
+
197
+ @keyframes spinner-rotate {
198
+ 0% {
199
+ transform: rotate(0deg);
200
+ }
201
+ 100% {
202
+ transform: rotate(360deg);
203
+ }
204
+ }
205
+
206
+ .error-message {
207
+ margin-top: 4px;
208
+ font-size: 12px;
209
+ color: var(--color-text-error-ti);
210
+ }
211
+
212
+ .suggestions-menu {
213
+ position: absolute;
214
+ left: 0;
215
+ right: 0;
216
+ top: calc(100% + 4px);
217
+ z-index: 20;
218
+ background: var(--color-bg-surface);
219
+ border: 1px solid var(--color-border-form);
220
+ border-radius: 6px;
221
+ box-shadow:
222
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
223
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
224
+ overflow: hidden;
225
+ max-height: 240px;
226
+ overflow-y: auto;
227
+ }
228
+
229
+ .suggestion-item {
230
+ width: 100%;
231
+ padding: 8px 12px;
232
+ text-align: left;
233
+ background: transparent;
234
+ border: none;
235
+ font-size: 14px;
236
+ color: var(--color-text-primary);
237
+ cursor: pointer;
238
+ }
239
+
240
+ .suggestion-item:hover,
241
+ .suggestion-item.active {
242
+ background: var(--color-bg-surface-hover, #f6f8fa);
243
+ }
244
+
245
+ .suggestion-empty {
246
+ padding: 8px 12px;
247
+ font-size: 13px;
248
+ color: var(--color-text-tertiary);
249
+ }
250
+
251
+ .suggestion-loading {
252
+ padding: 8px 12px;
253
+ font-size: 13px;
254
+ color: var(--color-text-tertiary);
255
+ }
@@ -0,0 +1,538 @@
1
+ <script lang="ts">
2
+ import { Icon } from '../../../index.js';
3
+ import type { TIconName } from '../../../icons/index.js';
4
+ import './TextInputSuggestion.css';
5
+ import type { HTMLInputAttributes } from 'svelte/elements';
6
+
7
+ type SuggestionOption =
8
+ | string
9
+ | {
10
+ label: string;
11
+ id?: string | number;
12
+ value?: string;
13
+ detail?: unknown;
14
+ };
15
+
16
+ interface Props extends HTMLInputAttributes {
17
+ // ===Styles===
18
+ // Label
19
+ labelColor?: string;
20
+ aligment?: 'side' | 'top';
21
+ position?: 'left' | 'right';
22
+
23
+ // Field
24
+ size?: 48 | 40 | 32;
25
+ backgroundColor?: string;
26
+ borderColor?: string;
27
+ accentColor?: string;
28
+ textColor?: string;
29
+ errorColor?: string;
30
+ icon?: TIconName;
31
+ iconColor?: string;
32
+ iconPosition?: 'left' | 'right';
33
+ borderRadius?: number;
34
+ boxShadow?: string;
35
+
36
+ // ===Properties===
37
+ // Data
38
+ id?: string;
39
+ label?: string;
40
+ subLabel?: string;
41
+ placeholder?: string;
42
+ value?: string;
43
+ prefix?: string;
44
+ suffix?: string;
45
+
46
+ // Suggestions
47
+ suggestions?: SuggestionOption[];
48
+ minChars?: number;
49
+ maxSuggestions?: number;
50
+ showSuggestionsOnFocus?: boolean;
51
+ emptyMessage?: string;
52
+ closeOnSelect?: boolean;
53
+ loadingSuggestions?: boolean;
54
+
55
+ // Events
56
+ onSelectSuggestion?: (value: string, option: SuggestionOption) => void;
57
+ onclick?: (event: MouseEvent) => void;
58
+ oninput?: (event: Event) => void;
59
+ onchange?: (event: Event) => void;
60
+ onfocus?: (event: FocusEvent) => void;
61
+ onblur?: (event: FocusEvent) => void;
62
+ onkeydown?: (event: KeyboardEvent) => void;
63
+
64
+ // Validation
65
+ isMandatory?: boolean;
66
+ regex?: RegExp;
67
+ minLength?: number;
68
+ maxLength?: number;
69
+ customValidation?: (value: string) => string | null;
70
+
71
+ // Additional Actions
72
+ isLoading?: boolean;
73
+ isShow?: boolean;
74
+ disabled?: boolean;
75
+ readOnly?: boolean;
76
+ tooltip?: string;
77
+
78
+ // Any
79
+ class?: string;
80
+ style?: string;
81
+ }
82
+
83
+ let {
84
+ // ===Styles===
85
+ // Label
86
+ labelColor = 'var(--color-text-primary)',
87
+ aligment = 'top',
88
+ position = 'left',
89
+
90
+ // Field
91
+ size = 40,
92
+ backgroundColor = 'var(--color-bg-surface)',
93
+ borderColor = 'var(--color-border-form)',
94
+ accentColor = 'var(--color-bg-act-primary)',
95
+ textColor = 'var(--color-text-primary)',
96
+ errorColor = 'var(--color-text-error-ti)',
97
+ icon,
98
+ iconColor = 'var(--color-text-primary)',
99
+ iconPosition = 'right',
100
+ borderRadius,
101
+ boxShadow = '',
102
+
103
+ // ===Properties===
104
+ // Data
105
+ id = '',
106
+ label = '',
107
+ subLabel = '',
108
+ placeholder = '',
109
+ value = $bindable(''),
110
+ prefix = '',
111
+ suffix = '',
112
+
113
+ // Suggestions
114
+ suggestions = [],
115
+ minChars = 1,
116
+ maxSuggestions = 8,
117
+ showSuggestionsOnFocus = true,
118
+ emptyMessage = 'Tidak ada hasil',
119
+ closeOnSelect = true,
120
+ loadingSuggestions = false,
121
+
122
+ // Events
123
+ onSelectSuggestion,
124
+ onclick,
125
+ oninput,
126
+ onchange,
127
+ onfocus,
128
+ onblur,
129
+ onkeydown,
130
+
131
+ // Validation
132
+ isMandatory = false,
133
+ regex,
134
+ minLength,
135
+ maxLength,
136
+ customValidation,
137
+
138
+ // Additional Actions
139
+ isLoading = false,
140
+ isShow = true,
141
+ disabled = false,
142
+ readOnly = false,
143
+ tooltip = '',
144
+
145
+ class: className = '',
146
+ style: customStyle = '',
147
+ ...props
148
+ }: Props = $props();
149
+
150
+ let inputValue = $state(value);
151
+ let errorMessage = $state('');
152
+ let isFocused = $state(false);
153
+ let showList = $state(false);
154
+ let activeIndex = $state(-1);
155
+ let wrapperEl: HTMLDivElement | null = $state(null);
156
+
157
+ $effect(() => {
158
+ inputValue = value;
159
+ });
160
+
161
+ const sizeConfig = $derived(() => {
162
+ switch (size) {
163
+ case 48:
164
+ return {
165
+ height: '48px',
166
+ labelFontSize: '16px',
167
+ inputFontSize: '16px',
168
+ borderRadius: 8,
169
+ padding: '8px 12px'
170
+ };
171
+ case 40:
172
+ return {
173
+ height: '40px',
174
+ labelFontSize: '14px',
175
+ inputFontSize: '16px',
176
+ borderRadius: 8,
177
+ padding: '6px 12px'
178
+ };
179
+ case 32:
180
+ return {
181
+ height: '32px',
182
+ labelFontSize: '14px',
183
+ inputFontSize: '14px',
184
+ borderRadius: 6,
185
+ padding: '6px 12px'
186
+ };
187
+ default:
188
+ return {
189
+ height: '40px',
190
+ labelFontSize: '14px',
191
+ inputFontSize: '16px',
192
+ borderRadius: 8,
193
+ padding: '6px 12px'
194
+ };
195
+ }
196
+ });
197
+
198
+ const wrapperStyles = $derived(() => {
199
+ const styles: string[] = [];
200
+ styles.push(`height: ${sizeConfig().height};`);
201
+
202
+ if (borderColor) {
203
+ styles.push(`border-color: ${borderColor};`);
204
+ }
205
+
206
+ const radius = borderRadius || sizeConfig().borderRadius;
207
+ styles.push(`border-radius: ${radius}px;`);
208
+
209
+ if (boxShadow) {
210
+ styles.push(`box-shadow: ${boxShadow};`);
211
+ }
212
+
213
+ if (isFocused && accentColor) {
214
+ styles.push(`border-color: ${accentColor};`);
215
+ styles.push(`box-shadow: 0 0 0 2px ${accentColor}40;`);
216
+ }
217
+
218
+ if (errorMessage && errorColor) {
219
+ styles.push(`border-color: ${errorColor};`);
220
+ }
221
+
222
+ if (disabled) {
223
+ styles.push(`background: var(--color-bg-disabled);`);
224
+ } else if (backgroundColor) {
225
+ styles.push(`background: ${backgroundColor};`);
226
+ }
227
+
228
+ if (customStyle) {
229
+ styles.push(customStyle);
230
+ }
231
+
232
+ return styles.join(' ');
233
+ });
234
+
235
+ const prefixStyles = $derived(() => {
236
+ const radius = borderRadius || sizeConfig().borderRadius;
237
+ return `border-radius: ${radius}px 0 0 ${radius}px;`;
238
+ });
239
+
240
+ const suffixStyles = $derived(() => {
241
+ const radius = borderRadius || sizeConfig().borderRadius;
242
+ return `border-radius: 0 ${radius}px ${radius}px 0;`;
243
+ });
244
+
245
+ const inputStyles = $derived(() => {
246
+ const styles: string[] = [];
247
+ styles.push(`font-size: ${sizeConfig().inputFontSize};`);
248
+ styles.push(`padding: ${sizeConfig().padding};`);
249
+
250
+ if (textColor) {
251
+ styles.push(`color: ${textColor};`);
252
+ }
253
+
254
+ return styles.join(' ');
255
+ });
256
+
257
+ const labelStyles = $derived(() => {
258
+ const styles: string[] = [];
259
+ styles.push(`font-size: ${sizeConfig().labelFontSize};`);
260
+
261
+ if (labelColor) {
262
+ styles.push(`color: ${labelColor};`);
263
+ }
264
+
265
+ return styles.join(' ');
266
+ });
267
+
268
+ const wrapperClasses = $derived(() => {
269
+ const classes = ['input-wrapper'];
270
+
271
+ if (isLoading) {
272
+ classes.push('input-loading');
273
+ }
274
+
275
+ if (disabled) {
276
+ classes.push('input-disabled');
277
+ }
278
+
279
+ if (errorMessage) {
280
+ classes.push('input-error');
281
+ }
282
+
283
+ if (className) {
284
+ classes.push(className);
285
+ }
286
+
287
+ return classes.join(' ');
288
+ });
289
+
290
+ function normalizeSuggestion(option: SuggestionOption) {
291
+ if (typeof option === 'string') {
292
+ return { label: option, value: option, raw: option };
293
+ }
294
+ const val = option.value ?? option.label;
295
+ return { label: option.label, value: val, raw: option };
296
+ }
297
+
298
+ const filteredSuggestions = $derived(() => {
299
+ const normalized = suggestions.map(normalizeSuggestion);
300
+ const query = inputValue.trim().toLowerCase();
301
+
302
+ if (query.length < minChars && !(showSuggestionsOnFocus && isFocused && minChars === 0)) {
303
+ return [];
304
+ }
305
+
306
+ const result = query
307
+ ? normalized.filter((item) => item.label.toLowerCase().includes(query))
308
+ : normalized;
309
+ return result.slice(0, maxSuggestions);
310
+ });
311
+
312
+ const shouldShowEmpty = $derived(() => {
313
+ const query = inputValue.trim();
314
+ if (!showList) return false;
315
+ if (query.length < minChars && !(showSuggestionsOnFocus && isFocused && minChars === 0))
316
+ return false;
317
+ return !loadingSuggestions && suggestions.length > 0 && filteredSuggestions().length === 0;
318
+ });
319
+
320
+ function openSuggestions() {
321
+ if (disabled || isLoading) return;
322
+ showList = true;
323
+ }
324
+
325
+ function closeSuggestions() {
326
+ showList = false;
327
+ activeIndex = -1;
328
+ }
329
+
330
+ function validateInput(value: string): string {
331
+ if (customValidation) {
332
+ const customError = customValidation(value);
333
+ if (customError) {
334
+ return customError;
335
+ }
336
+ }
337
+
338
+ if (isMandatory && !value.trim()) {
339
+ return 'Field ini wajib diisi';
340
+ }
341
+
342
+ if (minLength !== undefined && value.length < minLength) {
343
+ return `Minimal ${minLength} karakter`;
344
+ }
345
+
346
+ if (maxLength !== undefined && value.length > maxLength) {
347
+ return `Maksimal ${maxLength} karakter`;
348
+ }
349
+
350
+ if (regex && value && !regex.test(value)) {
351
+ return 'Format tidak valid';
352
+ }
353
+
354
+ return '';
355
+ }
356
+
357
+ function handleInput(e: Event) {
358
+ const target = e.target as HTMLInputElement;
359
+ const nextValue = target.value;
360
+ inputValue = nextValue;
361
+ // keep parent in sync
362
+ value = inputValue;
363
+ errorMessage = validateInput(nextValue);
364
+ openSuggestions();
365
+ oninput?.(e);
366
+ }
367
+
368
+ function handleChange(e: Event) {
369
+ const target = e.target as HTMLInputElement;
370
+ const value = target.value;
371
+ errorMessage = validateInput(value);
372
+ onchange?.(e);
373
+ }
374
+
375
+ function handleFocus(e: FocusEvent) {
376
+ isFocused = true;
377
+ if (showSuggestionsOnFocus) {
378
+ openSuggestions();
379
+ }
380
+ onfocus?.(e);
381
+ }
382
+
383
+ function handleBlur(e: FocusEvent) {
384
+ isFocused = false;
385
+ const target = e.target as HTMLInputElement;
386
+ errorMessage = validateInput(target.value);
387
+ onblur?.(e);
388
+ }
389
+
390
+ function handleKeyDown(e: KeyboardEvent) {
391
+ const list = filteredSuggestions();
392
+ if (list.length > 0) {
393
+ if (e.key === 'ArrowDown') {
394
+ e.preventDefault();
395
+ openSuggestions();
396
+ activeIndex = Math.min(activeIndex + 1, list.length - 1);
397
+ } else if (e.key === 'ArrowUp') {
398
+ e.preventDefault();
399
+ openSuggestions();
400
+ activeIndex = Math.max(activeIndex - 1, 0);
401
+ } else if (e.key === 'Enter' && showList && activeIndex >= 0) {
402
+ e.preventDefault();
403
+ selectSuggestion(list[activeIndex]);
404
+ } else if (e.key === 'Escape') {
405
+ closeSuggestions();
406
+ }
407
+ }
408
+ onkeydown?.(e);
409
+ }
410
+
411
+ function handleClick(e: MouseEvent) {
412
+ if (!disabled && !isLoading && onclick) {
413
+ onclick(e);
414
+ }
415
+ }
416
+
417
+ function selectSuggestion(item: { label: string; value: string; raw: SuggestionOption }) {
418
+ inputValue = item.value;
419
+ value = item.value;
420
+ onSelectSuggestion?.(item.value, item.raw);
421
+ if (closeOnSelect) {
422
+ closeSuggestions();
423
+ }
424
+ }
425
+
426
+ function handleClickOutside(e: MouseEvent) {
427
+ if (wrapperEl && !wrapperEl.contains(e.target as Node)) {
428
+ closeSuggestions();
429
+ }
430
+ }
431
+
432
+ $effect(() => {
433
+ if (showList) {
434
+ setTimeout(() => document.addEventListener('click', handleClickOutside), 10);
435
+ } else {
436
+ document.removeEventListener('click', handleClickOutside);
437
+ }
438
+ return () => document.removeEventListener('click', handleClickOutside);
439
+ });
440
+ </script>
441
+
442
+ {#if isShow}
443
+ <div class="input-container" class:input-side={aligment === 'side'}>
444
+ {#if label}
445
+ <label
446
+ for={id}
447
+ class="input-label"
448
+ class:label-left={position === 'left'}
449
+ class:label-right={position === 'right'}
450
+ style={labelStyles()}
451
+ >
452
+ {label}
453
+ {#if subLabel}
454
+ <span class="label-subLabel">{subLabel}</span>
455
+ {/if}
456
+ {#if isMandatory}
457
+ <span class="label-required">*</span>
458
+ {/if}
459
+ </label>
460
+ {/if}
461
+ <div class={wrapperClasses()} style={wrapperStyles()} bind:this={wrapperEl}>
462
+ {#if prefix}
463
+ <div class="prefix-wrapper" style={prefixStyles()}>
464
+ <span class="prefix-text">{prefix}</span>
465
+ </div>
466
+ {/if}
467
+ {#if isLoading && iconPosition === 'left'}
468
+ <div class="input-loader input-icon-left">
469
+ <span class="loader-spinner"></span>
470
+ </div>
471
+ {:else if icon && iconPosition === 'left' && !prefix}
472
+ <div class="input-icon input-icon-left">
473
+ <Icon name={icon} color={iconColor || 'currentColor'} width={16} height={16} />
474
+ </div>
475
+ {/if}
476
+ <input
477
+ {id}
478
+ class="input-field"
479
+ style={inputStyles()}
480
+ value={inputValue}
481
+ {placeholder}
482
+ disabled={disabled || isLoading}
483
+ readonly={readOnly}
484
+ oninput={handleInput}
485
+ onchange={handleChange}
486
+ onfocus={handleFocus}
487
+ onblur={handleBlur}
488
+ onkeydown={handleKeyDown}
489
+ onclick={handleClick}
490
+ title={tooltip || ''}
491
+ {...props}
492
+ />
493
+ {#if isLoading && iconPosition === 'right'}
494
+ <div class="input-loader input-icon-right">
495
+ <span class="loader-spinner"></span>
496
+ </div>
497
+ {:else if icon && iconPosition === 'right' && !suffix}
498
+ <div class="input-icon input-icon-right">
499
+ <Icon name={icon} color={iconColor || 'currentColor'} width={16} height={16} />
500
+ </div>
501
+ {/if}
502
+ {#if suffix}
503
+ <div class="suffix-wrapper" style={suffixStyles()}>
504
+ <span class="suffix-text">{suffix}</span>
505
+ </div>
506
+ {/if}
507
+
508
+ {#if showList}
509
+ <div class="suggestions-menu">
510
+ {#if loadingSuggestions}
511
+ <div class="suggestion-loading">Loading...</div>
512
+ {:else if filteredSuggestions().length}
513
+ {#each filteredSuggestions() as item, index (item.value)}
514
+ <button
515
+ type="button"
516
+ class="suggestion-item"
517
+ class:active={index === activeIndex}
518
+ onmousedown={(e) => {
519
+ e.preventDefault();
520
+ selectSuggestion(item);
521
+ }}
522
+ >
523
+ {item.label}
524
+ </button>
525
+ {/each}
526
+ {:else if shouldShowEmpty()}
527
+ <div class="suggestion-empty">{emptyMessage}</div>
528
+ {/if}
529
+ </div>
530
+ {/if}
531
+ </div>
532
+ {#if errorMessage}
533
+ <p class="error-message" style={errorColor ? `color: ${errorColor};` : ''}>
534
+ {errorMessage}
535
+ </p>
536
+ {/if}
537
+ </div>
538
+ {/if}
@@ -0,0 +1,61 @@
1
+ import type { TIconName } from '../../../icons/index.js';
2
+ import './TextInputSuggestion.css';
3
+ import type { HTMLInputAttributes } from 'svelte/elements';
4
+ type SuggestionOption = string | {
5
+ label: string;
6
+ id?: string | number;
7
+ value?: string;
8
+ detail?: unknown;
9
+ };
10
+ interface Props extends HTMLInputAttributes {
11
+ labelColor?: string;
12
+ aligment?: 'side' | 'top';
13
+ position?: 'left' | 'right';
14
+ size?: 48 | 40 | 32;
15
+ backgroundColor?: string;
16
+ borderColor?: string;
17
+ accentColor?: string;
18
+ textColor?: string;
19
+ errorColor?: string;
20
+ icon?: TIconName;
21
+ iconColor?: string;
22
+ iconPosition?: 'left' | 'right';
23
+ borderRadius?: number;
24
+ boxShadow?: string;
25
+ id?: string;
26
+ label?: string;
27
+ subLabel?: string;
28
+ placeholder?: string;
29
+ value?: string;
30
+ prefix?: string;
31
+ suffix?: string;
32
+ suggestions?: SuggestionOption[];
33
+ minChars?: number;
34
+ maxSuggestions?: number;
35
+ showSuggestionsOnFocus?: boolean;
36
+ emptyMessage?: string;
37
+ closeOnSelect?: boolean;
38
+ loadingSuggestions?: boolean;
39
+ onSelectSuggestion?: (value: string, option: SuggestionOption) => void;
40
+ onclick?: (event: MouseEvent) => void;
41
+ oninput?: (event: Event) => void;
42
+ onchange?: (event: Event) => void;
43
+ onfocus?: (event: FocusEvent) => void;
44
+ onblur?: (event: FocusEvent) => void;
45
+ onkeydown?: (event: KeyboardEvent) => void;
46
+ isMandatory?: boolean;
47
+ regex?: RegExp;
48
+ minLength?: number;
49
+ maxLength?: number;
50
+ customValidation?: (value: string) => string | null;
51
+ isLoading?: boolean;
52
+ isShow?: boolean;
53
+ disabled?: boolean;
54
+ readOnly?: boolean;
55
+ tooltip?: string;
56
+ class?: string;
57
+ style?: string;
58
+ }
59
+ declare const TextInputSuggestion: import("svelte").Component<Props, {}, "value">;
60
+ type TextInputSuggestion = ReturnType<typeof TextInputSuggestion>;
61
+ export default TextInputSuggestion;
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  export { default as Button } from './components/Button/Button.svelte';
2
2
  export { default as Icon } from './components/Icon/Icon.svelte';
3
3
  export { default as TextInput } from './components/inputs/TextInput/TextInput.svelte';
4
+ export { default as TextInputSuggestion } from './components/inputs/TextInputSuggestion/TextInputSuggestion.svelte';
4
5
  export { default as TextareaInput } from './components/inputs/TextareaInput/TextareaInput.svelte';
5
6
  export { default as SelectInput } from './components/inputs/SelectInput/SelectInput.svelte';
6
7
  export { default as MultiSelectInput } from './components/inputs/MultiSelectInput/MultiSelectInput.svelte';
8
+ export type { IMultiSelectOption } from './components/inputs/MultiSelectInput/MultiSelectInput.ts';
7
9
  export { default as Radio } from './components/inputs/Radio/Radio.svelte';
8
10
  export { default as Toggle } from './components/inputs/Toggle/Toggle.svelte';
9
11
  export { default as Segmented } from './components/inputs/Segmented/Segmented.svelte';
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  export { default as Button } from './components/Button/Button.svelte';
3
3
  export { default as Icon } from './components/Icon/Icon.svelte';
4
4
  export { default as TextInput } from './components/inputs/TextInput/TextInput.svelte';
5
+ export { default as TextInputSuggestion } from './components/inputs/TextInputSuggestion/TextInputSuggestion.svelte';
5
6
  export { default as TextareaInput } from './components/inputs/TextareaInput/TextareaInput.svelte';
6
7
  export { default as SelectInput } from './components/inputs/SelectInput/SelectInput.svelte';
7
8
  export { default as MultiSelectInput } from './components/inputs/MultiSelectInput/MultiSelectInput.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mertani-web-toolkit",
3
- "version": "0.1.48",
3
+ "version": "0.1.49",
4
4
  "homepage": "https://storybook.mertani.com/",
5
5
  "scripts": {
6
6
  "dev": "vite dev",