@softwareone/spi-sv5-library 0.1.3 → 1.1.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 (72) hide show
  1. package/README.md +75 -19
  2. package/dist/Avatar/Avatar.svelte +33 -0
  3. package/dist/Avatar/Avatar.svelte.d.ts +10 -0
  4. package/dist/Breadcrumbs/Breadcrumbs.svelte +10 -20
  5. package/dist/Button/Button.svelte +66 -115
  6. package/dist/Button/Button.svelte.d.ts +8 -6
  7. package/dist/Card/Card.svelte +18 -44
  8. package/dist/Card/Card.svelte.d.ts +1 -1
  9. package/dist/Chips/Chips.svelte +40 -46
  10. package/dist/Chips/Chips.svelte.d.ts +2 -1
  11. package/dist/Chips/chipsState.svelte.d.ts +7 -0
  12. package/dist/Chips/chipsState.svelte.js +8 -0
  13. package/dist/ErrorPage/ErrorPage.svelte +96 -0
  14. package/dist/ErrorPage/ErrorPage.svelte.d.ts +7 -0
  15. package/dist/Footer/Footer.svelte +29 -135
  16. package/dist/Footer/Footer.svelte.d.ts +1 -1
  17. package/dist/Form/Input/Input.svelte +393 -0
  18. package/dist/Form/Input/Input.svelte.d.ts +14 -0
  19. package/dist/Form/Input/InputIcon.svelte +97 -0
  20. package/dist/Form/Input/InputIcon.svelte.d.ts +9 -0
  21. package/dist/Form/TextArea/TextArea.svelte +260 -0
  22. package/dist/Form/TextArea/TextArea.svelte.d.ts +13 -0
  23. package/dist/Form/Toggle/Toggle.svelte +120 -0
  24. package/dist/{Toggle → Form/Toggle}/Toggle.svelte.d.ts +4 -3
  25. package/dist/Header/Header.svelte +54 -136
  26. package/dist/Header/Header.svelte.d.ts +2 -2
  27. package/dist/Header/HeaderAccount.svelte +14 -35
  28. package/dist/Header/HeaderLoader.svelte +2 -2
  29. package/dist/Header/HeaderLogo.svelte +7 -4
  30. package/dist/Header/HeaderLogo.svelte.d.ts +14 -6
  31. package/dist/HighlightPanel/HighlightPanel.svelte +125 -0
  32. package/dist/HighlightPanel/HighlightPanel.svelte.d.ts +10 -0
  33. package/dist/HighlightPanel/highlightPanelState.svelte.d.ts +35 -0
  34. package/dist/HighlightPanel/highlightPanelState.svelte.js +13 -0
  35. package/dist/Menu/Menu.svelte +158 -0
  36. package/dist/Menu/Menu.svelte.d.ts +8 -0
  37. package/dist/Menu/MenuItem.svelte +149 -0
  38. package/dist/Menu/MenuItem.svelte.d.ts +11 -0
  39. package/dist/Menu/Sidebar.svelte +228 -0
  40. package/dist/Menu/Sidebar.svelte.d.ts +11 -0
  41. package/dist/Menu/SidebarState.svelte.d.ts +6 -0
  42. package/dist/Menu/SidebarState.svelte.js +1 -0
  43. package/dist/Modal/Modal.svelte +81 -29
  44. package/dist/Modal/Modal.svelte.d.ts +2 -9
  45. package/dist/Modal/ModalContent.svelte +8 -88
  46. package/dist/Modal/ModalContent.svelte.d.ts +2 -3
  47. package/dist/Modal/ModalFooter.svelte +21 -66
  48. package/dist/Modal/ModalFooter.svelte.d.ts +5 -5
  49. package/dist/Modal/ModalHeader.svelte +50 -34
  50. package/dist/Modal/ModalHeader.svelte.d.ts +5 -4
  51. package/dist/Modal/modalState.svelte.d.ts +15 -0
  52. package/dist/Modal/modalState.svelte.js +1 -0
  53. package/dist/ProgressWizard/ProgressWizard.svelte +273 -294
  54. package/dist/ProgressWizard/ProgressWizard.svelte.d.ts +11 -13
  55. package/dist/ProgressWizard/progressWizardState.svelte.d.ts +6 -0
  56. package/dist/ProgressWizard/progressWizardState.svelte.js +1 -0
  57. package/dist/Search/Search.svelte +154 -0
  58. package/dist/Search/Search.svelte.d.ts +10 -0
  59. package/dist/Tabs/Tabs.svelte +111 -0
  60. package/dist/Tabs/Tabs.svelte.d.ts +8 -0
  61. package/dist/Tabs/tabsState.svelte.d.ts +7 -0
  62. package/dist/Tabs/tabsState.svelte.js +1 -0
  63. package/dist/Toast/Toast.svelte +116 -49
  64. package/dist/Toast/toastState.svelte.d.ts +7 -3
  65. package/dist/Toast/toastState.svelte.js +13 -10
  66. package/dist/Tooltip/Tooltip.svelte +168 -0
  67. package/dist/Tooltip/Tooltip.svelte.d.ts +13 -0
  68. package/dist/assets/icons/feedback.svg +5 -0
  69. package/dist/index.d.ts +28 -8
  70. package/dist/index.js +24 -9
  71. package/package.json +4 -5
  72. package/dist/Toggle/Toggle.svelte +0 -170
@@ -0,0 +1,154 @@
1
+ <script lang="ts">
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
3
+
4
+ type Props = {
5
+ value?: string;
6
+ placeholder?: string;
7
+ disabled?: boolean;
8
+ onclear?: () => void;
9
+ } & Omit<HTMLInputAttributes, 'value' | 'type'>;
10
+
11
+ let {
12
+ value = $bindable(''),
13
+ placeholder = 'Search',
14
+ disabled = false,
15
+ onclear,
16
+ ...props
17
+ }: Props = $props();
18
+
19
+ const hasValue = $derived(!!value);
20
+
21
+ const handleClear = () => {
22
+ value = '';
23
+ onclear?.();
24
+ };
25
+
26
+ const handleKeydown = (event: KeyboardEvent) => {
27
+ if (event.key === 'Escape' && hasValue) {
28
+ handleClear();
29
+ event.preventDefault();
30
+ }
31
+ };
32
+ </script>
33
+
34
+ <div class="search-container" class:disabled>
35
+ <div class="search-wrapper">
36
+ <span class="material-icons-outlined search-icon" aria-hidden="true">search</span>
37
+ <input
38
+ type="search"
39
+ class="search-input"
40
+ bind:value
41
+ {placeholder}
42
+ {disabled}
43
+ onkeydown={handleKeydown}
44
+ {...props}
45
+ />
46
+
47
+ {#if hasValue && !disabled}
48
+ <button type="button" class="clear-button" onclick={handleClear} aria-label="Clear search">
49
+ <span class="material-icons-outlined" aria-hidden="true">close</span>
50
+ </button>
51
+ {/if}
52
+ </div>
53
+ </div>
54
+
55
+ <style>
56
+ .search-container {
57
+ position: relative;
58
+ font-size: 14px;
59
+ line-height: 20px;
60
+ }
61
+
62
+ .search-wrapper {
63
+ position: relative;
64
+ display: flex;
65
+ align-items: center;
66
+ width: 100%;
67
+ border-radius: 8px;
68
+ border: 1px solid #6b7180;
69
+ background: #fff;
70
+ transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
71
+ }
72
+
73
+ .search-wrapper:hover:not(:has(.search-input:disabled)),
74
+ .search-wrapper:focus-within {
75
+ border-color: #472aff;
76
+ box-shadow: 0 0 0 3px rgba(149, 155, 255, 0.3);
77
+ }
78
+
79
+ .search-input {
80
+ width: 100%;
81
+ padding: 8px 40px 8px 40px;
82
+ border: none;
83
+ background: transparent;
84
+ font-size: 14px;
85
+ color: #000;
86
+ }
87
+
88
+ .search-input:focus {
89
+ outline: none;
90
+ }
91
+
92
+ .search-input::placeholder {
93
+ color: #6b7180;
94
+ }
95
+
96
+ .search-input::-webkit-search-cancel-button,
97
+ .search-input::-webkit-search-decoration {
98
+ -webkit-appearance: none;
99
+ }
100
+
101
+ .search-icon,
102
+ .clear-button {
103
+ position: absolute;
104
+ top: 50%;
105
+ transform: translateY(-50%);
106
+ color: #6b7180;
107
+ font-size: 18px;
108
+ }
109
+
110
+ .search-icon {
111
+ left: 12px;
112
+ pointer-events: none;
113
+ }
114
+
115
+ .clear-button {
116
+ right: 12px;
117
+ background: none;
118
+ border: none;
119
+ padding: 4px;
120
+ cursor: pointer;
121
+ border-radius: 4px;
122
+ transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out;
123
+ display: flex;
124
+ align-items: center;
125
+ justify-content: center;
126
+ }
127
+
128
+ .clear-button:hover,
129
+ .clear-button:focus {
130
+ color: #000;
131
+ background-color: #f3f4f6;
132
+ outline: none;
133
+ }
134
+
135
+ .clear-button span {
136
+ font-size: 16px;
137
+ }
138
+
139
+ .disabled .search-wrapper {
140
+ border-color: #d1d5db;
141
+ background-color: #f3f4f6;
142
+ }
143
+
144
+ .disabled .search-input {
145
+ color: #6b7180;
146
+ cursor: not-allowed;
147
+ }
148
+
149
+ @media (max-width: 640px) {
150
+ .search-input {
151
+ font-size: 16px;
152
+ }
153
+ }
154
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ type Props = {
3
+ value?: string;
4
+ placeholder?: string;
5
+ disabled?: boolean;
6
+ onclear?: () => void;
7
+ } & Omit<HTMLInputAttributes, 'value' | 'type'>;
8
+ declare const Search: import("svelte").Component<Props, {}, "value">;
9
+ type Search = ReturnType<typeof Search>;
10
+ export default Search;
@@ -0,0 +1,111 @@
1
+ <script lang="ts">
2
+ import type { Tab } from '../index.js';
3
+
4
+ interface TabsProps {
5
+ tabs: Tab[];
6
+ activeTab?: number;
7
+ }
8
+
9
+ let { tabs = [], activeTab = 0 }: TabsProps = $props();
10
+
11
+ const handleClick = (index: number) => (): void => {
12
+ activeTab = index;
13
+ };
14
+ </script>
15
+
16
+ <div class="tabs-container">
17
+ <div class="tabs-list" role="tablist" aria-label="tabs">
18
+ {#each tabs as tab}
19
+ {#if !tab.hidden}
20
+ {@const isActiveTab = activeTab === tab.index}
21
+ <button
22
+ id="tab-{tab.index}"
23
+ role="tab"
24
+ aria-selected={isActiveTab}
25
+ aria-controls="panel-{tab.index}"
26
+ tabindex={isActiveTab ? 0 : -1}
27
+ type="button"
28
+ class:active={isActiveTab}
29
+ onclick={handleClick(tab.index)}
30
+ >
31
+ {tab.label}
32
+ </button>
33
+ {/if}
34
+ {/each}
35
+ </div>
36
+
37
+ {#each tabs as tab}
38
+ {#if activeTab === tab.index}
39
+ <div
40
+ class="tabs-content"
41
+ id="panel-{tab.index}"
42
+ role="tabpanel"
43
+ aria-labelledby="tab-{tab.index}"
44
+ >
45
+ {@render tab.component?.()}
46
+ </div>
47
+ {/if}
48
+ {/each}
49
+ </div>
50
+
51
+ <style>
52
+ .tabs-container {
53
+ display: flex;
54
+ flex-direction: column;
55
+ width: 100%;
56
+ }
57
+
58
+ .tabs-list {
59
+ display: flex;
60
+ gap: 16px;
61
+ border-bottom: 1px solid #e0e5e8;
62
+ }
63
+
64
+ .tabs-list button {
65
+ border-radius: 8px;
66
+ background-color: #fff;
67
+ padding: 20px 16px 20px;
68
+ position: relative;
69
+ border: none;
70
+ font-size: 16px;
71
+ }
72
+
73
+ .tabs-list button:hover {
74
+ background-color: #f4f6f8;
75
+ cursor: pointer;
76
+ }
77
+
78
+ .tabs-list button.active {
79
+ color: #472aff;
80
+ }
81
+
82
+ .tabs-list button::after {
83
+ content: '';
84
+ position: absolute;
85
+ left: 50%;
86
+ bottom: -2px;
87
+ width: 0;
88
+ height: 4px;
89
+ background-color: #472aff;
90
+ border-radius: 8px;
91
+ transform: translateX(-50%);
92
+ }
93
+
94
+ .tabs-list button:hover::after,
95
+ .tabs-list button.active::after {
96
+ width: 90%;
97
+ }
98
+
99
+ .tabs-content {
100
+ width: 100%;
101
+ height: 100%;
102
+ position: relative;
103
+ border-radius: 0px 0px 16px 16px;
104
+ overflow: hidden;
105
+ padding: 24px;
106
+ box-sizing: border-box;
107
+ min-height: 300px;
108
+ text-align: left;
109
+ font-size: 18px;
110
+ }
111
+ </style>
@@ -0,0 +1,8 @@
1
+ import type { Tab } from '../index.js';
2
+ interface TabsProps {
3
+ tabs: Tab[];
4
+ activeTab?: number;
5
+ }
6
+ declare const Tabs: import("svelte").Component<TabsProps, {}, "">;
7
+ type Tabs = ReturnType<typeof Tabs>;
8
+ export default Tabs;
@@ -0,0 +1,7 @@
1
+ import type { Snippet } from 'svelte';
2
+ export interface Tab {
3
+ index: number;
4
+ label: string;
5
+ component?: Snippet;
6
+ hidden?: boolean;
7
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,47 +1,47 @@
1
1
  <script lang="ts">
2
- import { fly } from 'svelte/transition';
3
2
  import { linear } from 'svelte/easing';
4
- import { getToast } from './toastState.svelte';
3
+ import { fly, type FlyParams } from 'svelte/transition';
4
+
5
+ import { deleteToastById, getToast } from './toastState.svelte.js';
5
6
 
6
7
  interface ToastProps {
7
8
  duration?: number;
8
9
  }
9
10
 
10
- let { duration }:ToastProps = $props();
11
+ let { duration }: ToastProps = $props();
11
12
 
12
- const toast = getToast(duration);
13
+ const TRANSITION_DURATION = 100;
14
+ const VERTICAL_DISTANCE = 8;
13
15
 
14
- const getToastColor = (type: string): string => {
15
- const colors: Record<string, string> = {
16
- info: '#472AFF',
17
- warning: '#E87D1E',
18
- danger: '#DC182C',
19
- neutral: '#6B7180',
20
- success: '#008556'
21
- };
22
- return colors[type] || '#6B7180'; // Default to neutral if type is unknown
16
+ const transitionConfig: FlyParams = {
17
+ duration: TRANSITION_DURATION,
18
+ easing: linear,
19
+ y: VERTICAL_DISTANCE
23
20
  };
24
21
 
22
+ const toastNotifications = getToast(duration);
25
23
  </script>
26
24
 
27
- {#if toast.msg.length > 0}
28
- <div class="toast-container" in:fly={{ y: 100, duration: 100, easing: linear }} out:fly={{ y: 100, duration: 100, easing: linear }}>
29
- {#each toast.msg as msg}
30
- <div class="toast" in:fly={{ y: 100, duration: 100, easing: linear }} out:fly={{ y: 100, duration: 100, easing: linear }}>
31
- <div>
32
- <svg
33
- xmlns="http://www.w3.org/2000/svg"
34
- width="8"
35
- height="36"
36
- viewBox="0 0 8 36"
37
- fill="none"
38
- >
39
- <rect width="8" height="36" rx="4" fill={getToastColor(msg.type)} />
40
- </svg>
25
+ {#if toastNotifications.toasts.length > 0}
26
+ <div class="toast-container" in:fly={transitionConfig} out:fly={transitionConfig}>
27
+ {#each toastNotifications.toasts as toast}
28
+ <div class="toast {toast.width ?? 'sm'}" in:fly={transitionConfig} out:fly={transitionConfig}>
29
+ <span class="status-indicator {toast.type}"></span>
30
+ <div class="toast-content-container">
31
+ <div class="toast-content">
32
+ <span>{toast.message}</span>
33
+ {#if toast.link}
34
+ <a class="toast-content-link" href={toast.link} title="View details">View details</a>
35
+ {/if}
36
+ </div>
41
37
  </div>
42
- <div class="toast-content">
43
- <div>{msg.message}</div>
44
- <div class="toast-content-link">View details</div>
38
+ <div class="toast-close-container">
39
+ <button
40
+ type="button"
41
+ class="toast-close-button material-icons"
42
+ aria-label="Close toast"
43
+ onclick={() => deleteToastById(toast.id)}>close</button
44
+ >
45
45
  </div>
46
46
  </div>
47
47
  {/each}
@@ -50,55 +50,122 @@
50
50
 
51
51
  <style>
52
52
  .toast-container {
53
- position: fixed;
53
+ position: fixed;
54
54
  top: 96px;
55
55
  right: 16px;
56
56
  z-index: 9999;
57
57
  display: flex;
58
58
  flex-direction: column;
59
59
  gap: 16px;
60
+ align-items: flex-end;
60
61
  }
62
+
61
63
  .toast {
64
+ width: var(--toast-width);
65
+ height: var(--toast-height);
62
66
  display: inline-flex;
63
67
  padding: 8px;
64
- justify-content: flex-start;
65
- align-items: center;
66
68
  gap: 16px;
67
69
  border-radius: 8px;
68
- border: 1px solid var(--gray-2, #e0e5e8);
69
- background: var(--brand-white, #fff);
70
- /* shadow/dropdown */
70
+ border: 1px solid #e0e5e8;
71
+ background: #fff;
71
72
  box-shadow:
72
73
  0px 4px 5px 0px rgba(51, 56, 64, 0.15),
73
74
  0px 1px 3px 0px rgba(51, 56, 64, 0.2),
74
75
  0px 1px 16px 0px rgba(51, 56, 64, 0.1);
75
76
  }
77
+
78
+ .toast.sm {
79
+ --toast-width: 400px;
80
+ --toast-height: 52px;
81
+ --toast-flex-direction: row;
82
+ --toast-gap: 8px;
83
+ --toast-close-button-align: center;
84
+ }
85
+
86
+ .toast.md {
87
+ --toast-width: 600px;
88
+ --toast-height: 88px;
89
+ --toast-flex-direction: column;
90
+ --toast-gap: 4px;
91
+ --toast-close-button-align: flex-start;
92
+ }
93
+
76
94
  .toast-content {
77
95
  display: flex;
96
+ justify-content: space-between;
97
+ flex-direction: var(--toast-flex-direction);
98
+ gap: var(--toast-gap);
78
99
  padding: 8px 0px;
79
- align-items: flex-start;
80
- gap: 8px;
81
- color: var(--brand-type, #000);
82
-
83
- /* regular/2 */
84
- font-family: 'Haas Grotesk Display Pro', sans-serif;
100
+ color: #000;
85
101
  font-size: 14px;
86
102
  font-style: normal;
87
103
  font-weight: 400;
88
- line-height: 20px; /* 142.857% */
104
+ line-height: 20px;
89
105
  }
106
+
107
+ .toast-content-container {
108
+ display: flex;
109
+ align-items: center;
110
+ gap: 16px;
111
+ flex-grow: 1;
112
+ }
113
+
114
+ .status-indicator {
115
+ width: 8px;
116
+ height: calc(var(--toast-height) - 20px);
117
+ flex-shrink: 0;
118
+ border-radius: 4px;
119
+ background-color: var(--toast-bg);
120
+ }
121
+
90
122
  .toast-content-link {
91
123
  display: flex;
92
- align-items: flex-start;
93
- color: var(--alerts-info-4, #2b1999);
94
- font-family: 'Haas Grotesk Display Pro', sans-serif;
124
+ align-items: center;
125
+ color: #2b1999;
95
126
  font-size: 14px;
96
- font-style: normal;
97
127
  font-weight: 400;
98
- line-height: 20px; /* 142.857% */
128
+ line-height: 20px;
99
129
  }
130
+
100
131
  .toast-content-link:hover {
101
- color: var(--brand-primary, #472aff);
132
+ color: #472aff;
102
133
  text-decoration: underline;
103
134
  }
135
+
136
+ .status-indicator.info {
137
+ --toast-bg: #472aff;
138
+ }
139
+
140
+ .status-indicator.warning {
141
+ --toast-bg: #e87d1e;
142
+ }
143
+ .status-indicator.danger {
144
+ --toast-bg: #dc182c;
145
+ }
146
+ .status-indicator.neutral {
147
+ --toast-bg: #6b7180;
148
+ }
149
+ .status-indicator.success {
150
+ --toast-bg: #008556;
151
+ }
152
+
153
+ .toast-close-container {
154
+ display: flex;
155
+ align-items: var(--toast-close-button-align);
156
+ }
157
+
158
+ .toast-close-button {
159
+ background: none;
160
+ border: none;
161
+ }
162
+
163
+ .toast-close-button:hover {
164
+ cursor: pointer;
165
+ color: #434952;
166
+ }
167
+
168
+ .toast-close-button:focus {
169
+ outline: none;
170
+ }
104
171
  </style>
@@ -1,14 +1,18 @@
1
1
  type ToastType = 'info' | 'success' | 'warning' | 'danger' | 'neutral';
2
+ type ToastWidth = 'sm' | 'md';
2
3
  export interface Toast {
3
4
  type: ToastType;
4
5
  message: string;
6
+ width?: ToastWidth;
7
+ link?: string;
5
8
  }
6
9
  interface ToastState extends Toast {
7
10
  id: string;
8
11
  }
9
- export declare function getToast(duration?: number): {
10
- msg: ToastState[];
12
+ export declare const getToast: (duration?: number) => {
13
+ toasts: ToastState[];
11
14
  duration: number;
12
15
  };
13
- export declare function addToast(toast: Toast): void;
16
+ export declare const addToast: (toast: Toast) => void;
17
+ export declare const deleteToastById: (id: string) => void;
14
18
  export {};
@@ -1,20 +1,23 @@
1
1
  const toastState = $state({
2
- msg: [],
3
- duration: 5000,
2
+ toasts: [],
3
+ duration: 5000
4
4
  });
5
- export function getToast(duration) {
5
+ export const getToast = (duration) => {
6
6
  if (duration) {
7
7
  toastState.duration = duration;
8
8
  }
9
9
  return toastState;
10
- }
11
- export function addToast(toast) {
10
+ };
11
+ export const addToast = (toast) => {
12
12
  const newToast = { ...toast, id: crypto.randomUUID() };
13
- toastState.msg.push(newToast);
13
+ toastState.toasts.push(newToast);
14
14
  scheduleToastRemoval(newToast.id);
15
- }
16
- function scheduleToastRemoval(id) {
15
+ };
16
+ const scheduleToastRemoval = (id) => {
17
17
  setTimeout(() => {
18
- toastState.msg = toastState.msg.filter((toast) => toast.id !== id);
18
+ deleteToastById(id);
19
19
  }, toastState.duration);
20
- }
20
+ };
21
+ export const deleteToastById = (id) => {
22
+ toastState.toasts = toastState.toasts.filter((toast) => toast.id !== id);
23
+ };