design-system-next 2.11.20 → 2.11.22

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.
@@ -1,31 +1,40 @@
1
1
  <template>
2
- <div :class="lozengeClasses.wrapperClasses">
3
- <div v-if="visible && !loading" :class="lozengeClasses.baseClasses">
4
- <div :class="lozengeClasses.toneClasses">
5
- <div v-if="$slots.icon" class="spr-flex spr-h-3 spr-w-3 spr-items-center spr-overflow-hidden">
6
- <slot name="icon" />
2
+ <div :class="['spr-lozenge__wrapper', lozengeClasses.wrapperClasses]">
3
+ <div v-if="visible && !loading" :class="['spr-lozenge__base', lozengeClasses.baseClasses]">
4
+ <div :class="['spr-lozenge__tone',lozengeClasses.toneClasses]" :tabindex="props.interactive || props.dropdown ? 0 : -1">
5
+ <div v-if="$slots.icon || props.icon" class="spr-lozenge__prefix-icon spr-flex spr-h-3 spr-w-3 spr-items-center spr-overflow-hidden">
6
+ <slot name="icon">
7
+ <icon :icon="props.icon" />
8
+ </slot>
7
9
  </div>
8
10
 
9
- <div v-if="$slots.avatar" class="spr-flex spr-items-center">
10
- <slot name="avatar" />
11
+ <div v-if="$slots.avatar || props.url" class="spr-lozenge__avatar">
12
+ <slot name="avatar">
13
+ <div v-if="url" class="spr-h-4 spr-w-4 spr-overflow-hidden">
14
+ <img class="spr-h-full spr-w-full spr-rounded-full spr-object-cover" :src="url" alt="avatar" />
15
+ </div>
16
+ </slot>
11
17
  </div>
12
18
 
13
- <div v-if="url && !$slots.avatar" class="spr-h-4 spr-w-4 spr-overflow-hidden">
14
- <img class="spr-h-full spr-w-full spr-rounded-full spr-object-cover" :src="url" alt="avatar" />
15
- </div>
19
+ <span class="spr-lozenge__label spr-label-xs-medium">{{ label }}</span>
16
20
 
17
- <div>{{ label }}</div>
21
+ <div v-if="$slots.postfixIcon || props.postfixIcon || props.dropdown" class="spr-lozenge__prefix-icon spr-flex spr-h-3 spr-w-3 spr-items-center spr-overflow-hidden">
22
+ <slot name="postfixIcon">
23
+ <icon v-if="props.postfixIcon" :icon="props.postfixIcon" />
24
+ <icon v-else icon="ph:caret-down-fill" />
25
+ </slot>
26
+ </div>
18
27
  </div>
19
28
  </div>
20
- <div v-if="visible && loading" :class="lozengeClasses.baseClasses" />
29
+ <div v-if="visible && loading" :class="['spr-lozenge__loading', lozengeClasses.baseClasses]" />
21
30
  </div>
22
31
  </template>
23
32
 
24
33
  <script lang="ts" setup>
25
34
  import { lozengePropTypes } from './lozenge';
26
35
  import { useLozenge } from './use-lozenge';
36
+ import { Icon } from '@iconify/vue';
27
37
 
28
38
  const props = defineProps(lozengePropTypes);
29
-
30
39
  const { lozengeClasses } = useLozenge(props);
31
40
  </script>
@@ -1,7 +1,5 @@
1
- import { toRefs, computed, ComputedRef } from 'vue';
2
-
1
+ import { toRefs, computed, ComputedRef, useSlots } from 'vue';
3
2
  import classNames from 'classnames';
4
-
5
3
  import type { LozengePropTypes } from './lozenge';
6
4
 
7
5
  interface LozengeClasses {
@@ -11,7 +9,9 @@ interface LozengeClasses {
11
9
  }
12
10
 
13
11
  export const useLozenge = (props: LozengePropTypes) => {
14
- const { tone, fill, loading } = toRefs(props);
12
+ const { tone, fill, loading, interactive, url, dropdown } = toRefs(props);
13
+ const slots = useSlots();
14
+ const isInteractive = computed(() => interactive.value || dropdown.value);
15
15
 
16
16
  const lozengeClasses: ComputedRef<LozengeClasses> = computed(() => {
17
17
  const wrapperClasses = classNames({
@@ -25,31 +25,62 @@ export const useLozenge = (props: LozengePropTypes) => {
25
25
  });
26
26
 
27
27
  const toneClasses = classNames(
28
- 'spr-label-xs-medium spr-inline-flex spr-items-center spr-gap-size-spacing-6xs spr-rounded-md spr-border spr-border-solid spr-p-size-spacing-5xs spr-text-xs spr-uppercase',
28
+ 'spr-box-border spr-h-[20px] spr-inline-flex spr-items-center spr-gap-size-spacing-6xs spr-rounded-md spr-border-solid spr-p-size-spacing-5xs spr-text-xs spr-uppercase',
29
29
  {
30
- 'spr-text-color-pending-base spr-background-color-pending-weak !spr-border-color-pending-base':
31
- tone.value === 'pending' && !fill.value,
32
- 'spr-text-color-information-base spr-background-color-information-weak !spr-border-color-information-base':
33
- tone.value === 'information' && !fill.value,
34
- 'spr-text-color-success-base spr-background-color-success-weak !spr-border-color-success-base':
35
- tone.value === 'success' && !fill.value,
36
- 'spr-text-color-base spr-background-color-base !spr-border-color-base': tone.value === 'neutral' && !fill.value,
37
- 'spr-text-color-danger-base spr-background-color-danger-weak !spr-border-color-danger-base':
38
- tone.value === 'danger' && !fill.value,
39
- 'spr-text-color-caution-base spr-background-color-caution-weak !spr-border-color-caution-base':
40
- tone.value === 'caution' && !fill.value,
41
- 'spr-text-color-strong !spr-border-color-base spr-background-color': tone.value === 'plain' && !fill.value,
30
+ 'spr-h-[20px]': !url.value,
31
+ 'spr-h-[24px]': url.value || slots.avatar,
32
+ 'spr-border': !fill.value,
33
+ 'spr-cursor-pointer': isInteractive.value,
34
+ // #region - Styles for hollow lozenge
35
+ // Pending
36
+ 'spr-text-mango-800 spr-background-color-pending-weak spr-border-mango-800': tone.value === 'pending' && !fill.value,
37
+ 'hover:spr-bg-[#FFF89E] active:spr-bg-[#FFF56B]': tone.value === 'pending' && !fill.value && isInteractive.value,
38
+ // Information
39
+ 'spr-text-blueberry-800 spr-background-color-information-weak spr-border-blueberry-800': tone.value === 'information' && !fill.value,
40
+ 'hover:spr-bg-[#ADD6FF] active:spr-bg-[#7ABDFF]': tone.value === 'information' && !fill.value && isInteractive.value,
41
+ // Success
42
+ 'spr-text-kangkong-800 spr-background-color-success-weak spr-border-kangkong-800': tone.value === 'success' && !fill.value,
43
+ 'hover:spr-bg-[#AFF8C6] active:spr-bg-[#80F4A4]': tone.value === 'success' && !fill.value && isInteractive.value,
44
+ // Neutral
45
+ 'spr-text-mushroom-800 spr-background-color-base spr-border-mushroom-800': tone.value === 'neutral' && !fill.value,
46
+ 'hover:spr-bg-[#D3D9D9] active:spr-bg-[#B8C1C1]': tone.value === 'neutral' && !fill.value && isInteractive.value,
47
+ // Danger
48
+ 'spr-text-tomato-800 spr-background-color-danger-weak spr-border-tomato-800': tone.value === 'danger' && !fill.value,
49
+ 'hover:spr-bg-[#FCB0B3] active:spr-bg-[#FB7F83]': tone.value === 'danger' && !fill.value && isInteractive.value,
50
+ // Caution
51
+ 'spr-text-carrot-800 spr-background-color-caution-weak spr-border-carrot-800': tone.value === 'caution' && !fill.value,
52
+ 'hover:spr-bg-[#FFE79E] active:spr-bg-[#FFDA6B]': tone.value === 'caution' && !fill.value && isInteractive.value,
53
+ // Plain
54
+ 'spr-text-color-strong spr-border-color-base spr-background-color': tone.value === 'plain' && !fill.value,
55
+ 'hover:spr-bg-[#E6E6E6] active:spr-bg-[#CCCCCC]': tone.value === 'plain' && !fill.value && isInteractive.value,
56
+ // #endregion - Styles for hollow (!fill) lozenge
42
57
 
43
- 'spr-background-color-pending-base spr-text-color-strong !spr-border-0': tone.value === 'pending' && fill.value,
44
- 'spr-background-color-information-base spr-text-color-inverted-strong !spr-border-0':
45
- tone.value === 'information' && fill.value,
46
- 'spr-background-color-success-base spr-text-color-inverted-strong !spr-border-0':
47
- tone.value === 'success' && fill.value,
48
- 'spr-background-color-base spr-text-color-strong !spr-border-0': tone.value === 'neutral' && fill.value,
49
- 'spr-background-color-danger-base spr-text-color-inverted-strong !spr-border-0':
50
- tone.value === 'danger' && fill.value,
51
- 'spr-background-color-caution-base spr-text-color-strong !spr-border-0': tone.value === 'caution' && fill.value,
52
- 'spr-text-color-strong spr-background-color !spr-border-0': tone.value === 'plain' && fill.value,
58
+ // #region - Styles for filled lozenge
59
+ 'spr-border-0': fill.value,
60
+ 'spr-text-color-strong': fill.value && (tone.value === 'pending' || tone.value === 'neutral' || tone.value === 'caution' || tone.value === 'plain'),
61
+ 'spr-text-color-inverted-strong': fill.value && (tone.value === 'information' || tone.value === 'success' || tone.value === 'danger'),
62
+ // Pending
63
+ 'spr-background-color-pending-base': tone.value === 'pending' && fill.value,
64
+ 'hover:spr-background-color-pending-hover active:spr-background-color-pending-pressed': tone.value === 'pending' && fill.value && isInteractive.value,
65
+ // Information
66
+ 'spr-background-color-information-base': tone.value === 'information' && fill.value,
67
+ 'hover:spr-background-color-information-hover active:spr-background-color-information-pressed': tone.value === 'information' && fill.value && isInteractive.value,
68
+ // Success
69
+ 'spr-background-color-success-base': tone.value === 'success' && fill.value,
70
+ 'hover:spr-background-color-success-hover active:spr-background-color-success-pressed': tone.value === 'success' && fill.value && isInteractive.value,
71
+ // Neutral
72
+ 'spr-background-color-base': tone.value === 'neutral' && fill.value,
73
+ 'hover:spr-background-color-surface active:spr-background-color-pressed': tone.value === 'neutral' && fill.value && isInteractive.value,
74
+ // Danger
75
+ 'spr-background-color-danger-base': tone.value === 'danger' && fill.value,
76
+ 'hover:spr-background-color-danger-hover active:spr-background-color-danger-pressed': tone.value === 'danger' && fill.value && isInteractive.value,
77
+ // Caution
78
+ 'spr-background-color-caution-base': tone.value === 'caution' && fill.value,
79
+ 'hover:spr-background-color-caution-hover active:spr-background-color-caution-pressed': tone.value === 'caution' && fill.value && isInteractive.value,
80
+ // Plain
81
+ 'spr-background-color': tone.value === 'plain' && fill.value,
82
+ 'hover:spr-background-color-hover active:spr-background-color-pressed': tone.value === 'plain' && fill.value && isInteractive.value,
83
+ // #endregion - Styles for filled lozenge
53
84
  },
54
85
  );
55
86
 
@@ -32,6 +32,7 @@ export type ParentLinkItem = {
32
32
  menuLinks: MenuLink[];
33
33
  submenuLinks?: SubmenuLink[];
34
34
  hidden?: boolean;
35
+ attributes?: Attributes[];
35
36
  };
36
37
 
37
38
  export type MenuLink = {
@@ -43,6 +44,7 @@ export type MenuLinkItem = {
43
44
  title: string;
44
45
  hidden: boolean;
45
46
  redirect: Redirect;
47
+ attributes?: Attributes[];
46
48
  submenuLinks: SubmenuLink[];
47
49
  };
48
50
 
@@ -55,6 +57,7 @@ export type SubmenuLinkItem = {
55
57
  title: string;
56
58
  hidden: boolean;
57
59
  redirect: Redirect;
60
+ attributes?: Attributes[];
58
61
  };
59
62
 
60
63
  export type Redirect = {
@@ -85,6 +88,16 @@ export interface NavItem {
85
88
  children?: NavItem[] | null;
86
89
  groupName?: string | null;
87
90
  hidden?: boolean;
91
+ attributes?: Attributes[] | null;
92
+ }
93
+
94
+ export type Attributes = {
95
+ name: string;
96
+ value: unknown | string | number | boolean | AttrLozenge;
97
+ };
98
+ type AttrLozenge = {
99
+ label: string;
100
+ tone?: string;
88
101
  }
89
102
 
90
103
  export interface MappedNavItem {
@@ -95,6 +108,7 @@ export interface MappedNavItem {
95
108
  isAbsoluteURL: boolean;
96
109
  link: string;
97
110
  };
111
+ attributes?: AttrLozenge | unknown [];
98
112
  menuLinks?: {
99
113
  menuHeading: string;
100
114
  items: MappedNavItem[];
@@ -241,10 +241,20 @@
241
241
  class="spr-background-color-brand-base spr-absolute spr-left-0 spr-top-0 spr-h-full spr-w-[2px]"
242
242
  ></div>
243
243
  <span>{{ menuLinkItem.title }}</span>
244
- <Icon
245
- icon="ph:caret-right"
246
- class="spr-h-[16px] spr-w-[16px] spr-transform spr-font-normal spr-transition-transform spr-duration-300"
247
- />
244
+ <div class="spr-flex spr-items-center spr-gap-1">
245
+ <template v-for="(attr, i) in menuLinkItem?.attributes" :key="i">
246
+ <spr-lozenge
247
+ v-if="attr?.name === 'lozenge' && attr?.value"
248
+ :label="(attr.value && typeof attr?.value === 'object' && 'label' in attr.value) ? String(attr.value.label) : ''"
249
+ :tone="getLozengeTone(attr)"
250
+ fill
251
+ />
252
+ </template>
253
+ <Icon
254
+ icon="ph:caret-right"
255
+ class="spr-h-[16px] spr-w-[16px] spr-transform spr-font-normal spr-transition-transform spr-duration-300"
256
+ />
257
+ </div>
248
258
  </div>
249
259
  <!-- #endregion - Menu links -->
250
260
 
@@ -301,6 +311,16 @@
301
311
  class="spr-background-color-brand-base spr-absolute spr-left-0 spr-top-0 spr-h-full spr-w-[2px]"
302
312
  ></div>
303
313
  <span>{{ submenuLinkItem.title }}</span>
314
+ <div class="spr-flex spr-items-center spr-gap-1">
315
+ <template v-for="(attr, i) in submenuLinkItem?.attributes" :key="i">
316
+ <spr-lozenge
317
+ v-if="attr?.name === 'lozenge' && attr?.value"
318
+ :label="(attr.value && typeof attr?.value === 'object' && 'label' in attr.value) ? String(attr.value.label) : ''"
319
+ :tone="getLozengeTone(attr)"
320
+ fill
321
+ />
322
+ </template>
323
+ </div>
304
324
  </div>
305
325
  <!-- #endregion - Submenu Links -->
306
326
  </template>
@@ -330,6 +350,14 @@
330
350
  class="spr-background-color-brand-base spr-absolute spr-left-0 spr-top-0 spr-h-full spr-w-[2px]"
331
351
  ></div>
332
352
  <span>{{ menuLinkItem.title }}</span>
353
+ <template v-for="(attr, i) in menuLinkItem?.attributes" :key="i">
354
+ <spr-lozenge
355
+ v-if="attr?.name === 'lozenge' && attr?.value"
356
+ :label="(attr.value && typeof attr?.value === 'object' && 'label' in attr.value) ? String(attr.value.label) : ''"
357
+ :tone="getLozengeTone(attr)"
358
+ fill
359
+ />
360
+ </template>
333
361
  </div>
334
362
  </template>
335
363
  <!-- #endregion - Menu link only -->
@@ -765,7 +793,7 @@
765
793
  @click="isUserMenuVisible = !isUserMenuVisible"
766
794
  >
767
795
  <template v-if="props.userMenu.profileImage && !userProfileError">
768
- <img :src="props.userMenu.profileImage" alt="profile-image" @error="userProfileError = true" />
796
+ <img :src="props.userMenu.profileImage" alt="profile" @error="userProfileError = true" />
769
797
  </template>
770
798
  <template v-else>
771
799
  <span>{{ getUserInitials(props.userMenu.name) }}</span>
@@ -788,7 +816,7 @@
788
816
  ]"
789
817
  >
790
818
  <template v-if="props.userMenu.profileImage && !userProfileError">
791
- <img :src="props.userMenu.profileImage" alt="profile-image" @error="userProfileError = true" />
819
+ <img :src="props.userMenu.profileImage" alt="profile" @error="userProfileError = true" />
792
820
  </template>
793
821
  <template v-else>
794
822
  <span>{{ getUserInitials(props.userMenu.name) }}</span>
@@ -847,6 +875,7 @@ import { useSidenav } from './use-sidenav';
847
875
 
848
876
  import SprTooltip from '../tooltip/tooltip.vue';
849
877
  import SprBadge from '../badge/badge.vue';
878
+ import SprLozenge from '../lozenge/lozenge.vue';
850
879
 
851
880
  const props = defineProps(sidenavPropTypes);
852
881
  const emit = defineEmits(sidenavEmitTypes);
@@ -859,5 +888,6 @@ const {
859
888
  getUserInitials,
860
889
  handleRedirect,
861
890
  generateId,
891
+ getLozengeTone,
862
892
  } = useSidenav(props, emit);
863
893
  </script>
@@ -1,7 +1,7 @@
1
- import { ref, toRefs, onMounted } from 'vue';
1
+ import { ref, onMounted } from 'vue';
2
2
 
3
3
  import type { SetupContext } from 'vue';
4
- import type { SidenavPropTypes, SidenavEmitTypes, ParentLinkItem, NavLinks, NavItem, MenuLinkItem } from './sidenav';
4
+ import type { SidenavPropTypes, SidenavEmitTypes, ParentLinkItem, NavLinks, NavItem, MenuLinkItem, Attributes } from './sidenav';
5
5
 
6
6
  interface ObjectItem {
7
7
  redirect?: {
@@ -17,7 +17,8 @@ interface ObjectItem {
17
17
  }
18
18
 
19
19
  export const useSidenav = (props: SidenavPropTypes, emit: SetupContext<SidenavEmitTypes>['emit']) => {
20
- const { navLinks } = toRefs(props);
20
+ // Create a separate reactive reference for the navigation data
21
+ const navLinks = ref<NavLinks>({ top: [], bottom: [] });
21
22
 
22
23
  const isQuckActionMenuVisible = ref(false);
23
24
  const isUserMenuVisible = ref(false);
@@ -148,6 +149,7 @@ export const useSidenav = (props: SidenavPropTypes, emit: SetupContext<SidenavEm
148
149
  subMenuHeading: string;
149
150
  items: ParentLinkItem[];
150
151
  }[],
152
+ attributes: item.attributes || [],
151
153
  };
152
154
  };
153
155
 
@@ -180,9 +182,19 @@ export const useSidenav = (props: SidenavPropTypes, emit: SetupContext<SidenavEm
180
182
  return transformedData;
181
183
  };
182
184
 
185
+ const getLozengeTone = (attr: Attributes) => {
186
+ if (typeof attr === 'object' && attr !== null && 'tone' in attr && typeof attr.tone === 'string' && ['danger', 'information', 'plain', 'pending', 'success', 'neutral', 'caution'].includes(attr.tone)) {
187
+ return attr.tone as 'danger' | 'information' | 'plain' | 'pending' | 'success' | 'neutral' | 'caution';
188
+ }
189
+ return 'success'; // Default tone
190
+ };
191
+
183
192
  onMounted(async () => {
184
193
  if (props.isNavApi) {
185
194
  navLinks.value = await transformedNavItems(props.navLinks);
195
+ } else {
196
+ // Use the original navLinks from props
197
+ navLinks.value = props.navLinks;
186
198
  }
187
199
  });
188
200
 
@@ -195,5 +207,6 @@ export const useSidenav = (props: SidenavPropTypes, emit: SetupContext<SidenavEm
195
207
  handleRedirect,
196
208
  generateId,
197
209
  transformedNavItems,
210
+ getLozengeTone
198
211
  };
199
212
  };