@softwareone/spi-sv5-library 1.5.2 → 1.5.4

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.
@@ -17,7 +17,6 @@
17
17
  .card {
18
18
  width: 100%;
19
19
  background: #fff;
20
- margin-bottom: 24px;
21
20
  border-radius: 16px;
22
21
  box-shadow:
23
22
  0px 1px 16px rgba(107, 113, 128, 0.1),
@@ -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 {
@@ -53,8 +53,7 @@
53
53
  display: flex;
54
54
  flex-direction: column;
55
55
  gap: 3px;
56
- font-size: 12px;
57
- letter-spacing: 1px;
56
+ font-size: 14px;
58
57
  }
59
58
 
60
59
  .footer-section a:hover {
@@ -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,175 @@
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 handleProfile = () => {
17
+ showProfileItems = !showProfileItems;
18
+ };
19
+
20
+ const handleKeydown = (event: KeyboardEvent) => {
21
+ if (event.key === 'Escape' && showProfileItems) {
22
+ showProfileItems = false;
23
+ }
24
+ };
10
25
  </script>
11
26
 
12
- <div class="account-container">
13
- <div class="account-avatar">
14
- <Avatar text={userName} />
15
- </div>
27
+ <svelte:window onkeydown={handleKeydown} />
28
+
29
+ {#snippet profile()}
30
+ <Avatar text={userName} />
16
31
  <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>
32
+ <p class="account-name">{accountName}</p>
33
+ <p class="account-more-name">Operations | {userName}</p>
21
34
  </div>
35
+ {/snippet}
36
+
37
+ <div class="container">
38
+ {#if profileMenuItems}
39
+ <button
40
+ onclick={() => (showProfileItems = !showProfileItems)}
41
+ type="button"
42
+ class="account-container account-button"
43
+ aria-expanded={showProfileItems}
44
+ >
45
+ {@render profile()}
46
+ </button>
47
+ {:else}
48
+ <div class="account-container">
49
+ {@render profile()}
50
+ </div>
51
+ {/if}
52
+
53
+ {#if showProfileItems}
54
+ <div
55
+ class="account-backdrop"
56
+ onclick={handleProfile}
57
+ onkeydown={handleProfile}
58
+ role="button"
59
+ transition:fade={{ duration: 200 }}
60
+ tabindex="0"
61
+ aria-label="Close profile menu"
62
+ ></div>
63
+ <menu class="account-dropdown" transition:fade={{ delay: 50, duration: 250 }}>
64
+ {#each profileMenuItems as item}
65
+ <li>
66
+ <a href={item.url} class="account-menu-item" onclick={() => handleProfile()}>
67
+ {item.text}
68
+ </a>
69
+ </li>
70
+ {/each}
71
+ </menu>
72
+ {/if}
22
73
  </div>
23
74
 
24
75
  <style>
76
+ .container {
77
+ position: relative;
78
+ display: none;
79
+ }
80
+
81
+ @media (min-width: 768px) {
82
+ .container {
83
+ display: flex;
84
+ }
85
+ }
86
+
25
87
  .account-container {
26
88
  display: flex;
27
- max-width: 300px;
89
+ width: 256px;
28
90
  align-items: center;
91
+ padding: 8px;
29
92
  gap: 16px;
30
- border-radius: 8px;
31
- background: #fff;
32
93
  }
33
94
 
34
- .account-avatar {
35
- display: flex;
36
- width: 48px;
37
- height: 48px;
38
- justify-content: center;
39
- align-items: center;
95
+ .account-button {
96
+ border-radius: 6px;
97
+ border: none;
98
+ height: 66px;
99
+ background: transparent;
100
+ cursor: pointer;
101
+ transition: background-color 0.2s ease-in-out;
102
+ }
103
+
104
+ .account-button:hover {
105
+ background: #e0e5e8;
40
106
  }
41
107
 
42
108
  .account-info {
43
109
  display: flex;
44
110
  flex-direction: column;
45
- align-items: flex-start;
111
+ text-align: left;
112
+ flex: 1;
113
+ word-break: break-words;
46
114
  }
47
115
 
48
116
  .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
117
  font-weight: 500;
118
+ font-size: 14px;
61
119
  line-height: 20px;
62
120
  }
63
121
 
64
- .account-more {
122
+ .account-more-name {
123
+ font-weight: 400;
124
+ font-size: 12px;
125
+ color: #6b7280;
126
+ line-height: normal;
127
+ }
128
+
129
+ .account-backdrop {
130
+ position: fixed;
131
+ inset: 0;
132
+ background: rgba(243, 244, 246, 0.5);
133
+ cursor: pointer;
134
+ z-index: 40;
135
+ }
136
+
137
+ .account-dropdown {
138
+ position: absolute;
65
139
  display: flex;
66
- align-items: center;
67
- color: #6b7180;
140
+ flex-direction: column;
141
+ width: 250px;
142
+ top: 70px;
143
+ gap: 8px;
144
+ padding: 16px;
145
+ background: white;
146
+ border: 1px solid #f3f4f6;
147
+ border-radius: 0 0 10px 10px;
148
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
149
+ z-index: 40;
68
150
  }
69
151
 
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;
152
+ .account-menu-item {
153
+ display: inline-block;
154
+ width: 100%;
82
155
  font-weight: 400;
83
- line-height: normal;
156
+ font-size: 14px;
157
+ text-align: left;
158
+ line-height: 20px;
159
+ padding: 8px;
160
+ border-radius: 8px;
161
+ border: none;
162
+ background: transparent;
163
+ cursor: pointer;
164
+ transition: background-color 0.2s ease;
165
+ }
166
+
167
+ .account-menu-item:hover {
168
+ background: #f4f6f8;
169
+ }
170
+
171
+ .account-menu-item:focus,
172
+ .account-menu-item:focus-visible {
173
+ background: #eaecff;
84
174
  }
85
175
  </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;
@@ -7,16 +7,13 @@
7
7
  }
8
8
 
9
9
  let { title, homeItems }: HomeProps = $props();
10
-
11
- const hasSingleItem: boolean = homeItems.length === 1;
12
- const hasManyItems: boolean = homeItems.length > 1;
13
10
  </script>
14
11
 
15
- <h1 class={['home-title', hasSingleItem && 'centered']}>
12
+ <h1 class="home-title">
16
13
  {title}
17
14
  </h1>
18
15
 
19
- <section class={['home-container', hasSingleItem && 'centered', hasManyItems && 'grid']}>
16
+ <section class="home-container grid">
20
17
  {#each homeItems as homeItem}
21
18
  <a href={homeItem.url} class="home-item">
22
19
  <img src={homeItem.icon} alt={homeItem.text} />
@@ -36,23 +33,12 @@
36
33
  font-weight: 600;
37
34
  }
38
35
 
39
- .home-title.centered {
40
- display: flex;
41
- justify-content: center;
42
- }
43
-
44
36
  .home-container {
45
37
  --black-1: #434952;
46
38
  --black-2: #273444;
47
39
  --white: #fff;
48
40
  }
49
41
 
50
- .home-container.centered {
51
- display: flex;
52
- flex-direction: column;
53
- align-items: center;
54
- }
55
-
56
42
  .home-container.grid {
57
43
  display: grid;
58
44
  gap: 24px;
@@ -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.2",
3
+ "version": "1.5.4",
4
4
  "description": "Svelte components",
5
5
  "keywords": [
6
6
  "svelte",