@softwareone/spi-sv5-library 1.5.3 → 1.5.5

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.
@@ -5,7 +5,7 @@
5
5
  let { homeUrl = '/' }: FooterProps = $props();
6
6
  </script>
7
7
 
8
- <div class="footer-section">
8
+ <footer class="footer-section">
9
9
  <a href={homeUrl} title="Home">
10
10
  <img alt="SoftwareOne logo" class="logo-image" src="/softwareone-logo-white.svg" />
11
11
  </a>
@@ -31,7 +31,7 @@
31
31
  </li>
32
32
  <li>© 2025 SoftwareOne. All rights reserved</li>
33
33
  </ul>
34
- </div>
34
+ </footer>
35
35
 
36
36
  <style>
37
37
  .footer-section {
@@ -1,6 +1,8 @@
1
1
  <script lang="ts">
2
- import type { Snippet } from 'svelte';
3
-
2
+ import type { MenuItem } from '../index.js';
3
+ import Menu from '../Menu/Menu.svelte';
4
+ import Waffle from '../Waffle/Waffle.svelte';
5
+ import type { WaffleItem } from '../Waffle/waffleState.svelte.js';
4
6
  import HeaderAccount from './HeaderAccount.svelte';
5
7
  import HeaderLoader from './HeaderLoader.svelte';
6
8
  import HeaderLogo from './HeaderLogo.svelte';
@@ -9,13 +11,12 @@
9
11
  title?: string;
10
12
  homeUrl?: string;
11
13
  hideAccount?: boolean;
12
- hideHelp?: boolean;
13
- hideNotification?: boolean;
14
14
  accountName?: string;
15
15
  userName?: string;
16
16
  hideLoader?: boolean;
17
- menu?: Snippet<[showMenu: boolean]>;
18
- waffle?: Snippet<[showWaffle: boolean]>;
17
+ menuItems?: MenuItem[];
18
+ waffleItems?: WaffleItem[];
19
+ profileMenuItems?: MenuItem[];
19
20
  }
20
21
 
21
22
  let {
@@ -25,8 +26,9 @@
25
26
  accountName = '',
26
27
  userName = '',
27
28
  hideLoader,
28
- menu,
29
- waffle
29
+ menuItems,
30
+ waffleItems,
31
+ profileMenuItems
30
32
  }: HeaderProps = $props();
31
33
 
32
34
  let showWaffle = $state(false);
@@ -52,12 +54,12 @@
52
54
 
53
55
  <svelte:window onkeydown={handleKeydown} />
54
56
 
55
- <div class="header-container">
57
+ <header class="header-container">
56
58
  <nav class="header-section">
57
59
  {#if !hideLoader}
58
60
  <HeaderLoader />
59
61
  {/if}
60
- {#if waffle}
62
+ {#if waffleItems}
61
63
  <button
62
64
  type="button"
63
65
  class={[showWaffle && 'active', 'header-button']}
@@ -67,13 +69,13 @@
67
69
  >
68
70
  <span class="material-icons icon-span">apps</span>
69
71
  </button>
70
- {@render waffle(showWaffle)}
72
+ <Waffle items={waffleItems} bind:showWaffle />
71
73
  {/if}
72
- {#if menu}
74
+ {#if menuItems}
73
75
  <button type="button" class="header-button" onclick={toggleMenu} aria-label="menu button">
74
76
  <span class="material-icons icon-span menu-icon">menu</span>
75
77
  </button>
76
- {@render menu(showMenu)}
78
+ <Menu {menuItems} bind:showMenu />
77
79
  {/if}
78
80
  <a href={homeUrl} title="Home">
79
81
  <HeaderLogo />
@@ -83,12 +85,12 @@
83
85
  </h2>
84
86
  </nav>
85
87
 
86
- <nav class="header-section">
87
- {#if !hideAccount}
88
- <HeaderAccount {accountName} {userName} />
89
- {/if}
90
- </nav>
91
- </div>
88
+ {#if !hideAccount}
89
+ <nav class="header-section">
90
+ <HeaderAccount {accountName} {userName} {profileMenuItems} />
91
+ </nav>
92
+ {/if}
93
+ </header>
92
94
 
93
95
  <style>
94
96
  .header-button {
@@ -128,9 +130,9 @@
128
130
  gap: 24px;
129
131
  justify-content: space-between;
130
132
  align-items: center;
131
- padding: 0px 24px;
133
+ padding: 0 24px;
132
134
  background: #fff;
133
- height: 80px;
135
+ height: 85px;
134
136
  border-bottom: 3px solid #aeb1b9;
135
137
  position: relative;
136
138
  z-index: 50;
@@ -1,15 +1,15 @@
1
- import type { Snippet } from 'svelte';
1
+ import type { MenuItem } from '../index.js';
2
+ import type { WaffleItem } from '../Waffle/waffleState.svelte.js';
2
3
  interface HeaderProps {
3
4
  title?: string;
4
5
  homeUrl?: string;
5
6
  hideAccount?: boolean;
6
- hideHelp?: boolean;
7
- hideNotification?: boolean;
8
7
  accountName?: string;
9
8
  userName?: string;
10
9
  hideLoader?: boolean;
11
- menu?: Snippet<[showMenu: boolean]>;
12
- waffle?: Snippet<[showWaffle: boolean]>;
10
+ menuItems?: MenuItem[];
11
+ waffleItems?: WaffleItem[];
12
+ profileMenuItems?: MenuItem[];
13
13
  }
14
14
  declare const Header: import("svelte").Component<HeaderProps, {}, "">;
15
15
  type Header = ReturnType<typeof Header>;
@@ -1,85 +1,185 @@
1
1
  <script lang="ts">
2
- import { Avatar } from '../index.js';
2
+ import { fade } from 'svelte/transition';
3
+ import { goto } from '$app/navigation';
4
+ import { Avatar, type MenuItem } from '../index.js';
3
5
 
4
- interface HeaderAccountProps {
5
- accountName?: string;
6
- userName?: string;
6
+ interface Props {
7
+ accountName: string;
8
+ userName: string;
9
+ profileMenuItems?: MenuItem[];
7
10
  }
8
11
 
9
- let { accountName = 'SoftwareOne', userName = 'John Doe' }: HeaderAccountProps = $props();
12
+ let { accountName, userName, profileMenuItems }: Props = $props();
13
+
14
+ let showProfileItems = $state(false);
15
+
16
+ const onClickProfileItem = (url: string) => {
17
+ handleProfile();
18
+ goto(url);
19
+ };
20
+
21
+ const handleProfile = () => {
22
+ showProfileItems = !showProfileItems;
23
+ };
24
+
25
+ const handleKeydown = (event: KeyboardEvent) => {
26
+ if (event.key === 'Escape' && showProfileItems) {
27
+ showProfileItems = false;
28
+ }
29
+ };
10
30
  </script>
11
31
 
12
- <div class="account-container">
13
- <div class="account-avatar">
14
- <Avatar text={userName} />
15
- </div>
32
+ <svelte:window onkeydown={handleKeydown} />
33
+
34
+ {#snippet profile()}
35
+ <Avatar text={userName} />
16
36
  <div class="account-info">
17
- <div class="account-name">{accountName}</div>
18
- <div class="account-more">
19
- <div class="account-more-name">Operations | {userName}</div>
20
- </div>
37
+ <p class="account-name">{accountName}</p>
38
+ <p class="account-more-name">Operations | {userName}</p>
21
39
  </div>
40
+ {/snippet}
41
+
42
+ <div class="container">
43
+ {#if profileMenuItems}
44
+ <button
45
+ onclick={() => (showProfileItems = !showProfileItems)}
46
+ type="button"
47
+ class="account-container account-button"
48
+ aria-expanded={showProfileItems}
49
+ >
50
+ {@render profile()}
51
+ </button>
52
+ {:else}
53
+ <div class="account-container">
54
+ {@render profile()}
55
+ </div>
56
+ {/if}
57
+
58
+ {#if showProfileItems}
59
+ <div
60
+ class="account-backdrop"
61
+ onclick={handleProfile}
62
+ onkeydown={handleProfile}
63
+ role="button"
64
+ transition:fade={{ duration: 200 }}
65
+ tabindex="0"
66
+ aria-label="Close profile menu"
67
+ ></div>
68
+ <menu class="account-dropdown" transition:fade={{ delay: 50, duration: 250 }}>
69
+ {#each profileMenuItems as item}
70
+ <li>
71
+ <button
72
+ class="account-menu-item"
73
+ type="button"
74
+ onclick={() => {
75
+ onClickProfileItem(item.url);
76
+ }}
77
+ >
78
+ {item.text}
79
+ </button>
80
+ </li>
81
+ {/each}
82
+ </menu>
83
+ {/if}
22
84
  </div>
23
85
 
24
86
  <style>
87
+ .container {
88
+ position: relative;
89
+ display: none;
90
+ }
91
+
92
+ @media (min-width: 768px) {
93
+ .container {
94
+ display: flex;
95
+ }
96
+ }
97
+
25
98
  .account-container {
26
99
  display: flex;
27
- max-width: 300px;
100
+ width: 256px;
28
101
  align-items: center;
102
+ padding: 8px;
29
103
  gap: 16px;
30
- border-radius: 8px;
31
- background: #fff;
32
104
  }
33
105
 
34
- .account-avatar {
35
- display: flex;
36
- width: 48px;
37
- height: 48px;
38
- justify-content: center;
39
- align-items: center;
106
+ .account-button {
107
+ border-radius: 6px;
108
+ border: none;
109
+ height: 66px;
110
+ background: transparent;
111
+ cursor: pointer;
112
+ transition: background-color 0.2s ease-in-out;
113
+ }
114
+
115
+ .account-button:hover {
116
+ background: #e0e5e8;
40
117
  }
41
118
 
42
119
  .account-info {
43
120
  display: flex;
44
121
  flex-direction: column;
45
- align-items: flex-start;
122
+ text-align: left;
123
+ flex: 1;
124
+ word-break: break-words;
46
125
  }
47
126
 
48
127
  .account-name {
49
- display: -webkit-box;
50
- -webkit-box-orient: vertical;
51
- -webkit-line-clamp: 1;
52
- line-clamp: 1;
53
- width: auto;
54
- max-width: auto;
55
- overflow: hidden;
56
- color: #000;
57
- text-overflow: ellipsis;
58
- font-size: 14px;
59
- font-style: normal;
60
128
  font-weight: 500;
129
+ font-size: 14px;
61
130
  line-height: 20px;
62
131
  }
63
132
 
64
- .account-more {
133
+ .account-more-name {
134
+ font-weight: 400;
135
+ font-size: 12px;
136
+ color: #6b7280;
137
+ line-height: normal;
138
+ }
139
+
140
+ .account-backdrop {
141
+ position: fixed;
142
+ inset: 0;
143
+ background: rgba(243, 244, 246, 0.5);
144
+ cursor: pointer;
145
+ z-index: 40;
146
+ }
147
+
148
+ .account-dropdown {
149
+ position: absolute;
65
150
  display: flex;
66
- align-items: center;
67
- color: #6b7180;
151
+ flex-direction: column;
152
+ width: 250px;
153
+ top: 70px;
154
+ gap: 8px;
155
+ padding: 16px;
156
+ background: white;
157
+ border: 1px solid #f3f4f6;
158
+ border-radius: 0 0 10px 10px;
159
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
160
+ z-index: 40;
68
161
  }
69
162
 
70
- .account-more-name {
71
- display: -webkit-box;
72
- width: auto;
73
- max-width: auto;
74
- -webkit-box-orient: vertical;
75
- -webkit-line-clamp: 1;
76
- line-clamp: 1;
77
- overflow: hidden;
78
- color: #6b7180;
79
- text-overflow: ellipsis;
80
- font-size: 12px;
81
- font-style: normal;
163
+ .account-menu-item {
164
+ width: 100%;
82
165
  font-weight: 400;
83
- line-height: normal;
166
+ font-size: 14px;
167
+ text-align: left;
168
+ line-height: 20px;
169
+ padding: 8px;
170
+ border-radius: 8px;
171
+ border: none;
172
+ background: transparent;
173
+ cursor: pointer;
174
+ transition: background-color 0.2s ease;
175
+ }
176
+
177
+ .account-menu-item:hover {
178
+ background: #f4f6f8;
179
+ }
180
+
181
+ .account-menu-item:focus,
182
+ .account-menu-item:focus-visible {
183
+ background: #eaecff;
84
184
  }
85
185
  </style>
@@ -1,7 +1,9 @@
1
- interface HeaderAccountProps {
2
- accountName?: string;
3
- userName?: string;
1
+ import { type MenuItem } from '../index.js';
2
+ interface Props {
3
+ accountName: string;
4
+ userName: string;
5
+ profileMenuItems?: MenuItem[];
4
6
  }
5
- declare const HeaderAccount: import("svelte").Component<HeaderAccountProps, {}, "">;
7
+ declare const HeaderAccount: import("svelte").Component<Props, {}, "">;
6
8
  type HeaderAccount = ReturnType<typeof HeaderAccount>;
7
9
  export default HeaderAccount;
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- import { goto } from '$app/navigation';
3
2
  import { page } from '$app/state';
3
+ import { fade } from 'svelte/transition';
4
4
 
5
5
  import MainMenuItem from './MenuItem.svelte';
6
6
  import type { MenuItem } from './SidebarState.svelte';
@@ -10,7 +10,7 @@
10
10
  showMenu: boolean;
11
11
  }
12
12
 
13
- let { menuItems, showMenu }: MenuProps = $props();
13
+ let { menuItems, showMenu = $bindable(false) }: MenuProps = $props();
14
14
 
15
15
  let activeItem = $state('');
16
16
 
@@ -28,10 +28,6 @@
28
28
  return regex.test(pathname);
29
29
  };
30
30
 
31
- const onClickMenuItem = (menuItem: MenuItem) => {
32
- onHandleMenu();
33
- goto(menuItem.url);
34
- };
35
31
 
36
32
  const onHandleMenu = () => {
37
33
  showMenu = !showMenu;
@@ -45,21 +41,24 @@
45
41
  </script>
46
42
 
47
43
  {#if showMenu}
48
- <button
44
+ <div
49
45
  class="menu-principal"
50
46
  onclick={onHandleMenu}
51
47
  onkeypress={onHandleMenu}
48
+ transition:fade={{ duration: 200 }}
49
+ role="button"
50
+ tabindex="0"
52
51
  aria-label="menu principal"
53
- ></button>
52
+ ></div>
54
53
 
55
- <nav class="menu-nav">
54
+ <nav class="menu-nav" transition:fade={{ duration: 250 }}>
56
55
  <menu class="menu-list">
57
56
  {#each menuItems as menuItem}
58
57
  <MainMenuItem
59
58
  item={menuItem}
60
59
  isCollapsed={false}
61
60
  activeItem={activeItem === menuItem.text}
62
- onClick={onClickMenuItem}
61
+ onClick={onHandleMenu}
63
62
  />
64
63
  {/each}
65
64
  </menu>
@@ -3,6 +3,6 @@ interface MenuProps {
3
3
  menuItems: MenuItem[];
4
4
  showMenu: boolean;
5
5
  }
6
- declare const Menu: import("svelte").Component<MenuProps, {}, "">;
6
+ declare const Menu: import("svelte").Component<MenuProps, {}, "showMenu">;
7
7
  type Menu = ReturnType<typeof Menu>;
8
8
  export default Menu;
@@ -14,8 +14,8 @@
14
14
 
15
15
  <li>
16
16
  <Tooltip content={isCollapsed ? item.text : ''} position="right">
17
- <button
18
- type="button"
17
+ <a
18
+ href={item.url}
19
19
  class={[
20
20
  'item',
21
21
  isCollapsed ? 'collapsed' : 'expanded',
@@ -25,7 +25,7 @@
25
25
  >
26
26
  <span class="material-icons-outlined icon-span">{item.icon}</span>
27
27
  <h2 class="item-name-span" class:hidden={isCollapsed}>{item.text}</h2>
28
- </button>
28
+ </a>
29
29
  </Tooltip>
30
30
  </li>
31
31
 
@@ -8,7 +8,7 @@
8
8
  showWaffle?: boolean;
9
9
  }
10
10
 
11
- let { items = [], showWaffle = false }: Props = $props();
11
+ let { items = [], showWaffle = $bindable(false) }: Props = $props();
12
12
 
13
13
  const handleTileClick = (item: WaffleItem) => {
14
14
  onHandleWaffle();
@@ -3,6 +3,6 @@ interface Props {
3
3
  items?: WaffleItem[];
4
4
  showWaffle?: boolean;
5
5
  }
6
- declare const Waffle: import("svelte").Component<Props, {}, "">;
6
+ declare const Waffle: import("svelte").Component<Props, {}, "showWaffle">;
7
7
  type Waffle = ReturnType<typeof Waffle>;
8
8
  export default Waffle;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwareone/spi-sv5-library",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
4
4
  "description": "Svelte components",
5
5
  "keywords": [
6
6
  "svelte",