@softwareone/spi-sv5-library 1.3.0 → 1.3.2

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.
@@ -22,9 +22,7 @@
22
22
  {/if}
23
23
  {#if infoTooltip}
24
24
  <Tooltip content={infoTooltip} width="sm">
25
- {#snippet children()}
26
- <span class="material-icons-outlined">info</span>
27
- {/snippet}
25
+ <span class="material-icons-outlined">info</span>
28
26
  </Tooltip>
29
27
  {/if}
30
28
  </div>
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { onMount, type Snippet } from 'svelte';
2
+ import { type Snippet } from 'svelte';
3
3
  import type { Action } from 'svelte/action';
4
4
 
5
5
  import { Search, type SelectOption } from '../../index.js';
@@ -9,133 +9,110 @@
9
9
  options: string[] | SelectOption[];
10
10
  label?: string;
11
11
  placeholder?: string;
12
+ infoTooltip?: string;
12
13
  required?: boolean;
13
14
  optional?: boolean;
14
15
  searchable?: boolean;
15
- clearable?: boolean;
16
+ hideClearButton?: boolean;
16
17
  multiple?: boolean;
17
- closeAfterSelect?: boolean;
18
- readonly?: boolean;
18
+ keepOpen?: boolean;
19
+ disabled?: boolean;
19
20
  disableValidationColor?: boolean;
20
21
  value?: string | string[] | null;
21
22
  error?: string | string[];
22
23
  customSelection?: Snippet<[option: SelectOption]>;
23
24
  customOption?: Snippet<[option: SelectOption]>;
24
- infoTooltip?: string;
25
+ onclear?: () => void;
26
+ onchange?: (option: SelectOption) => void;
25
27
  }
26
28
 
27
29
  let {
28
30
  options,
29
31
  label,
30
- placeholder,
32
+ placeholder = 'Please select',
33
+ infoTooltip,
31
34
  required = false,
32
35
  optional = false,
33
36
  searchable = false,
34
- clearable = false,
37
+ hideClearButton = false,
35
38
  multiple = false,
36
- closeAfterSelect = false,
37
- readonly = false,
39
+ keepOpen = false,
40
+ disabled = false,
38
41
  disableValidationColor = false,
39
42
  value = $bindable(),
40
43
  error,
41
44
  customSelection,
42
45
  customOption,
43
- infoTooltip
46
+ onclear,
47
+ onchange
44
48
  }: SelectProps = $props();
45
49
 
46
- let dropdownElement: HTMLElement;
47
-
48
- const isStringArray = (items: string[] | SelectOption[]): items is string[] =>
49
- typeof items[0] === 'string';
50
-
51
- const originalOptions: SelectOption[] = isStringArray(options)
52
- ? options.map((value) => ({ label: value, value }))
53
- : options;
54
-
55
50
  let searchText = $state('');
56
51
  let showInTopPosition = $state(false);
57
52
  let showListOptions = $state(false);
58
- let filteredOptions = $state(originalOptions);
59
- let selectedOptions = $state<SelectOption[]>([]);
60
- let selectedOption = $state<SelectOption | undefined>();
61
-
62
- const isInvalid: boolean = $derived(!!error && !disableValidationColor);
63
- const isValid: boolean = $derived(!isInvalid && (!!value || optional) && !disableValidationColor);
64
- const noOptionsAvailable: boolean = $derived(
65
- options.length === 0 || (multiple && selectedOptions?.length === options.length)
66
- );
67
53
 
68
- const generateSelectOption = (value: string): SelectOption => ({ label: value, value });
69
- const generateMultiselectValue = (option: SelectOption): string => option.value;
54
+ let dropdown: HTMLElement;
55
+ let dropdownContainer: HTMLElement;
70
56
 
71
- const canBeVisible = (option: SelectOption): boolean =>
72
- !selectedOptions.some((selectedOption) => selectedOption.value === option.value);
57
+ const isStringArray = (items: string[] | SelectOption[]): items is string[] =>
58
+ typeof items[0] === 'string';
73
59
 
74
- const onCloseAfterSelect = () => {
75
- if (closeAfterSelect && showListOptions) {
76
- showListOptions = false;
77
- }
78
- };
60
+ const generateSelectOption = (text: string, options: SelectOption[]): SelectOption =>
61
+ options.find((option) => option.value === text) ?? {
62
+ label: text,
63
+ value: text
64
+ };
79
65
 
80
- const onSelectOption = (option: SelectOption) => {
81
- if (multiple) {
82
- selectedOptions = [...selectedOptions, option];
83
- value = selectedOptions.map(generateMultiselectValue);
84
- } else {
85
- selectedOption = option;
86
- value = option.value;
87
- }
88
- onCloseAfterSelect();
89
- };
66
+ const valueSet = $derived<Set<string>>(new Set(Array.isArray(value) ? value : []));
90
67
 
91
- const onClearAll = () => {
92
- if (multiple) {
93
- selectedOptions = [];
94
- value = [];
95
- } else {
96
- selectedOption = undefined;
97
- value = '';
98
- }
68
+ const isInvalid = $derived<boolean>(!!error && !disableValidationColor);
99
69
 
100
- onCloseAfterSelect();
101
- };
70
+ const isValid = $derived<boolean>(
71
+ !isInvalid &&
72
+ !disableValidationColor &&
73
+ (Array.isArray(value) ? value.length > 0 : !!value || optional)
74
+ );
102
75
 
103
- const onRemoveSelectedOption = (index: number) => {
104
- const newSelectedOptions = [...selectedOptions];
105
- newSelectedOptions.splice(index, 1);
106
- selectedOptions = newSelectedOptions;
107
- value = selectedOptions.map(generateMultiselectValue);
108
- };
76
+ const originalOptions = $derived<SelectOption[]>(
77
+ isStringArray(options) ? options.map((option) => ({ label: option, value: option })) : options
78
+ );
109
79
 
110
- const onInputSearch = () => {
111
- const text = searchText.toLowerCase();
80
+ const selectedOptions = $derived<SelectOption[]>(
81
+ multiple && Array.isArray(value) && value.length
82
+ ? value.map((text) => generateSelectOption(text, originalOptions))
83
+ : []
84
+ );
112
85
 
113
- filteredOptions = text
114
- ? originalOptions.filter((option) => option.label.toLowerCase().includes(text))
115
- : originalOptions;
116
- };
86
+ const selectedOption = $derived<SelectOption | undefined>(
87
+ !multiple && typeof value === 'string' && value
88
+ ? generateSelectOption(value, originalOptions)
89
+ : undefined
90
+ );
117
91
 
118
- const onClearSearch = () => {
119
- filteredOptions = originalOptions;
120
- };
92
+ const noOptionsAvailable = $derived<boolean>(
93
+ !options.length
94
+ ? true
95
+ : multiple && originalOptions.every((option) => valueSet.has(option.value))
96
+ );
121
97
 
122
- const onHandleClickOutside = (event: MouseEvent) => {
123
- if (showListOptions && dropdownElement && !dropdownElement.contains(event.target as Node)) {
124
- showListOptions = false;
125
- }
126
- };
98
+ const filteredOptions = $derived.by<SelectOption[]>(() => {
99
+ const text = searchText.toLowerCase();
100
+ return !text
101
+ ? originalOptions
102
+ : originalOptions.filter((option) => option.label.toLowerCase().includes(text));
103
+ });
127
104
 
128
105
  const activeOptionScroll: Action<HTMLElement, boolean> = (node, isActive) => {
129
106
  $effect(() => {
130
107
  if (isActive) {
131
- node.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
108
+ node.scrollIntoView({ behavior: 'instant', block: 'nearest' });
132
109
  }
133
110
  });
134
111
  };
135
112
 
136
113
  const autoDirection: Action = () => {
137
114
  $effect(() => {
138
- const rect = dropdownElement.getBoundingClientRect();
115
+ const rect = dropdown.getBoundingClientRect();
139
116
  const viewportHeight = window.innerHeight;
140
117
  const footer = 300;
141
118
 
@@ -143,34 +120,71 @@
143
120
  });
144
121
  };
145
122
 
146
- onMount(() => {
147
- if (!value) return;
123
+ const canBeVisible = (option: SelectOption): boolean =>
124
+ !selectedOptions.some((selectedOption) => selectedOption.value === option.value);
148
125
 
149
- if (multiple && Array.isArray(value)) {
150
- selectedOptions = value.map(generateSelectOption);
151
- } else if (!multiple && typeof value === 'string') {
152
- selectedOption = generateSelectOption(value);
126
+ const closeAfterSelect = () => {
127
+ if (!keepOpen && showListOptions) {
128
+ showListOptions = false;
153
129
  }
154
- });
130
+ };
131
+
132
+ const onSelectOption = (option: SelectOption) => {
133
+ value = multiple ? [...(value ?? []), option.value] : option.value;
134
+ closeAfterSelect();
135
+ onchange?.(option);
136
+ };
137
+
138
+ const onRemoveSelectedOption = (index: number) => {
139
+ const newSelectedOptions = [...selectedOptions];
140
+ newSelectedOptions.splice(index, 1);
141
+ value = newSelectedOptions.map((option) => option.value);
142
+ };
143
+
144
+ const onClearAll = () => {
145
+ value = multiple ? [] : '';
146
+ onclear?.();
147
+ };
148
+
149
+ const onClearSearch = () => (searchText = '');
150
+
151
+ const onFocusDropdownContainer = () => dropdownContainer && dropdownContainer.focus();
152
+
153
+ const onToggleListOptions = () => (showListOptions = !showListOptions);
154
+
155
+ const onHandleClickOutside = (event: MouseEvent) => {
156
+ if (showListOptions && dropdown && !dropdown.contains(event.target as Node)) {
157
+ showListOptions = false;
158
+ }
159
+ };
155
160
  </script>
156
161
 
157
- <svelte:window on:click={onHandleClickOutside} />
162
+ <svelte:window onclick={onHandleClickOutside} />
158
163
 
159
- <div class="form-container">
164
+ <div class="form-control">
160
165
  {#if label}
161
- <Label {label} {required} {optional} {infoTooltip} />
166
+ <div
167
+ role="button"
168
+ aria-disabled="true"
169
+ class="label-wrapper"
170
+ onclick={onFocusDropdownContainer}
171
+ onkeypress={onFocusDropdownContainer}
172
+ >
173
+ <Label {label} {required} {optional} {infoTooltip} />
174
+ </div>
162
175
  {/if}
163
176
 
164
177
  <div
165
- class={['dropdown', readonly ? 'readonly' : [isInvalid && 'invalid', isValid && 'valid']]}
166
- bind:this={dropdownElement}
178
+ class={['dropdown', disabled ? 'disabled' : [isInvalid && 'invalid', isValid && 'valid']]}
179
+ bind:this={dropdown}
167
180
  >
168
181
  <section
169
- class="dropdown-container"
170
182
  role="button"
171
183
  tabindex="0"
172
- onclick={() => (showListOptions = !showListOptions)}
173
- onkeypress={() => {}}
184
+ class="dropdown-container"
185
+ onclick={onToggleListOptions}
186
+ onkeypress={onToggleListOptions}
187
+ bind:this={dropdownContainer}
174
188
  >
175
189
  <div class="dropdown-container-selected-options">
176
190
  {#if selectedOption || selectedOptions.length}
@@ -193,24 +207,25 @@
193
207
  {/if}
194
208
  </div>
195
209
 
196
- {#if clearable && (selectedOption || selectedOptions.length)}
210
+ {#if !hideClearButton && (selectedOption || selectedOptions.length)}
197
211
  {@render removeButton(onClearAll)}
198
212
  {/if}
199
213
  </section>
200
214
 
201
215
  {#if showListOptions}
202
216
  <section class="dropdown-list" class:upwards={showInTopPosition} use:autoDirection>
203
- {#if noOptionsAvailable}
217
+ {#if searchable && !noOptionsAvailable}
218
+ <Search
219
+ placeholder="Search"
220
+ bind:value={searchText}
221
+ onclear={onClearSearch}
222
+ autocomplete="off"
223
+ />
224
+ {/if}
225
+
226
+ {#if noOptionsAvailable || !filteredOptions.length}
204
227
  <p class="dropdown-list-no-options-message">No options</p>
205
228
  {:else}
206
- {#if searchable}
207
- <Search
208
- placeholder="Search"
209
- bind:value={searchText}
210
- oninput={onInputSearch}
211
- onclear={onClearSearch}
212
- />
213
- {/if}
214
229
  <ul class="dropdown-list-options-container">
215
230
  {#each filteredOptions as filteredOption}
216
231
  {@const isVisible = multiple ? canBeVisible(filteredOption) : true}
@@ -279,7 +294,7 @@
279
294
  {/snippet}
280
295
 
281
296
  <style>
282
- .form-container {
297
+ .form-control {
283
298
  --primary-color: #472aff;
284
299
  --white: #fff;
285
300
  --black: #000;
@@ -290,18 +305,24 @@
290
305
  --gray-2: #e0e5e8;
291
306
  --gray-3: #aeb1b9;
292
307
  --gray-4: #6b7180;
293
- --border: 1px solid var(--gray-3);
308
+ --border: 1px solid var(--gray-4);
294
309
 
295
310
  display: flex;
296
311
  flex-direction: column;
297
312
  width: 100%;
298
313
  gap: 8px;
299
314
  font-size: 14px;
300
- }
301
315
 
302
- .form-container > .form-message-error {
303
- font-size: 12px;
304
- color: var(--error);
316
+ > .label-wrapper {
317
+ width: fit-content;
318
+ word-break: break-word;
319
+ cursor: default;
320
+ }
321
+
322
+ > .form-message-error {
323
+ font-size: 12px;
324
+ color: var(--error);
325
+ }
305
326
  }
306
327
 
307
328
  .dropdown {
@@ -314,7 +335,7 @@
314
335
  box-shadow 0.2s ease-in-out;
315
336
  }
316
337
 
317
- .dropdown.readonly {
338
+ .dropdown.disabled {
318
339
  cursor: not-allowed;
319
340
 
320
341
  > .dropdown-container {
@@ -323,11 +344,11 @@
323
344
  }
324
345
  }
325
346
 
326
- .dropdown:not(.readonly, .invalid, .valid):hover {
347
+ .dropdown:not(.disabled, .invalid, .valid):hover {
327
348
  border: 1px solid var(--primary-color);
328
349
  }
329
350
 
330
- .dropdown:not(.readonly, .invalid, .valid):focus-within {
351
+ .dropdown:not(.disabled, .invalid, .valid):focus-within {
331
352
  border: 1px solid var(--primary-color);
332
353
  box-shadow: 0 0 0 3px rgba(149, 155, 255, 0.3);
333
354
  }
@@ -341,7 +362,7 @@
341
362
  }
342
363
 
343
364
  .dropdown.invalid {
344
- border-color: var(--error);
365
+ border: 1px solid var(--error);
345
366
  }
346
367
 
347
368
  .dropdown.invalid:focus-within {
@@ -351,11 +372,12 @@
351
372
  .dropdown > .dropdown-container {
352
373
  display: grid;
353
374
  grid-template-columns: 1fr auto;
354
- min-height: 42px;
355
- padding: 8px;
375
+ min-height: 36px;
376
+ padding: 8px 16px;
356
377
  gap: 8px;
357
378
  align-items: center;
358
379
  cursor: pointer;
380
+ outline: none;
359
381
  border: none;
360
382
  border-radius: 8px;
361
383
  background: var(--white);
@@ -375,13 +397,13 @@
375
397
  }
376
398
 
377
399
  > .dropdown-container-selected-option {
378
- display: grid;
379
- grid-template-columns: auto auto;
380
- padding: 8px;
381
- gap: 8px;
400
+ display: flex;
401
+ padding: 0px 5px;
402
+ gap: 4px;
382
403
  align-items: center;
383
404
  cursor: default;
384
- border-radius: 8px;
405
+ border-radius: 4px;
406
+ border: 1px solid var(--gray-2);
385
407
  background: var(--gray-1);
386
408
  }
387
409
  }
@@ -431,11 +453,13 @@
431
453
  min-height: 40px;
432
454
  padding: 0px 8px;
433
455
  cursor: pointer;
456
+ outline: none;
434
457
  border: none;
435
458
  background: transparent;
436
459
  }
437
460
  }
438
461
 
462
+ .dropdown-list-option:not(.active) > button:focus-visible,
439
463
  .dropdown-list-option:not(.active) > button:hover {
440
464
  background: var(--gray-1);
441
465
  }
@@ -446,12 +470,14 @@
446
470
  }
447
471
 
448
472
  .clear-button {
473
+ outline: none;
449
474
  border: none;
450
475
  color: var(--gray-4);
451
476
  background: transparent;
452
477
  transition: color 0.2s ease-in-out;
453
478
  }
454
479
 
480
+ .clear-button:focus-visible,
455
481
  .clear-button:hover {
456
482
  cursor: pointer;
457
483
  color: var(--black);
@@ -4,19 +4,21 @@ interface SelectProps {
4
4
  options: string[] | SelectOption[];
5
5
  label?: string;
6
6
  placeholder?: string;
7
+ infoTooltip?: string;
7
8
  required?: boolean;
8
9
  optional?: boolean;
9
10
  searchable?: boolean;
10
- clearable?: boolean;
11
+ hideClearButton?: boolean;
11
12
  multiple?: boolean;
12
- closeAfterSelect?: boolean;
13
- readonly?: boolean;
13
+ keepOpen?: boolean;
14
+ disabled?: boolean;
14
15
  disableValidationColor?: boolean;
15
16
  value?: string | string[] | null;
16
17
  error?: string | string[];
17
18
  customSelection?: Snippet<[option: SelectOption]>;
18
19
  customOption?: Snippet<[option: SelectOption]>;
19
- infoTooltip?: string;
20
+ onclear?: () => void;
21
+ onchange?: (option: SelectOption) => void;
20
22
  }
21
23
  declare const Select: import("svelte").Component<SelectProps, {}, "value">;
22
24
  type Select = ReturnType<typeof Select>;
@@ -1,36 +1,71 @@
1
1
  <script lang="ts">
2
+ import Label from '../Label.svelte';
3
+
2
4
  interface ToggleProps {
3
5
  id?: string;
4
6
  checked?: boolean;
5
7
  disabled?: boolean;
8
+ label?: string;
9
+ infoTooltip?: string;
10
+ vertical?: boolean;
6
11
  onchange?: (event: Event) => void;
7
12
  }
8
13
 
9
- let { id = '', checked = $bindable(false), disabled = false, onchange }: ToggleProps = $props();
14
+ let {
15
+ id = '',
16
+ checked = $bindable(false),
17
+ disabled = false,
18
+ label,
19
+ infoTooltip,
20
+ vertical,
21
+ onchange
22
+ }: ToggleProps = $props();
10
23
  </script>
11
24
 
12
- <label class="toggle-container">
13
- <input
14
- type="checkbox"
15
- class="toggle-input"
16
- bind:checked
17
- {disabled}
18
- {id}
19
- {onchange}
20
- role="switch"
21
- />
22
- <div class={['toggle-slider', checked && 'checked', disabled && 'disabled']}>
23
- <span class="material-icons-outlined toggle-icon">
24
- {#if checked}
25
- done
26
- {:else}
27
- close
28
- {/if}
29
- </span>
30
- </div>
31
- </label>
25
+ <div class="container" class:vertical>
26
+ <label class="toggle-container">
27
+ <input
28
+ type="checkbox"
29
+ class="toggle-input"
30
+ bind:checked
31
+ {disabled}
32
+ {id}
33
+ {onchange}
34
+ role="switch"
35
+ />
36
+ <div class={['toggle-slider', checked && 'checked', disabled && 'disabled']}>
37
+ <span class="material-icons-outlined toggle-icon">
38
+ {#if checked}
39
+ done
40
+ {:else}
41
+ close
42
+ {/if}
43
+ </span>
44
+ </div>
45
+ </label>
46
+ <Label {label} {infoTooltip} />
47
+ </div>
32
48
 
33
49
  <style>
50
+ .container {
51
+ display: flex;
52
+ flex-direction: row;
53
+ align-items: center;
54
+ gap: 8px;
55
+ }
56
+
57
+ .container.vertical {
58
+ flex-direction: column;
59
+ align-items: flex-start;
60
+ }
61
+
62
+ .container.vertical .toggle-container {
63
+ order: 2;
64
+ }
65
+
66
+ .container.vertical label {
67
+ order: 1;
68
+ }
34
69
  .toggle-container {
35
70
  position: relative;
36
71
  }
@@ -2,6 +2,9 @@ interface ToggleProps {
2
2
  id?: string;
3
3
  checked?: boolean;
4
4
  disabled?: boolean;
5
+ label?: string;
6
+ infoTooltip?: string;
7
+ vertical?: boolean;
5
8
  onchange?: (event: Event) => void;
6
9
  }
7
10
  declare const Toggle: import("svelte").Component<ToggleProps, {}, "checked">;
@@ -9,27 +9,29 @@
9
9
  title?: string;
10
10
  homeUrl?: string;
11
11
  hideAccount?: boolean;
12
- hideHelp?: boolean;
13
- hideNotification?: boolean;
14
12
  accountName?: string;
15
13
  userName?: string;
16
14
  profileUrl?: string;
17
15
  hideLoader?: boolean;
18
- menu?: Snippet;
16
+ menu?: Snippet<[showMenu: boolean]>;
19
17
  }
20
18
 
21
19
  let {
22
- title = 'Default Title',
20
+ title = '',
23
21
  homeUrl = '/',
24
22
  hideAccount,
25
- hideHelp,
26
- hideNotification,
27
- accountName = 'Company Name',
28
- userName = 'User Name',
23
+ accountName = '',
24
+ userName = '',
29
25
  profileUrl = '/profile',
30
26
  hideLoader,
31
27
  menu
32
28
  }: HeaderProps = $props();
29
+
30
+ let showMenu = $state(false);
31
+
32
+ const toggleMenu = () => {
33
+ showMenu = !showMenu;
34
+ };
33
35
  </script>
34
36
 
35
37
  <div class="header-container">
@@ -37,7 +39,13 @@
37
39
  {#if !hideLoader}
38
40
  <HeaderLoader />
39
41
  {/if}
40
- {@render menu?.()}
42
+
43
+ {#if menu}
44
+ <button type="button" class="header-button" onclick={toggleMenu} aria-label="menu button">
45
+ <span class="material-icons icon-span">menu</span>
46
+ </button>
47
+ {@render menu(showMenu)}
48
+ {/if}
41
49
  <a href={homeUrl} title="Home">
42
50
  <HeaderLogo />
43
51
  </a>
@@ -47,16 +55,6 @@
47
55
  </nav>
48
56
 
49
57
  <nav class="header-section">
50
- {#if !hideHelp}
51
- <button class="header-btn material-icons-outlined" aria-labelledby="help-button">
52
- help_outline
53
- </button>
54
- {/if}
55
- {#if !hideNotification}
56
- <button class="header-btn material-icons-outlined" aria-labelledby="notifications-button">
57
- notifications
58
- </button>
59
- {/if}
60
58
  {#if !hideAccount}
61
59
  <a href={profileUrl} title="Profile">
62
60
  <HeaderAccount {accountName} {userName} />
@@ -66,6 +64,28 @@
66
64
  </div>
67
65
 
68
66
  <style>
67
+ .header-button {
68
+ display: flex;
69
+ justify-content: center;
70
+ align-items: center;
71
+ border-radius: 50%;
72
+ background: white;
73
+ z-index: 40;
74
+ cursor: pointer;
75
+ border: none;
76
+ width: 40px;
77
+ height: 40px;
78
+ }
79
+
80
+ .header-button:hover {
81
+ background: #e0e5e8;
82
+ }
83
+
84
+ .icon-span {
85
+ font-size: 32px;
86
+ color: #6b7180;
87
+ }
88
+
69
89
  .header-container {
70
90
  display: flex;
71
91
  gap: 24px;
@@ -88,10 +108,4 @@
88
108
  font-size: 24px;
89
109
  font-weight: 600;
90
110
  }
91
-
92
- .header-btn {
93
- border: none;
94
- background: transparent;
95
- color: #6b7180;
96
- }
97
111
  </style>
@@ -3,13 +3,11 @@ interface HeaderProps {
3
3
  title?: string;
4
4
  homeUrl?: string;
5
5
  hideAccount?: boolean;
6
- hideHelp?: boolean;
7
- hideNotification?: boolean;
8
6
  accountName?: string;
9
7
  userName?: string;
10
8
  profileUrl?: string;
11
9
  hideLoader?: boolean;
12
- menu?: Snippet;
10
+ menu?: Snippet<[showMenu: boolean]>;
13
11
  }
14
12
  declare const Header: import("svelte").Component<HeaderProps, {}, "">;
15
13
  type Header = ReturnType<typeof Header>;
@@ -6,15 +6,14 @@
6
6
  import type { MenuItem } from './SidebarState.svelte';
7
7
 
8
8
  interface MenuProps {
9
- disableMenuButton?: boolean;
10
9
  menuItems: MenuItem[];
10
+ showMenu: boolean;
11
11
  }
12
12
 
13
- let showMenu: boolean = $state(false);
14
13
  let isMainMenu: boolean = $state(true);
15
14
  let activeItem: string = $state('');
16
15
 
17
- const { disableMenuButton = false, menuItems }: MenuProps = $props();
16
+ let { menuItems, showMenu }: MenuProps = $props();
18
17
 
19
18
  $effect(() => {
20
19
  if (showMenu) {
@@ -46,17 +45,6 @@
46
45
  };
47
46
  </script>
48
47
 
49
- <button
50
- type="button"
51
- class="menu-button"
52
- class:disabled-menu={disableMenuButton}
53
- onclick={onHandleMenu}
54
- disabled={disableMenuButton}
55
- aria-label="menu button"
56
- >
57
- <span class="material-icons icon-span">menu</span>
58
- </button>
59
-
60
48
  {#if showMenu}
61
49
  <button
62
50
  class="menu-principal"
@@ -81,28 +69,6 @@
81
69
  {/if}
82
70
 
83
71
  <style>
84
- .menu-button {
85
- display: flex;
86
- justify-content: center;
87
- align-items: center;
88
- border-radius: 50%;
89
- background: white;
90
- z-index: 40;
91
- cursor: pointer;
92
- border: none;
93
- width: 40px;
94
- height: 40px;
95
- }
96
-
97
- .menu-button:hover {
98
- background: #e0e5e8;
99
- }
100
-
101
- .icon-span {
102
- font-size: 32px;
103
- color: #6b7180;
104
- }
105
-
106
72
  .menu-principal {
107
73
  position: fixed;
108
74
  inset: 0;
@@ -138,11 +104,6 @@
138
104
  overflow-x: hidden;
139
105
  }
140
106
 
141
- .disabled-menu {
142
- cursor: not-allowed;
143
- opacity: 0.5;
144
- }
145
-
146
107
  @media (min-width: 768px) {
147
108
  .menu-list {
148
109
  min-width: 220px;
@@ -1,7 +1,7 @@
1
1
  import type { MenuItem } from './SidebarState.svelte';
2
2
  interface MenuProps {
3
- disableMenuButton?: boolean;
4
3
  menuItems: MenuItem[];
4
+ showMenu: boolean;
5
5
  }
6
6
  declare const Menu: import("svelte").Component<MenuProps, {}, "">;
7
7
  type Menu = ReturnType<typeof Menu>;
@@ -13,7 +13,8 @@
13
13
  errorIcon,
14
14
  onclose = () => {},
15
15
  children,
16
- footer
16
+ footer,
17
+ disablePadding
17
18
  }: ModalProps = $props();
18
19
 
19
20
  const onHandleClose = () => {
@@ -22,34 +23,37 @@
22
23
  };
23
24
  </script>
24
25
 
25
- <div class="modal-container">
26
- <div
27
- onclose={onHandleClose}
28
- class="modal {width}"
29
- transition:scale={{
30
- duration: 150,
31
- start: 0.95
32
- }}
33
- >
34
- <ModalHeader {title} {errorIcon} onclose={onHandleClose} />
35
- <ModalContent content={children} />
36
- <ModalFooter {footer} onclose={onHandleClose} />
26
+ <div class="modal-backdrop">
27
+ <div class="modal-container {width}">
28
+ <div
29
+ onclose={onHandleClose}
30
+ class="modal"
31
+ transition:scale={{
32
+ duration: 150,
33
+ start: 0.95
34
+ }}
35
+ >
36
+ <ModalHeader {title} {errorIcon} onclose={onHandleClose} />
37
+ <ModalContent content={children} {disablePadding} />
38
+ <ModalFooter {footer} onclose={onHandleClose} />
39
+ </div>
37
40
  </div>
38
41
  </div>
39
42
 
40
43
  <style>
41
- .modal-container {
44
+ .modal-backdrop {
42
45
  display: flex;
43
46
  position: fixed;
44
47
  top: 0;
45
48
  left: 0;
46
49
  z-index: 1000;
50
+ justify-content: center;
47
51
  width: 100%;
48
52
  height: 100%;
49
53
  background-color: #e0e5e880;
50
54
  }
51
55
 
52
- .modal {
56
+ .modal-container {
53
57
  width: var(--modal-width);
54
58
  max-height: 90%;
55
59
  border-radius: 16px;
@@ -62,36 +66,40 @@
62
66
  0px 4px 5px 0px rgba(51, 56, 64, 0.14);
63
67
  }
64
68
 
65
- .modal.xs {
69
+ .modal {
70
+ position: relative;
71
+ }
72
+
73
+ .modal-container.xs {
66
74
  --modal-width: 500px;
67
75
  }
68
76
 
69
- .modal.md {
77
+ .modal-container.md {
70
78
  --modal-width: 600px;
71
79
  }
72
80
 
73
- .modal.lg {
81
+ .modal-container.lg {
74
82
  --modal-width: 800px;
75
83
  }
76
84
 
77
- .modal.xl {
85
+ .modal-container.xl {
78
86
  --modal-width: 1000px;
79
87
  }
80
88
 
81
- .modal::-webkit-scrollbar {
89
+ .modal-container::-webkit-scrollbar {
82
90
  width: 10px;
83
91
  }
84
92
 
85
- .modal::-webkit-scrollbar-thumb {
93
+ .modal-container::-webkit-scrollbar-thumb {
86
94
  background: #888;
87
95
  border-radius: 10px;
88
96
  }
89
97
 
90
- .modal::-webkit-scrollbar-thumb:hover {
98
+ .modal-container::-webkit-scrollbar-thumb:hover {
91
99
  background: #555;
92
100
  }
93
101
 
94
- :global(html:has(.modal-container)) {
102
+ :global(html:has(.modal-backdrop)) {
95
103
  overflow: hidden;
96
104
  }
97
105
  </style>
@@ -3,19 +3,19 @@
3
3
 
4
4
  interface ModalContentProps {
5
5
  content?: Snippet;
6
+ disablePadding?: boolean;
6
7
  }
7
8
 
8
- let { content }: ModalContentProps = $props();
9
+ let { content, disablePadding = false }: ModalContentProps = $props();
9
10
  </script>
10
11
 
11
- <div class="modal-content">
12
+ <div class="modal-content" class:padding={!disablePadding}>
12
13
  {@render content?.()}
13
14
  </div>
14
15
 
15
16
  <style>
16
17
  .modal-content {
17
18
  display: flex;
18
- padding: 24px;
19
19
  flex-direction: column;
20
20
  align-items: flex-start;
21
21
  gap: 24px;
@@ -23,4 +23,8 @@
23
23
  background: #fff;
24
24
  border-top: 1px solid #aeb1b9;
25
25
  }
26
+
27
+ .padding {
28
+ padding: 24px;
29
+ }
26
30
  </style>
@@ -1,6 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  interface ModalContentProps {
3
3
  content?: Snippet;
4
+ disablePadding?: boolean;
4
5
  }
5
6
  declare const ModalContent: import("svelte").Component<ModalContentProps, {}, "">;
6
7
  type ModalContent = ReturnType<typeof ModalContent>;
@@ -4,6 +4,7 @@ export interface ModalProps extends ModalHeaderProps, ModalFooterProps {
4
4
  width?: WidthModal;
5
5
  children?: Snippet;
6
6
  onclose?: VoidFunction;
7
+ disablePadding?: boolean;
7
8
  }
8
9
  export interface ModalHeaderProps {
9
10
  title?: string;
@@ -7,9 +7,8 @@
7
7
  steps: ProgressWizardStep[];
8
8
  readonly?: boolean;
9
9
  currentStep: number;
10
- lastActiveStep: number;
11
10
  content: Snippet;
12
- additionalButtons: Snippet;
11
+ additionalButtons: Snippet<[lastActiveStep: number]>;
13
12
  oncancel: VoidFunction;
14
13
  }
15
14
 
@@ -17,7 +16,6 @@
17
16
  steps,
18
17
  readonly = false,
19
18
  currentStep = $bindable(0),
20
- lastActiveStep = $bindable(0),
21
19
  content,
22
20
  additionalButtons,
23
21
  oncancel
@@ -25,6 +23,7 @@
25
23
 
26
24
  const allStepsDisabled: boolean = steps.every((step) => step.disabled);
27
25
  const firstActiveStep: number = steps.findIndex((step) => !step.disabled) + 1;
26
+ const lastActiveStep: number = steps.findLastIndex((step) => !step.disabled) + 1;
28
27
 
29
28
  const onclickNext = () => {
30
29
  const nextStepIndex = steps.slice(currentStep).findIndex((step) => !step.disabled);
@@ -47,7 +46,6 @@
47
46
  };
48
47
 
49
48
  currentStep = stepToStart();
50
- lastActiveStep = steps.findLastIndex((step) => !step.disabled) + 1;
51
49
 
52
50
  $effect(() => {
53
51
  if (!readonly) {
@@ -132,7 +130,7 @@
132
130
  {/if}
133
131
  {/if}
134
132
 
135
- {@render additionalButtons()}
133
+ {@render additionalButtons(lastActiveStep)}
136
134
  {/if}
137
135
  </div>
138
136
  </section>
@@ -154,7 +152,6 @@
154
152
  display: flex;
155
153
  flex-direction: column;
156
154
  width: 100%;
157
- height: 100vh;
158
155
  }
159
156
 
160
157
  .progress-wizard-container {
@@ -4,11 +4,10 @@ interface ProgressWizardProps {
4
4
  steps: ProgressWizardStep[];
5
5
  readonly?: boolean;
6
6
  currentStep: number;
7
- lastActiveStep: number;
8
7
  content: Snippet;
9
- additionalButtons: Snippet;
8
+ additionalButtons: Snippet<[lastActiveStep: number]>;
10
9
  oncancel: VoidFunction;
11
10
  }
12
- declare const ProgressWizard: import("svelte").Component<ProgressWizardProps, {}, "currentStep" | "lastActiveStep">;
11
+ declare const ProgressWizard: import("svelte").Component<ProgressWizardProps, {}, "currentStep">;
13
12
  type ProgressWizard = ReturnType<typeof ProgressWizard>;
14
13
  export default ProgressWizard;
@@ -0,0 +1,4 @@
1
+ import { type Writable } from 'svelte/store';
2
+ import type { ProgressWizardStep } from './progressWizardState.svelte.js';
3
+ export declare const setProgressWizardStepsContext: (steps: ProgressWizardStep[]) => Writable<ProgressWizardStep[]>;
4
+ export declare const getProgressWizardContext: () => Writable<ProgressWizardStep[]>;
@@ -0,0 +1,11 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ import { writable } from 'svelte/store';
3
+ const stepsKey = Symbol('steps');
4
+ export const setProgressWizardStepsContext = (steps) => {
5
+ const stepsContext = writable(steps);
6
+ setContext(stepsKey, stepsContext);
7
+ return stepsContext;
8
+ };
9
+ export const getProgressWizardContext = () => {
10
+ return getContext(stepsKey);
11
+ };
@@ -4,3 +4,4 @@ export type ProgressWizardStep = {
4
4
  isValid: boolean;
5
5
  disabled: boolean;
6
6
  };
7
+ export declare const setStepValidity: (steps: ProgressWizardStep[], title: string, isValid: boolean) => void;
@@ -1 +1,6 @@
1
- export {};
1
+ export const setStepValidity = (steps, title, isValid) => {
2
+ const index = steps.findIndex((step) => step.title === title);
3
+ if (index !== -1) {
4
+ steps[index].isValid = isValid;
5
+ }
6
+ };
@@ -41,6 +41,7 @@
41
41
  top: 0;
42
42
  left: 0;
43
43
  background-color: rgba(255, 255, 255, 0.8);
44
+ z-index: 99;
44
45
  }
45
46
 
46
47
  .spinner {
package/dist/index.d.ts CHANGED
@@ -28,8 +28,9 @@ import Modal from './Modal/Modal.svelte';
28
28
  import type { ModalProps } from './Modal/modalState.svelte.js';
29
29
  import Notification from './Notification/Notification.svelte';
30
30
  import ProgressPage from './ProgressPage/ProgressPage.svelte';
31
+ import { setProgressWizardStepsContext, getProgressWizardContext } from './ProgressWizard/context.js';
31
32
  import ProgressWizard from './ProgressWizard/ProgressWizard.svelte';
32
- import type { ProgressWizardStep } from './ProgressWizard/progressWizardState.svelte.js';
33
+ import { setStepValidity, type ProgressWizardStep } from './ProgressWizard/progressWizardState.svelte.js';
33
34
  import Search from './Search/Search.svelte';
34
35
  import Spinner from './Spinner/Spinner.svelte';
35
36
  import Tabs from './Tabs/Tabs.svelte';
@@ -37,4 +38,4 @@ import type { Tab } from './Tabs/tabsState.svelte.js';
37
38
  import Toaster from './Toast/Toast.svelte';
38
39
  import { addToast, type Toast } from './Toast/toastState.svelte';
39
40
  import Tooltip from './Tooltip/Tooltip.svelte';
40
- export { Accordion, addBreadcrumbsNameMap, addToast, Avatar, Breadcrumbs, Button, Card, Chips, ChipType, ColumnType, ErrorPage, Footer, Header, HeaderAccount, HeaderLoader, HeaderLogo, HighlightPanel, Home, ImageType, Input, Menu, Modal, Notification, ProgressPage, ProgressWizard, Search, Select, Sidebar, Spinner, Tabs, TextArea, Toaster, Toggle, Tooltip, type BreadcrumbsNameMap, type HighlightPanelColumn, type HomeItem, type MenuItem, type ModalProps, type ProgressWizardStep, type SelectOption, type Tab, type Toast };
41
+ export { Accordion, addBreadcrumbsNameMap, addToast, Avatar, Breadcrumbs, Button, Card, Chips, ChipType, ColumnType, ErrorPage, Footer, getProgressWizardContext, Header, HeaderAccount, HeaderLoader, HeaderLogo, HighlightPanel, Home, ImageType, Input, Menu, Modal, Notification, ProgressPage, ProgressWizard, Search, Select, setStepValidity, setProgressWizardStepsContext, Sidebar, Spinner, Tabs, TextArea, Toaster, Toggle, Tooltip, type BreadcrumbsNameMap, type HighlightPanelColumn, type HomeItem, type MenuItem, type ModalProps, type ProgressWizardStep, type SelectOption, type Tab, type Toast };
package/dist/index.js CHANGED
@@ -24,11 +24,13 @@ import Sidebar from './Menu/Sidebar.svelte';
24
24
  import Modal from './Modal/Modal.svelte';
25
25
  import Notification from './Notification/Notification.svelte';
26
26
  import ProgressPage from './ProgressPage/ProgressPage.svelte';
27
+ import { setProgressWizardStepsContext, getProgressWizardContext } from './ProgressWizard/context.js';
27
28
  import ProgressWizard from './ProgressWizard/ProgressWizard.svelte';
29
+ import { setStepValidity } from './ProgressWizard/progressWizardState.svelte.js';
28
30
  import Search from './Search/Search.svelte';
29
31
  import Spinner from './Spinner/Spinner.svelte';
30
32
  import Tabs from './Tabs/Tabs.svelte';
31
33
  import Toaster from './Toast/Toast.svelte';
32
34
  import { addToast } from './Toast/toastState.svelte';
33
35
  import Tooltip from './Tooltip/Tooltip.svelte';
34
- export { Accordion, addBreadcrumbsNameMap, addToast, Avatar, Breadcrumbs, Button, Card, Chips, ChipType, ColumnType, ErrorPage, Footer, Header, HeaderAccount, HeaderLoader, HeaderLogo, HighlightPanel, Home, ImageType, Input, Menu, Modal, Notification, ProgressPage, ProgressWizard, Search, Select, Sidebar, Spinner, Tabs, TextArea, Toaster, Toggle, Tooltip };
36
+ export { Accordion, addBreadcrumbsNameMap, addToast, Avatar, Breadcrumbs, Button, Card, Chips, ChipType, ColumnType, ErrorPage, Footer, getProgressWizardContext, Header, HeaderAccount, HeaderLoader, HeaderLogo, HighlightPanel, Home, ImageType, Input, Menu, Modal, Notification, ProgressPage, ProgressWizard, Search, Select, setStepValidity, setProgressWizardStepsContext, Sidebar, Spinner, Tabs, TextArea, Toaster, Toggle, Tooltip };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwareone/spi-sv5-library",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Svelte components",
5
5
  "keywords": [
6
6
  "svelte",