@softwareone/spi-sv5-library 1.11.10 → 1.12.1

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,268 @@
1
+ <script lang="ts">
2
+ import { fade } from 'svelte/transition';
3
+
4
+ import type { AnnouncementItem } from './AnnouncementState.svelte.js';
5
+
6
+ interface Props {
7
+ announcementItems?: AnnouncementItem[];
8
+ showAnnouncement: boolean;
9
+ toggleAnnouncement: VoidFunction;
10
+ }
11
+
12
+ let { announcementItems = [], showAnnouncement, toggleAnnouncement }: Props = $props();
13
+ </script>
14
+
15
+ <div class="announcement-wrapper">
16
+ {#if showAnnouncement}
17
+ <div
18
+ class="announcement-backdrop"
19
+ onclick={toggleAnnouncement}
20
+ onkeydown={toggleAnnouncement}
21
+ transition:fade={{ duration: 200 }}
22
+ role="button"
23
+ tabindex="0"
24
+ aria-label="Close announcement panel"
25
+ ></div>
26
+ <section class="announcement-panel" transition:fade={{ duration: 250 }}>
27
+ <header class="panel-header">
28
+ <h1 class="panel-title">Announcements</h1>
29
+ <button
30
+ class="close-button"
31
+ onclick={toggleAnnouncement}
32
+ aria-label="Close announcements"
33
+ type="button"
34
+ >
35
+ <span class="material-icons close-icon">close</span>
36
+ </button>
37
+ </header>
38
+
39
+ {#if announcementItems.length}
40
+ <div class="announcements-list">
41
+ {#each announcementItems as announcement}
42
+ <article class={['announcement-item', !announcement.isRead && 'unread']}>
43
+ <div class="item-header">
44
+ <div class="icon-container">
45
+ {#if !announcement.isRead}
46
+ <div class="unread-indicator"></div>
47
+ {/if}
48
+ <span class="material-icons-outlined announcement-icon">notifications_active</span
49
+ >
50
+ </div>
51
+ <div class="item-info">
52
+ <h2 class="item-title">{announcement.title}</h2>
53
+ <time class="item-date">{announcement.createdAt}</time>
54
+ </div>
55
+ </div>
56
+ <p class="item-message">{announcement.message}</p>
57
+ </article>
58
+ <hr class="item-divider" />
59
+ {/each}
60
+ </div>
61
+ {:else}
62
+ <div class="empty-state">
63
+ <span class="material-icons-outlined empty-icon icon-gradient">hide_source</span>
64
+ <div class="empty-content">
65
+ <p class="empty-title">No announcements yet</p>
66
+ <p class="empty-description">Check back later for updates</p>
67
+ </div>
68
+ </div>
69
+ {/if}
70
+ </section>
71
+ {/if}
72
+ </div>
73
+
74
+ <style>
75
+ .announcement-wrapper {
76
+ --color-primary: #472aff;
77
+ --color-gray-100: #e0e5e8;
78
+ --color-gray-200: #6b7180;
79
+
80
+ position: relative;
81
+ }
82
+
83
+ .announcement-backdrop {
84
+ position: fixed;
85
+ inset: 0;
86
+ background-color: rgba(243, 244, 246, 0.5);
87
+ cursor: pointer;
88
+ z-index: 30;
89
+ border: none;
90
+ }
91
+
92
+ .announcement-panel {
93
+ position: absolute;
94
+ z-index: 40;
95
+ right: -260px;
96
+ top: 30px;
97
+ background: #ffffff;
98
+ border-radius: 8px;
99
+ display: flex;
100
+ width: 500px;
101
+ max-height: 800px;
102
+ padding: 24px;
103
+ flex-direction: column;
104
+ align-items: flex-start;
105
+ gap: 16px;
106
+ box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
107
+ }
108
+
109
+ .panel-header {
110
+ display: flex;
111
+ justify-content: space-between;
112
+ align-items: center;
113
+ width: 100%;
114
+ border-bottom: 1px solid var(--color-gray-100);
115
+ padding-bottom: 24px;
116
+ }
117
+
118
+ .panel-title {
119
+ font-weight: 500;
120
+ font-size: 18px;
121
+ line-height: 28px;
122
+ margin: 0;
123
+ }
124
+
125
+ .close-button {
126
+ display: flex;
127
+ align-items: center;
128
+ background: none;
129
+ border: none;
130
+ cursor: pointer;
131
+ padding: 0;
132
+ }
133
+
134
+ .close-icon {
135
+ font-size: 24px;
136
+ color: #000000;
137
+ }
138
+
139
+ .announcements-list {
140
+ overflow-y: auto;
141
+ width: 100%;
142
+ }
143
+
144
+ .announcement-item {
145
+ display: flex;
146
+ flex-direction: column;
147
+ align-items: flex-start;
148
+ padding: 16px;
149
+ gap: 16px;
150
+ }
151
+
152
+ .announcement-item.unread {
153
+ background-color: #eaecff;
154
+ border-radius: 16px;
155
+ }
156
+
157
+ .item-header {
158
+ display: flex;
159
+ align-items: center;
160
+ gap: 16px;
161
+ width: 100%;
162
+ }
163
+
164
+ .icon-container {
165
+ position: relative;
166
+ display: flex;
167
+ width: 40px;
168
+ height: 40px;
169
+ padding: 8px;
170
+ align-items: center;
171
+ justify-content: center;
172
+ border-radius: 20px;
173
+ border: 1px solid var(--color-gray-100);
174
+ background: #ffffff;
175
+ }
176
+
177
+ .unread-indicator {
178
+ position: absolute;
179
+ left: -5px;
180
+ top: 40%;
181
+ width: 8px;
182
+ height: 8px;
183
+ background-color: var(--color-primary);
184
+ border-radius: 50%;
185
+ }
186
+
187
+ .announcement-icon {
188
+ color: #000000;
189
+ font-size: 24px;
190
+ }
191
+
192
+ .item-info {
193
+ display: flex;
194
+ flex-direction: column;
195
+ }
196
+
197
+ .item-title {
198
+ font-weight: 500;
199
+ font-size: 14px;
200
+ line-height: 20px;
201
+ margin: 0;
202
+ }
203
+
204
+ .item-date {
205
+ font-size: 12px;
206
+ line-height: 16px;
207
+ color: var(--color-gray-200);
208
+ text-align: left;
209
+ }
210
+
211
+ .item-message {
212
+ font-size: 14px;
213
+ line-height: 20px;
214
+ margin: 0;
215
+ }
216
+
217
+ .item-divider {
218
+ width: 100%;
219
+ border: 0;
220
+ border-top: 1px solid var(--color-gray-100);
221
+ margin: 16px 0;
222
+ }
223
+
224
+ .empty-state {
225
+ display: flex;
226
+ flex-direction: column;
227
+ padding: 24px;
228
+ align-items: center;
229
+ gap: 24px;
230
+ width: 100%;
231
+ }
232
+
233
+ .empty-icon {
234
+ font-size: 32px;
235
+ }
236
+
237
+ .empty-content {
238
+ display: flex;
239
+ flex-direction: column;
240
+ justify-content: center;
241
+ align-items: center;
242
+ gap: 8px;
243
+ width: 100%;
244
+ }
245
+
246
+ .empty-title {
247
+ font-size: 18px;
248
+ line-height: 28px;
249
+ font-weight: 700;
250
+ word-break: break-word;
251
+ margin: 0;
252
+ }
253
+
254
+ .empty-description {
255
+ font-size: 14px;
256
+ line-height: 20px;
257
+ word-break: break-word;
258
+ text-align: center;
259
+ margin: 0;
260
+ }
261
+
262
+ .icon-gradient {
263
+ background: linear-gradient(226deg, #00c9cd 0%, #472aff 33%, #392d9c 67%, black 100%);
264
+ -webkit-background-clip: text;
265
+ -webkit-text-fill-color: transparent;
266
+ background-clip: text;
267
+ }
268
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { AnnouncementItem } from './AnnouncementState.svelte.js';
2
+ interface Props {
3
+ announcementItems?: AnnouncementItem[];
4
+ showAnnouncement: boolean;
5
+ toggleAnnouncement: VoidFunction;
6
+ }
7
+ declare const Announcement: import("svelte").Component<Props, {}, "">;
8
+ type Announcement = ReturnType<typeof Announcement>;
9
+ export default Announcement;
@@ -0,0 +1,6 @@
1
+ export interface AnnouncementItem {
2
+ title: string;
3
+ message: string;
4
+ createdAt: string;
5
+ isRead: boolean;
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
- import type { MenuItem } from '../index.js';
2
+ import type { AnnouncementItem, MenuItem } from '../index.js';
3
+ import Announcement from '../Announcement/Announcement.svelte';
3
4
  import Menu from '../Menu/Menu.svelte';
4
5
  import Waffle from '../Waffle/Waffle.svelte';
5
6
  import type { WaffleItem } from '../Waffle/waffleState.svelte.js';
@@ -14,9 +15,12 @@
14
15
  accountName?: string;
15
16
  userName?: string;
16
17
  hideLoader?: boolean;
18
+ hideAnnouncement?: boolean;
17
19
  menuItems?: MenuItem[];
18
20
  waffleItems?: WaffleItem[];
19
21
  profileMenuItems?: MenuItem[];
22
+ announcementItems?: AnnouncementItem[];
23
+ onreadannouncementitems?: VoidFunction;
20
24
  }
21
25
 
22
26
  let {
@@ -26,28 +30,46 @@
26
30
  accountName = '',
27
31
  userName = '',
28
32
  hideLoader,
33
+ hideAnnouncement,
29
34
  menuItems,
30
35
  waffleItems,
31
- profileMenuItems
36
+ profileMenuItems,
37
+ announcementItems,
38
+ onreadannouncementitems
32
39
  }: HeaderProps = $props();
33
40
 
34
41
  let showWaffle = $state(false);
35
42
  let showMenu = $state(false);
43
+ let showAnnouncement = $state(false);
44
+
45
+ let unreadCount = $derived(announcementItems?.filter((item) => !item.isRead).length ?? 0);
36
46
 
37
47
  const toggleWaffle = () => {
38
48
  showWaffle = !showWaffle;
39
49
  showMenu = false;
50
+ showAnnouncement = false;
40
51
  };
41
52
 
42
53
  const toggleMenu = () => {
43
54
  showMenu = !showMenu;
44
55
  showWaffle = false;
56
+ showAnnouncement = false;
57
+ };
58
+
59
+ const toggleAnnouncement = () => {
60
+ showAnnouncement = !showAnnouncement;
61
+ showMenu = false;
62
+ showWaffle = false;
63
+ if (!showAnnouncement && unreadCount > 0) {
64
+ onreadannouncementitems?.();
65
+ }
45
66
  };
46
67
 
47
68
  const handleKeydown = (event: KeyboardEvent) => {
48
- if (event.key === 'Escape' && (showWaffle || showMenu)) {
69
+ if (event.key === 'Escape' && (showWaffle || showMenu || showAnnouncement)) {
49
70
  showWaffle = false;
50
71
  showMenu = false;
72
+ showAnnouncement = false;
51
73
  }
52
74
  };
53
75
  </script>
@@ -57,51 +79,71 @@
57
79
  <header class="header-container">
58
80
  <nav class="header-section">
59
81
  <div class="header-buttons-container">
60
- {#if !hideLoader}
61
- <HeaderLoader />
62
- {/if}
63
- {#if waffleItems}
64
- <button
65
- type="button"
66
- class={[showWaffle && 'active', 'header-button']}
67
- onclick={toggleWaffle}
68
- aria-label="Waffle Component"
69
- aria-expanded={showWaffle}
70
- >
71
- <span class="material-icons icon-span">apps</span>
72
- </button>
73
- <Waffle items={waffleItems} bind:showWaffle />
74
- {/if}
75
- {#if menuItems}
76
- <button
77
- type="button"
78
- class={[showMenu && 'active', 'header-button']}
79
- onclick={toggleMenu}
80
- aria-label="menu button"
81
- aria-expanded={showMenu}
82
- >
83
- <span class="material-icons icon-span menu-icon">menu</span>
84
- </button>
85
- <Menu {menuItems} bind:showMenu />
86
- {/if}
87
- <a href={homeUrl} title="Home">
88
- <HeaderLogo />
89
- </a>
82
+ {#if !hideLoader}
83
+ <HeaderLoader />
84
+ {/if}
85
+ {#if waffleItems}
86
+ <button
87
+ type="button"
88
+ class={[showWaffle && 'active', 'header-button']}
89
+ onclick={toggleWaffle}
90
+ aria-label="Waffle Component"
91
+ aria-expanded={showWaffle}
92
+ >
93
+ <span class="material-icons icon-span">apps</span>
94
+ </button>
95
+ <Waffle items={waffleItems} bind:showWaffle />
96
+ {/if}
97
+ {#if menuItems}
98
+ <button
99
+ type="button"
100
+ class={[showMenu && 'active', 'header-button']}
101
+ onclick={toggleMenu}
102
+ aria-label="menu button"
103
+ aria-expanded={showMenu}
104
+ >
105
+ <span class="material-icons icon-span menu-icon">menu</span>
106
+ </button>
107
+ <Menu {menuItems} bind:showMenu />
108
+ {/if}
109
+ <a href={homeUrl} title="Home">
110
+ <HeaderLogo />
111
+ </a>
90
112
  </div>
91
113
  <h2 class="header-title">
92
114
  {title}
93
115
  </h2>
94
116
  </nav>
95
117
 
96
- {#if !hideAccount}
97
- <nav class="header-section">
98
- <HeaderAccount {accountName} {userName} {profileMenuItems} />
99
- </nav>
100
- {/if}
118
+ <div class="header-buttons-container">
119
+ {#if !hideAnnouncement}
120
+ <button
121
+ type="button"
122
+ class={[showAnnouncement && 'active', 'header-button']}
123
+ onclick={toggleAnnouncement}
124
+ aria-label="Announcement"
125
+ aria-expanded={showAnnouncement}
126
+ >
127
+ <span class="material-icons-outlined icon-span menu-icon">notifications</span>
128
+ {#if unreadCount > 0}
129
+ <div class="notification-badge">
130
+ <span class="badge-count">{unreadCount}</span>
131
+ </div>
132
+ {/if}
133
+ </button>
134
+ <Announcement {announcementItems} {showAnnouncement} {toggleAnnouncement} />
135
+ {/if}
136
+ {#if !hideAccount}
137
+ <nav class="header-section">
138
+ <HeaderAccount {accountName} {userName} {profileMenuItems} />
139
+ </nav>
140
+ {/if}
141
+ </div>
101
142
  </header>
102
143
 
103
144
  <style>
104
145
  .header-button {
146
+ position: relative;
105
147
  display: flex;
106
148
  justify-content: center;
107
149
  align-items: center;
@@ -162,4 +204,24 @@
162
204
  font-weight: 500;
163
205
  cursor: default;
164
206
  }
207
+
208
+ .notification-badge {
209
+ position: absolute;
210
+ top: 0;
211
+ right: 0;
212
+ transform: translate(5px, 2px);
213
+ display: flex;
214
+ justify-content: center;
215
+ background-color: #472aff;
216
+ border-radius: 4px;
217
+ min-width: 19px;
218
+ padding: 0 3px;
219
+ }
220
+
221
+ .badge-count {
222
+ color: #ffffff;
223
+ font-weight: 500;
224
+ font-size: 12px;
225
+ line-height: 16px;
226
+ }
165
227
  </style>
@@ -1,4 +1,4 @@
1
- import type { MenuItem } from '../index.js';
1
+ import type { AnnouncementItem, MenuItem } from '../index.js';
2
2
  import type { WaffleItem } from '../Waffle/waffleState.svelte.js';
3
3
  interface HeaderProps {
4
4
  title?: string;
@@ -7,9 +7,12 @@ interface HeaderProps {
7
7
  accountName?: string;
8
8
  userName?: string;
9
9
  hideLoader?: boolean;
10
+ hideAnnouncement?: boolean;
10
11
  menuItems?: MenuItem[];
11
12
  waffleItems?: WaffleItem[];
12
13
  profileMenuItems?: MenuItem[];
14
+ announcementItems?: AnnouncementItem[];
15
+ onreadannouncementitems?: VoidFunction;
13
16
  }
14
17
  declare const Header: import("svelte").Component<HeaderProps, {}, "">;
15
18
  type Header = ReturnType<typeof Header>;
@@ -42,9 +42,9 @@
42
42
 
43
43
  {#if showMenu}
44
44
  <div
45
- class="menu-principal"
45
+ class="menu-backdrop"
46
46
  onclick={onHandleMenu}
47
- onkeypress={onHandleMenu}
47
+ onkeydown={onHandleMenu}
48
48
  transition:fade={{ duration: 200 }}
49
49
  role="button"
50
50
  tabindex="0"
@@ -66,7 +66,7 @@
66
66
  {/if}
67
67
 
68
68
  <style>
69
- .menu-principal {
69
+ .menu-backdrop {
70
70
  position: fixed;
71
71
  inset: 0;
72
72
  background-color: rgba(243, 244, 246, 0.5);
package/dist/index.d.ts CHANGED
@@ -42,6 +42,7 @@ import { setProgressWizardStepsContext, getProgressWizardContext } from './Progr
42
42
  import { setStepValidity } from './ProgressWizard/progressWizardState.svelte.js';
43
43
  import { addToast } from './Toast/toastState.svelte';
44
44
  import { getSubMenuItemsFromMenu } from './Menu/MenuState.svelte';
45
+ import type { AnnouncementItem } from './Announcement/AnnouncementState.svelte.js';
45
46
  import type { BreadcrumbsNameMap } from './Breadcrumbs/breadcrumbsState.svelte.js';
46
47
  import type { HighlightPanelColumn } from './HighlightPanel/highlightPanelState.svelte.js';
47
48
  import type { HomeItem } from './Menu/MenuState.svelte.js';
@@ -54,4 +55,4 @@ import type { Tab } from './Tabs/tabsState.svelte.js';
54
55
  import type { Toast } from './Toast/toastState.svelte';
55
56
  import type { WaffleItem } from './Waffle/waffleState.svelte.js';
56
57
  import type { NotificationProps } from './Notification/notificationState.svelte.js';
57
- export { Accordion, AttachFile, Avatar, Breadcrumbs, Button, Card, Chips, DeleteConfirmationModal, ErrorPage, Footer, Header, HeaderAccount, HeaderLoader, HeaderLogo, HighlightPanel, Home, Input, Label, Link, Menu, Modal, Notification, Processing, ProgressPage, ProgressWizard, Search, Select, Sidebar, Spinner, Switcher, Tabs, TextArea, Toaster, Toggle, Tooltip, Waffle, addBreadcrumbsNameMap, addToast, getProgressWizardContext, setProgressWizardStepsContext, setStepValidity, getSubMenuItemsFromMenu, createTabComponent, ChipType, ColumnType, ImageType, type BreadcrumbsNameMap, type HighlightPanelColumn, type HomeItem, type MainMenu, type MenuItem, type ModalProps, type ProgressWizardStep, type SelectOption, type SwitcherOption, type Tab, type Toast, type WaffleItem, type SubMenuItem, type NotificationProps };
58
+ export { Accordion, AttachFile, Avatar, Breadcrumbs, Button, Card, Chips, DeleteConfirmationModal, ErrorPage, Footer, Header, HeaderAccount, HeaderLoader, HeaderLogo, HighlightPanel, Home, Input, Label, Link, Menu, Modal, Notification, Processing, ProgressPage, ProgressWizard, Search, Select, Sidebar, Spinner, Switcher, Tabs, TextArea, Toaster, Toggle, Tooltip, Waffle, addBreadcrumbsNameMap, addToast, getProgressWizardContext, setProgressWizardStepsContext, setStepValidity, getSubMenuItemsFromMenu, createTabComponent, ChipType, ColumnType, ImageType, type AnnouncementItem, type BreadcrumbsNameMap, type HighlightPanelColumn, type HomeItem, type MainMenu, type MenuItem, type ModalProps, type ProgressWizardStep, type SelectOption, type SwitcherOption, type Tab, type Toast, type WaffleItem, type SubMenuItem, type NotificationProps };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwareone/spi-sv5-library",
3
- "version": "1.11.10",
3
+ "version": "1.12.1",
4
4
  "description": "Svelte components",
5
5
  "keywords": [
6
6
  "svelte",