@juspay/svelte-ui-components 2.14.0 → 2.18.0

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 (41) hide show
  1. package/dist/Button/Button.svelte +14 -0
  2. package/dist/Button/properties.d.ts +2 -0
  3. package/dist/Card/Card.svelte +51 -0
  4. package/dist/Card/Card.svelte.d.ts +4 -0
  5. package/dist/Card/properties.d.ts +10 -0
  6. package/dist/Card/properties.js +1 -0
  7. package/dist/ColorPicker/ColorPicker.svelte +583 -0
  8. package/dist/ColorPicker/ColorPicker.svelte.d.ts +4 -0
  9. package/dist/ColorPicker/properties.d.ts +15 -0
  10. package/dist/ColorPicker/properties.js +1 -0
  11. package/dist/Combobox/Combobox.svelte +432 -0
  12. package/dist/Combobox/Combobox.svelte.d.ts +6 -0
  13. package/dist/Combobox/properties.d.ts +42 -0
  14. package/dist/Combobox/properties.js +1 -0
  15. package/dist/EmptyState/EmptyState.svelte +66 -0
  16. package/dist/EmptyState/EmptyState.svelte.d.ts +4 -0
  17. package/dist/EmptyState/properties.d.ts +11 -0
  18. package/dist/EmptyState/properties.js +1 -0
  19. package/dist/Icon/Icon.svelte +22 -2
  20. package/dist/Icon/properties.d.ts +3 -4
  21. package/dist/Input/Input.svelte +30 -5
  22. package/dist/Input/Input.svelte.d.ts +1 -0
  23. package/dist/Input/properties.d.ts +11 -2
  24. package/dist/ListItem/ListItem.svelte +9 -3
  25. package/dist/ListItem/properties.d.ts +3 -0
  26. package/dist/Menu/Menu.svelte +17 -6
  27. package/dist/Menu/properties.d.ts +4 -0
  28. package/dist/Slider/Slider.svelte +9 -6
  29. package/dist/SplitInput/SplitInput.svelte +225 -0
  30. package/dist/SplitInput/SplitInput.svelte.d.ts +7 -0
  31. package/dist/SplitInput/properties.d.ts +20 -0
  32. package/dist/SplitInput/properties.js +1 -0
  33. package/dist/Tabs/Tabs.svelte +22 -6
  34. package/dist/Tabs/properties.d.ts +5 -0
  35. package/dist/assets/swap-vertical.svg +6 -0
  36. package/dist/index.d.ts +10 -0
  37. package/dist/index.js +5 -0
  38. package/dist/types.d.ts +15 -1
  39. package/dist/utils.d.ts +10 -1
  40. package/dist/utils.js +118 -0
  41. package/package.json +1 -1
@@ -17,6 +17,8 @@
17
17
  addFocusColor = false,
18
18
  maxLength = 1000,
19
19
  minLength = 0,
20
+ min,
21
+ max,
20
22
  actionInput = false,
21
23
  useTextArea = false,
22
24
  autoComplete = 'on',
@@ -26,12 +28,18 @@
26
28
  textViewPresentation = [],
27
29
  onFocus = () => {},
28
30
  onFocusout = () => {},
31
+ onBlur = () => {},
29
32
  onInput = () => {},
30
33
  onPaste = () => {},
31
34
  onStateChange = () => {},
32
35
  onClick = () => {},
33
36
  onKeyDown = () => {},
34
- classes
37
+ classes,
38
+ role,
39
+ ariaExpanded,
40
+ ariaAutocomplete,
41
+ ariaControls,
42
+ ariaActivedescendant
35
43
  }: InputProperties = $props();
36
44
 
37
45
  export function focus() {
@@ -51,6 +59,10 @@
51
59
  }
52
60
  }
53
61
 
62
+ export function getInputRef(): HTMLInputElement | HTMLTextAreaElement | null {
63
+ return inputElement;
64
+ }
65
+
54
66
  let inputElement: HTMLInputElement | HTMLTextAreaElement | null = $state(null);
55
67
 
56
68
  let validationState = $derived.by(() => {
@@ -64,7 +76,7 @@
64
76
  if (
65
77
  valueValidation === 'InProgress' &&
66
78
  value.length > 0 &&
67
- inputElement &&
79
+ inputElement !== null &&
68
80
  inputElement !== document.activeElement
69
81
  ) {
70
82
  return 'Invalid';
@@ -171,6 +183,7 @@
171
183
  validationState = 'Invalid';
172
184
  }
173
185
  onFocusout(event);
186
+ onBlur(event);
174
187
  }
175
188
 
176
189
  let _validationStateForCallback = $derived.by(() => {
@@ -197,6 +210,11 @@
197
210
  {placeholder}
198
211
  autocomplete={autoComplete}
199
212
  {name}
213
+ {role}
214
+ aria-expanded={ariaExpanded}
215
+ aria-autocomplete={ariaAutocomplete}
216
+ aria-controls={ariaControls}
217
+ aria-activedescendant={ariaActivedescendant}
200
218
  onfocus={onFocus}
201
219
  onfocusout={_onFocusOut}
202
220
  oninput={handleOnInput}
@@ -217,6 +235,11 @@
217
235
  {placeholder}
218
236
  autocomplete={autoComplete}
219
237
  {name}
238
+ {role}
239
+ aria-expanded={ariaExpanded}
240
+ aria-autocomplete={ariaAutocomplete}
241
+ aria-controls={ariaControls}
242
+ aria-activedescendant={ariaActivedescendant}
220
243
  onfocus={onFocus}
221
244
  onfocusout={_onFocusOut}
222
245
  oninput={handleOnInput}
@@ -229,6 +252,8 @@
229
252
  bind:this={inputElement}
230
253
  maxlength={dataType === 'tel' ? null : maxLength}
231
254
  minlength={minLength}
255
+ {min}
256
+ {max}
232
257
  />
233
258
  {/if}
234
259
 
@@ -293,9 +318,9 @@
293
318
  .input-container {
294
319
  display: flex;
295
320
  flex-direction: column;
296
- margin: var(--input-container-margin);
297
- padding: var(--input-container-padding);
298
- width: var(--input-container-width);
321
+ margin: var(--input-container-margin, 0);
322
+ padding: var(--input-container-padding, 0);
323
+ width: var(--input-container-width, fit-content);
299
324
  }
300
325
 
301
326
  .label {
@@ -2,6 +2,7 @@ import type { InputProperties } from './properties';
2
2
  declare const Input: import("svelte").Component<InputProperties, {
3
3
  focus: () => void;
4
4
  blur: () => void;
5
+ getInputRef: () => HTMLInputElement | HTMLTextAreaElement | null;
5
6
  }, "value">;
6
7
  type Input = ReturnType<typeof Input>;
7
8
  export default Input;
@@ -1,4 +1,5 @@
1
- import type { AutoCompleteType, CustomValidator, InputDataType, TextTransformer, ValidationState } from '../types';
1
+ import type { CustomValidator, InputDataType, TextTransformer, ValidationState } from '../types';
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
3
  export type InputProperties = OptionalInputProperties & InputEventProperties & MandatoryInputProperties;
3
4
  export type MandatoryInputProperties = {
4
5
  value: string;
@@ -16,19 +17,27 @@ export type OptionalInputProperties = {
16
17
  addFocusColor?: boolean;
17
18
  maxLength?: number;
18
19
  minLength?: number;
20
+ min?: number;
21
+ max?: number;
19
22
  actionInput?: boolean;
20
23
  useTextArea?: boolean;
21
- autoComplete?: AutoCompleteType;
24
+ autoComplete?: HTMLInputAttributes['autocomplete'];
22
25
  name?: string;
23
26
  textTransformers?: TextTransformer[];
24
27
  textViewPresentation?: TextTransformer[];
25
28
  testId?: string;
26
29
  classes?: string;
30
+ role?: string;
31
+ ariaExpanded?: boolean;
32
+ ariaAutocomplete?: 'none' | 'inline' | 'list' | 'both';
33
+ ariaControls?: string | null;
34
+ ariaActivedescendant?: string | null;
27
35
  };
28
36
  export type InputEventProperties = {
29
37
  onInput?: (value: string, event: Event) => void;
30
38
  onFocus?: (event: FocusEvent) => void;
31
39
  onFocusout?: (event: FocusEvent) => void;
40
+ onBlur?: (event: FocusEvent) => void;
32
41
  onPaste?: (event: ClipboardEvent) => void;
33
42
  onClick?: (event: MouseEvent) => void;
34
43
  onStateChange?: (state: ValidationState) => void;
@@ -30,7 +30,10 @@
30
30
  onitemClick,
31
31
  ontopSectionClick,
32
32
  onkeydown,
33
- classes
33
+ classes,
34
+ role: itemRole,
35
+ ariaSelected,
36
+ id
34
37
  }: ListItemProperties = $props();
35
38
 
36
39
  function handleLeftImageClick(event: MouseEvent): void {
@@ -59,13 +62,16 @@
59
62
  {#if showLoader}
60
63
  <div class="item-loader"></div>
61
64
  {/if}
65
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
62
66
  <div
63
67
  class="item"
64
68
  class:prevent-focus={preventFocus}
65
69
  onclick={handleItemClick}
66
70
  {onkeydown}
67
- role="button"
68
- tabindex="0"
71
+ role={itemRole ?? 'button'}
72
+ tabindex={itemRole === 'option' ? -1 : 0}
73
+ aria-selected={ariaSelected}
74
+ {id}
69
75
  data-pw={testId}
70
76
  >
71
77
  <div
@@ -20,6 +20,9 @@ export type ListItemProperties = ListItemEventProperties & {
20
20
  rightContent?: Snippet;
21
21
  bottomContent?: Snippet;
22
22
  classes?: string;
23
+ role?: string;
24
+ ariaSelected?: boolean;
25
+ id?: string;
23
26
  };
24
27
  export type ListItemEventProperties = {
25
28
  onleftImageClick?: (event: MouseEvent) => void;
@@ -12,9 +12,14 @@
12
12
  onselect,
13
13
  onopen,
14
14
  onclose,
15
- classes
15
+ classes,
16
+ role: menuRole = 'menu',
17
+ ariaLabel: menuAriaLabel,
18
+ id: menuId
16
19
  }: MenuProperties = $props();
17
20
 
21
+ let itemRole = $derived(menuRole === 'listbox' ? 'option' : 'menuitem');
22
+
18
23
  let menuContainerEl: HTMLDivElement | null = $state(null);
19
24
  let menuListEl: HTMLDivElement | null = $state(null);
20
25
  let triggerEl: HTMLDivElement | null = $state(null);
@@ -70,7 +75,7 @@
70
75
  return;
71
76
  }
72
77
  const focusableItems = menuListEl.querySelectorAll(
73
- '[role="menuitem"]:not([aria-disabled="true"])'
78
+ `[role="${itemRole}"]:not([aria-disabled="true"])`
74
79
  );
75
80
  const item = focusableItems.item(index);
76
81
  if (index >= 0 && index < focusableItems.length && item instanceof HTMLElement) {
@@ -212,7 +217,9 @@
212
217
  <div
213
218
  class="menu-dropdown"
214
219
  bind:this={menuListEl}
215
- role="menu"
220
+ role={menuRole}
221
+ id={menuId}
222
+ aria-label={menuAriaLabel}
216
223
  tabindex="-1"
217
224
  onkeydown={handleMenuKeydown}
218
225
  >
@@ -220,14 +227,18 @@
220
227
  {#if item.separator === true}
221
228
  <div class="menu-separator" role="separator"></div>
222
229
  {:else}
223
- <!-- svelte-ignore a11y_click_events_have_key_events -->
230
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
224
231
  <div
225
232
  class="menu-item"
226
233
  class:menu-item-danger={item.danger === true}
227
234
  class:menu-item-disabled={item.disabled === true}
228
- role="menuitem"
229
- tabindex={item.disabled === true ? -1 : 0}
235
+ role={itemRole}
236
+ id={item.id}
237
+ tabindex={item.disabled === true || menuRole === 'listbox' ? -1 : 0}
230
238
  aria-disabled={item.disabled === true ? 'true' : null}
239
+ aria-selected={menuRole === 'listbox' && focusedIndex === getSelectableIndex(item)
240
+ ? true
241
+ : null}
231
242
  onclick={() => selectItem(item)}
232
243
  onfocus={() => {
233
244
  if (item.disabled !== true) {
@@ -6,6 +6,7 @@ export type MenuItem = {
6
6
  disabled?: boolean;
7
7
  danger?: boolean;
8
8
  separator?: boolean;
9
+ id?: string;
9
10
  };
10
11
  export type MenuProperties = MandatoryMenuProperties & OptionalMenuProperties & MenuEventProperties;
11
12
  export type MandatoryMenuProperties = {
@@ -16,6 +17,9 @@ export type OptionalMenuProperties = {
16
17
  testId?: string;
17
18
  trigger?: Snippet;
18
19
  classes?: string;
20
+ role?: 'menu' | 'listbox';
21
+ ariaLabel?: string;
22
+ id?: string;
19
23
  };
20
24
  export type MenuEventProperties = {
21
25
  onselect?: (item: MenuItem) => void;
@@ -70,12 +70,15 @@
70
70
  width: 100%;
71
71
  height: var(--slider-track-height, 6px);
72
72
  border-radius: var(--slider-track-border-radius, 3px);
73
- background: linear-gradient(
74
- to right,
75
- var(--slider-track-active-color, #2196f3) 0%,
76
- var(--slider-track-active-color, #2196f3) var(--slider-fill-percent, 0%),
77
- var(--slider-track-background, #e0e0e0) var(--slider-fill-percent, 0%),
78
- var(--slider-track-background, #e0e0e0) 100%
73
+ background: var(
74
+ --slider-track,
75
+ linear-gradient(
76
+ to right,
77
+ var(--slider-track-active-color, #2196f3) 0%,
78
+ var(--slider-track-active-color, #2196f3) var(--slider-fill-percent, 0%),
79
+ var(--slider-track-background, #e0e0e0) var(--slider-fill-percent, 0%),
80
+ var(--slider-track-background, #e0e0e0) 100%
81
+ )
79
82
  );
80
83
  outline: none;
81
84
  transition: var(--slider-transition, background 0.2s ease);
@@ -0,0 +1,225 @@
1
+ <script lang="ts">
2
+ import Input from '../Input/Input.svelte';
3
+ import type { FieldConfig, SplitInputProperties } from './properties';
4
+
5
+ let {
6
+ values = $bindable([]),
7
+ fields,
8
+ length = 4,
9
+ disabled = false,
10
+ autoAdvance = false,
11
+ separator,
12
+ testId,
13
+ classes,
14
+ onchange,
15
+ oninput,
16
+ oncomplete
17
+ }: SplitInputProperties = $props();
18
+
19
+ let fieldCount = $derived(typeof fields !== 'undefined' ? fields.length : length);
20
+
21
+ let fieldConfigs: FieldConfig[] = $derived(
22
+ typeof fields !== 'undefined'
23
+ ? fields
24
+ : Array.from({ length: fieldCount }, () => ({
25
+ dataType: 'tel' as const,
26
+ maxLength: 1
27
+ }))
28
+ );
29
+
30
+ let inputRefs: (ReturnType<typeof Input> | null)[] = $state(
31
+ Array.from({ length: 20 }, () => null)
32
+ );
33
+
34
+ // ── Value helpers ───────────────────────────────────────────────
35
+
36
+ function getFieldValue(index: number): string {
37
+ return values.at(index) ?? '';
38
+ }
39
+
40
+ function commitValues() {
41
+ const filled = values.filter((v) => v.length > 0);
42
+ oninput?.([...values]);
43
+ onchange?.([...values]);
44
+ if (filled.length === fieldCount) {
45
+ oncomplete?.([...values]);
46
+ }
47
+ }
48
+
49
+ // ── Focus management ────────────────────────────────────────────
50
+
51
+ function focusField(index: number) {
52
+ if (index >= 0 && index < fieldCount) {
53
+ inputRefs.at(index)?.focus();
54
+ }
55
+ }
56
+
57
+ // ── Input handler ───────────────────────────────────────────────
58
+
59
+ function handleFieldInput(index: number, inputValue: string) {
60
+ const config = fieldConfigs.at(index);
61
+ if (typeof config === 'undefined') {
62
+ return;
63
+ }
64
+ const maxLen = config.maxLength ?? 1000;
65
+
66
+ if (autoAdvance && maxLen === 1 && inputValue.length > 1) {
67
+ const chars = inputValue.replace(/\s/g, '');
68
+ for (let i = 0; i < fieldCount; i++) {
69
+ values[i] = chars.charAt(i);
70
+ }
71
+ focusField(Math.min(chars.length, fieldCount) - 1);
72
+ } else {
73
+ values[index] = inputValue;
74
+ if (autoAdvance && maxLen === 1 && inputValue.length > 0 && index < fieldCount - 1) {
75
+ focusField(index + 1);
76
+ }
77
+ }
78
+ commitValues();
79
+ }
80
+
81
+ // ── Keyboard handler ────────────────────────────────────────────
82
+
83
+ function handleKeyDown(e: KeyboardEvent, index: number) {
84
+ if (e.key === 'Backspace') {
85
+ const current = getFieldValue(index);
86
+ if (current.length === 0 && index > 0) {
87
+ values[index - 1] = '';
88
+ focusField(index - 1);
89
+ commitValues();
90
+ }
91
+ } else if (e.key === 'ArrowLeft' && index > 0) {
92
+ focusField(index - 1);
93
+ } else if (e.key === 'ArrowRight' && index < fieldCount - 1) {
94
+ focusField(index + 1);
95
+ } else if (e.key === 'Tab') {
96
+ if (e.shiftKey && index > 0) {
97
+ e.preventDefault();
98
+ focusField(index - 1);
99
+ } else if (e.shiftKey === false && index < fieldCount - 1) {
100
+ e.preventDefault();
101
+ focusField(index + 1);
102
+ }
103
+ }
104
+ }
105
+
106
+ // ── Paste handler ───────────────────────────────────────────────
107
+
108
+ function handlePaste(e: ClipboardEvent, index: number) {
109
+ if (e.clipboardData === null) {
110
+ return;
111
+ }
112
+ e.preventDefault();
113
+ const pasted = e.clipboardData.getData('text').trim();
114
+
115
+ if (autoAdvance) {
116
+ const chars = pasted.replace(/\s/g, '');
117
+ for (let i = index; i < fieldCount; i++) {
118
+ const charIdx = i - index;
119
+ if (charIdx >= chars.length) {
120
+ break;
121
+ }
122
+ values[i] = chars.charAt(charIdx);
123
+ }
124
+ focusField(Math.min(index + chars.length, fieldCount) - 1);
125
+ } else {
126
+ values[index] = pasted;
127
+ }
128
+ commitValues();
129
+ }
130
+
131
+ // ── Exported methods ────────────────────────────────────────────
132
+
133
+ export function clear() {
134
+ for (let i = 0; i < fieldCount; i++) {
135
+ values[i] = '';
136
+ }
137
+ oninput?.([...values]);
138
+ onchange?.([...values]);
139
+ focusField(0);
140
+ }
141
+
142
+ export function focus() {
143
+ focusField(0);
144
+ }
145
+ </script>
146
+
147
+ <div class="field-group {classes ?? ''}" data-pw={testId}>
148
+ {#each fieldConfigs as config, index (index)}
149
+ {#if typeof separator === 'string' && index > 0}
150
+ <span class="field-group-separator">{separator}</span>
151
+ {/if}
152
+ <div class="field-group-item">
153
+ <Input
154
+ value={getFieldValue(index)}
155
+ dataType={config.dataType ?? 'text'}
156
+ maxLength={config.maxLength ?? 1000}
157
+ min={config.min}
158
+ max={config.max}
159
+ placeholder={config.placeholder}
160
+ validationPattern={config.validationPattern ?? null}
161
+ validators={config.validators ?? []}
162
+ disable={disabled}
163
+ actionInput={true}
164
+ classes="field-group-input"
165
+ onInput={(v) => handleFieldInput(index, v)}
166
+ onKeyDown={(e) => handleKeyDown(e, index)}
167
+ onPaste={(e) => handlePaste(e, index)}
168
+ bind:this={inputRefs[index]}
169
+ />
170
+ {#if typeof config.label === 'string'}
171
+ <span class="field-group-label">{config.label}</span>
172
+ {/if}
173
+ </div>
174
+ {/each}
175
+ </div>
176
+
177
+ <style>
178
+ .field-group {
179
+ display: flex;
180
+ align-items: flex-start;
181
+ gap: var(--field-group-gap, 8px);
182
+ width: var(--field-group-width, fit-content);
183
+ }
184
+
185
+ .field-group-item {
186
+ display: flex;
187
+ flex-direction: column;
188
+ align-items: center;
189
+ gap: var(--field-group-label-gap, 3px);
190
+ flex: 1;
191
+ min-width: 0;
192
+ }
193
+
194
+ .field-group-item :global(.field-group-input) {
195
+ --input-border: var(--field-group-border, 1px solid #d1d5db);
196
+ --input-radius: var(--field-group-border-radius, 6px);
197
+ --input-focus-border: var(--field-group-focus-border, 1px solid #3b82f6);
198
+ --input-width: var(--field-group-input-width, 100%);
199
+ --input-height: var(--field-group-input-height, 36px);
200
+ --input-text-align: var(--field-group-text-align, center);
201
+ --input-font-size: var(--field-group-font-size, 14px);
202
+ --input-font-weight: var(--field-group-font-weight, 500);
203
+ --input-font-family: var(--field-group-font-family);
204
+ --input-padding: var(--field-group-input-padding, 0 6px);
205
+ --input-margin: 0;
206
+ --input-box-shadow: none;
207
+ }
208
+
209
+ .field-group-label {
210
+ font-size: var(--field-group-label-font-size, 10px);
211
+ font-weight: var(--field-group-label-font-weight, 500);
212
+ color: var(--field-group-label-color, #9ca3af);
213
+ text-transform: uppercase;
214
+ letter-spacing: 0.05em;
215
+ }
216
+
217
+ .field-group-separator {
218
+ display: flex;
219
+ align-items: center;
220
+ height: var(--field-group-input-height, 36px);
221
+ font-size: var(--field-group-separator-font-size, 16px);
222
+ color: var(--field-group-separator-color, #9ca3af);
223
+ flex-shrink: 0;
224
+ }
225
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { SplitInputProperties } from './properties';
2
+ declare const SplitInput: import("svelte").Component<SplitInputProperties, {
3
+ clear: () => void;
4
+ focus: () => void;
5
+ }, "values">;
6
+ type SplitInput = ReturnType<typeof SplitInput>;
7
+ export default SplitInput;
@@ -0,0 +1,20 @@
1
+ import type { OptionalInputProperties } from '../Input/properties';
2
+ export type FieldConfig = Pick<OptionalInputProperties, 'dataType' | 'maxLength' | 'min' | 'max' | 'placeholder' | 'validationPattern' | 'validators' | 'label'>;
3
+ export type SplitInputProperties = MandatorySplitInputProperties & OptionalSplitInputProperties & SplitInputEventProperties;
4
+ export type MandatorySplitInputProperties = {
5
+ values: string[];
6
+ };
7
+ export type OptionalSplitInputProperties = {
8
+ fields?: FieldConfig[];
9
+ length?: number;
10
+ disabled?: boolean;
11
+ autoAdvance?: boolean;
12
+ separator?: string;
13
+ testId?: string;
14
+ classes?: string;
15
+ };
16
+ export type SplitInputEventProperties = {
17
+ onchange?: (values: string[]) => void;
18
+ oninput?: (values: string[]) => void;
19
+ oncomplete?: (values: string[]) => void;
20
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -10,6 +10,7 @@
10
10
  testId,
11
11
  scrollLeftIcon,
12
12
  scrollRightIcon,
13
+ tab,
13
14
  classes,
14
15
  onchange
15
16
  }: TabsProperties = $props();
@@ -46,6 +47,13 @@
46
47
  onchange?.(index, items[index]);
47
48
  }
48
49
 
50
+ function handleKeydown(event: KeyboardEvent, index: number): void {
51
+ if (event.key === 'Enter' || event.key === ' ') {
52
+ event.preventDefault();
53
+ handleTabClick(index);
54
+ }
55
+ }
56
+
49
57
  function initOverflow(node: HTMLDivElement): { destroy: () => void } {
50
58
  scrollContainer = node;
51
59
  updateOverflow();
@@ -83,20 +91,25 @@
83
91
  onscroll={updateOverflow}
84
92
  >
85
93
  {#each items as label, index (index)}
86
- <button
94
+ <div
87
95
  class="tabs-item"
88
96
  class:active={index === activeIndex}
89
97
  role="tab"
90
98
  aria-selected={index === activeIndex}
99
+ aria-disabled={disabled ? true : null}
91
100
  tabindex={index === activeIndex ? 0 : -1}
92
- {disabled}
93
101
  onclick={() => handleTabClick(index)}
102
+ onkeydown={(e) => handleKeydown(e, index)}
94
103
  >
95
- {label}
104
+ {#if tab}
105
+ {@render tab({ label, index, active: index === activeIndex })}
106
+ {:else}
107
+ {label}
108
+ {/if}
96
109
  {#if index === activeIndex}
97
110
  <span class="tabs-indicator"></span>
98
111
  {/if}
99
- </button>
112
+ </div>
100
113
  {/each}
101
114
  </div>
102
115
  {#if canScrollRight}
@@ -197,6 +210,8 @@
197
210
  }
198
211
 
199
212
  .tabs-item {
213
+ display: flex;
214
+ align-items: center;
200
215
  position: relative;
201
216
  padding: var(--tabs-item-padding, 12px 16px);
202
217
  font-size: var(--tabs-item-font-size, 14px);
@@ -210,10 +225,11 @@
210
225
  outline: none;
211
226
  white-space: nowrap;
212
227
  flex-shrink: 0;
228
+ user-select: none;
213
229
  transition: var(--tabs-transition, color 0.2s ease, background 0.2s ease);
214
230
  }
215
231
 
216
- .tabs-item:hover:not(.active):not(:disabled) {
232
+ .tabs-item:hover:not(.active):not([aria-disabled]) {
217
233
  color: var(--tabs-hover-color, #333333);
218
234
  background: var(--tabs-hover-background, #f5f5f5);
219
235
  }
@@ -224,7 +240,7 @@
224
240
  background: var(--tabs-active-background, transparent);
225
241
  }
226
242
 
227
- .tabs-item:disabled {
243
+ .tabs-item[aria-disabled] {
228
244
  cursor: var(--tabs-disabled-cursor, not-allowed);
229
245
  }
230
246
 
@@ -9,6 +9,11 @@ export type OptionalTabsProperties = {
9
9
  testId?: string;
10
10
  scrollLeftIcon?: Snippet;
11
11
  scrollRightIcon?: Snippet;
12
+ tab?: Snippet<[{
13
+ label: string;
14
+ index: number;
15
+ active: boolean;
16
+ }]>;
12
17
  classes?: string;
13
18
  };
14
19
  export type TabsEventProperties = {
@@ -0,0 +1,6 @@
1
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M7 4v16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
3
+ <path d="M3 8l4-4 4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
4
+ <path d="M17 20V4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
5
+ <path d="M13 16l4 4 4-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
6
+ </svg>
package/dist/index.d.ts CHANGED
@@ -51,6 +51,11 @@ export { default as ThemeSwitcher } from './ThemeSwitcher/ThemeSwitcher.svelte';
51
51
  export { default as Book } from './Book/Book.svelte';
52
52
  export { default as Browser } from './Browser/Browser.svelte';
53
53
  export { default as Phone } from './Phone/Phone.svelte';
54
+ export { default as Card } from './Card/Card.svelte';
55
+ export { default as EmptyState } from './EmptyState/EmptyState.svelte';
56
+ export { default as Combobox } from './Combobox/Combobox.svelte';
57
+ export { default as ColorPicker } from './ColorPicker/ColorPicker.svelte';
58
+ export { default as SplitInput } from './SplitInput/SplitInput.svelte';
54
59
  export type * from './Button/properties';
55
60
  export type * from './Modal/properties';
56
61
  export type * from './Input/properties';
@@ -98,4 +103,9 @@ export type * from './Book/properties';
98
103
  export type * from './Browser/properties';
99
104
  export type * from './Phone/properties';
100
105
  export type * from './Loader/properties';
106
+ export type * from './Card/properties';
107
+ export type * from './EmptyState/properties';
108
+ export type * from './Combobox/properties';
109
+ export type * from './ColorPicker/properties';
110
+ export type * from './SplitInput/properties';
101
111
  export { validateInput } from './utils';
package/dist/index.js CHANGED
@@ -51,4 +51,9 @@ export { default as ThemeSwitcher } from './ThemeSwitcher/ThemeSwitcher.svelte';
51
51
  export { default as Book } from './Book/Book.svelte';
52
52
  export { default as Browser } from './Browser/Browser.svelte';
53
53
  export { default as Phone } from './Phone/Phone.svelte';
54
+ export { default as Card } from './Card/Card.svelte';
55
+ export { default as EmptyState } from './EmptyState/EmptyState.svelte';
56
+ export { default as Combobox } from './Combobox/Combobox.svelte';
57
+ export { default as ColorPicker } from './ColorPicker/ColorPicker.svelte';
58
+ export { default as SplitInput } from './SplitInput/SplitInput.svelte';
54
59
  export { validateInput } from './utils';