@weni/unnnic-system 3.25.6 → 3.26.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 (69) hide show
  1. package/dist/{es-CNigXHb1.mjs → es-Dhz_Vb5J.mjs} +1 -1
  2. package/dist/{index-B-PMK0Fu.mjs → index-CyIfw1Zx.mjs} +6972 -6910
  3. package/dist/index.d.ts +60 -2
  4. package/dist/{pt-br-D4aJiC1v.mjs → pt-br-LgmWO25q.mjs} +1 -1
  5. package/dist/style.css +1 -1
  6. package/dist/tokens/colors.d.ts +2 -0
  7. package/dist/tokens/colors.mjs +2 -0
  8. package/dist/unnnic.mjs +18 -17
  9. package/dist/unnnic.umd.js +29 -29
  10. package/package.json +2 -1
  11. package/src/assets/scss/colors-hsl.scss +2 -0
  12. package/src/assets/scss/colors.scss +0 -43
  13. package/src/assets/scss/deprecated/colors.scss +1 -0
  14. package/src/assets/scss/scheme-colors.scss +3 -0
  15. package/src/assets/scss/semantic-colors.scss +48 -0
  16. package/src/assets/scss/semantic-text-utilities.scss +9 -0
  17. package/src/assets/scss/tailwind.scss +1 -1
  18. package/src/assets/scss/theme.scss +98 -0
  19. package/src/assets/scss/unnnic.scss +2 -0
  20. package/src/assets/tokens/colors-primitives.json +155 -0
  21. package/src/assets/tokens/colors-semantic-dark.json +55 -0
  22. package/src/assets/tokens/colors-semantic.json +55 -0
  23. package/src/components/Alert/__tests__/__snapshots__/Alert.spec.js.snap +1 -1
  24. package/src/components/AudioRecorder/AudioTranscriptionButton.vue +3 -1
  25. package/src/components/Button/Button.vue +17 -18
  26. package/src/components/Card/CardCompany.vue +1 -1
  27. package/src/components/CardNumber/CardNumber.vue +1 -1
  28. package/src/components/CardProject/CardProject.vue +4 -4
  29. package/src/components/Carousel/TagCarousel.vue +2 -2
  30. package/src/components/ChartRainbow/ChartRainbow.vue +2 -2
  31. package/src/components/ChatsContact/ChatsContact.vue +70 -10
  32. package/src/components/ChatsContact/__tests__/ChatsContact.spec.js +119 -0
  33. package/src/components/ChatsContact/__tests__/__snapshots__/ChatsContact.spec.js.snap +1 -1
  34. package/src/components/ChatsMessage/ChatsMessage.vue +6 -7
  35. package/src/components/ChatsMessage/ChatsMessageStatusBackdrop.vue +1 -1
  36. package/src/components/ChatsMessage/ReplyMessage.vue +4 -4
  37. package/src/components/ChatsNavbar/ChatsNavbar.vue +1 -1
  38. package/src/components/ChatsUserAvatar/ChatsUserAvatar.vue +3 -3
  39. package/src/components/Chip/Chip.vue +20 -26
  40. package/src/components/Chip/__tests__/Chip.spec.js +3 -3
  41. package/src/components/DataTable/index.vue +2 -2
  42. package/src/components/DatePicker/DatePicker.vue +4 -4
  43. package/src/components/EmojiPicker/EmojiPicker.vue +40 -4
  44. package/src/components/EmojiPicker/__tests__/EmojiPicker.spec.js +1 -1
  45. package/src/components/FormElement/FormElement.vue +1 -1
  46. package/src/components/Input/Input.scss +2 -2
  47. package/src/components/Input/TextInput.vue +1 -1
  48. package/src/components/SelectTime/index.vue +1 -1
  49. package/src/components/Sidebar/index.vue +1 -1
  50. package/src/components/SkeletonLoading/skeletonTheme.vue +2 -4
  51. package/src/components/Table/Table.vue +1 -1
  52. package/src/components/Tag/DefaultTag.vue +9 -11
  53. package/src/components/TemplatePreview/TemplatePreview.vue +1 -1
  54. package/src/components/Toast/Toast.vue +4 -4
  55. package/src/components/Toast/__tests__/Toast.spec.js +5 -5
  56. package/src/components/ToolTip/ToolTip.vue +1 -1
  57. package/src/components/Tour/TourMask.vue +1 -1
  58. package/src/components/ui/dialog/DialogContent.vue +1 -1
  59. package/src/components/ui/popover/PopoverContent.vue +1 -1
  60. package/src/components/ui/popover/PopoverOption.vue +2 -2
  61. package/src/components/ui/tooltip/TooltipContent.vue +5 -4
  62. package/src/composables/useTheme.ts +67 -0
  63. package/src/index.ts +2 -0
  64. package/src/stories/ChatsContact.stories.js +53 -0
  65. package/src/stories/Colors.stories.js +6 -1
  66. package/src/stories/Icon.stories.js +1 -0
  67. package/src/stories/IconLoading.stories.js +1 -0
  68. package/src/types/scheme-colors.d.ts +2 -0
  69. package/src/assets/tokens/colors.json +0 -552
@@ -11,7 +11,7 @@
11
11
  ]"
12
12
  @click="onClick"
13
13
  >
14
- <p :class="['chip__text', textColorClass]">{{ text }}</p>
14
+ <p class="chip__text">{{ text }}</p>
15
15
  <p
16
16
  v-if="count"
17
17
  :class="['chip__count', countColorClass]"
@@ -22,7 +22,7 @@
22
22
  <UnnnicIcon
23
23
  v-if="type === 'multiple'"
24
24
  :icon="isSelected ? 'close' : 'add'"
25
- :scheme="isSelected ? 'teal-600' : 'feedback-grey'"
25
+ scheme="fg-emphasized"
26
26
  class="chip__icon"
27
27
  size="sm"
28
28
  />
@@ -48,13 +48,6 @@ const shouldShowBorder = computed(() => {
48
48
  return !props.isSelected;
49
49
  });
50
50
 
51
- const textColorClass = computed(() => {
52
- if (props.isSelected) {
53
- return 'chip__text--is-selected';
54
- }
55
- return '';
56
- });
57
-
58
51
  const countColorClass = computed(() => {
59
52
  if (props.isSelected) {
60
53
  return 'chip__count--is-selected';
@@ -78,18 +71,25 @@ const chipClass = computed(() => {
78
71
  align-items: center;
79
72
  gap: $unnnic-space-2;
80
73
  flex-shrink: 0;
81
- border-radius: 600px;
74
+ border-radius: $unnnic-radius-full;
82
75
  background-color: $unnnic-color-bg-base;
83
- border: 1px solid transparent;
76
+ border: 1px solid $unnnic-color-border-base;
77
+
78
+ cursor: default;
79
+
80
+ transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1); // Same as UnnnicButton
84
81
 
85
82
  &:hover {
86
83
  background-color: $unnnic-color-bg-soft;
87
84
  }
88
85
 
89
86
  &--is-selected {
90
- background-color: $unnnic-color-teal-100;
87
+ background-color: $unnnic-color-bg-accent-plain;
88
+ border-color: transparent;
89
+
91
90
  &:hover {
92
- background-color: $unnnic-color-teal-200;
91
+ background-color: $unnnic-color-bg-accent-plain;
92
+ border-color: $unnnic-color-border-accent-plain;
93
93
  }
94
94
  }
95
95
 
@@ -102,12 +102,8 @@ const chipClass = computed(() => {
102
102
  }
103
103
 
104
104
  &__text {
105
- color: $unnnic-color-fg-base;
106
- font: $unnnic-font-caption-1;
107
-
108
- &--is-selected {
109
- color: $unnnic-color-fg-active;
110
- }
105
+ color: $unnnic-color-fg-emphasized;
106
+ @include unnnic-font-caption-1;
111
107
  }
112
108
 
113
109
  &__count {
@@ -116,15 +112,13 @@ const chipClass = computed(() => {
116
112
  justify-content: center;
117
113
  align-items: center;
118
114
  gap: 10px;
119
- border-radius: 600px;
120
- background: $unnnic-color-bg-soft;
121
- color: $unnnic-color-fg-base;
122
- font: $unnnic-font-caption-1;
123
- font-weight: $unnnic-font-weight-medium;
115
+ border-radius: $unnnic-radius-full;
116
+ background: $unnnic-color-bg-muted;
117
+ color: $unnnic-color-fg-emphasized;
118
+ @include unnnic-font-caption-2;
124
119
 
125
120
  &--is-selected {
126
- color: $unnnic-color-fg-active;
127
- background: $unnnic-color-white;
121
+ background: $unnnic-color-bg-teal-plain;
128
122
  }
129
123
  }
130
124
  }
@@ -42,7 +42,7 @@ describe('Chip', () => {
42
42
  expect(wrapper.classes()).not.toContain('chip--border');
43
43
 
44
44
  const textElement = wrapper.find('.chip__text');
45
- expect(textElement.classes()).toContain('chip__text--is-selected');
45
+ expect(textElement.exists()).toBe(true);
46
46
  });
47
47
  });
48
48
 
@@ -82,14 +82,14 @@ describe('Chip', () => {
82
82
  let icon = wrapper.findComponent({ name: 'UnnnicIcon' });
83
83
  expect(icon.exists()).toBe(true);
84
84
  expect(icon.props('icon')).toBe('add');
85
- expect(icon.props('scheme')).toBe('feedback-grey');
85
+ expect(icon.props('scheme')).toBe('fg-emphasized');
86
86
  expect(icon.props('size')).toBe('sm');
87
87
 
88
88
  await wrapper.setProps({ isSelected: true });
89
89
 
90
90
  icon = wrapper.findComponent({ name: 'UnnnicIcon' });
91
91
  expect(icon.props('icon')).toBe('close');
92
- expect(icon.props('scheme')).toBe('teal-600');
92
+ expect(icon.props('scheme')).toBe('fg-emphasized');
93
93
  });
94
94
  });
95
95
 
@@ -398,7 +398,7 @@ $tableBorder: 1px solid $unnnic-color-border-base;
398
398
  }
399
399
 
400
400
  &::-webkit-scrollbar-thumb {
401
- background: $unnnic-color-gray-7;
401
+ background: $unnnic-color-border-emphasized;
402
402
  border-radius: $unnnic-border-radius-pill;
403
403
  }
404
404
 
@@ -458,7 +458,7 @@ $tableBorder: 1px solid $unnnic-color-border-base;
458
458
  }
459
459
 
460
460
  &::-webkit-scrollbar-thumb {
461
- background: $unnnic-color-gray-7;
461
+ background: $unnnic-color-border-emphasized;
462
462
  border-radius: $unnnic-border-radius-pill;
463
463
  }
464
464
 
@@ -1068,12 +1068,12 @@ onMounted(() => {
1068
1068
  .selected {
1069
1069
  font-weight: $unnnic-font-weight-black;
1070
1070
  background-color: $unnnic-color-bg-base-soft;
1071
- color: $unnnic-color-teal-8;
1071
+ color: $unnnic-color-fg-accent;
1072
1072
  border-radius: 0;
1073
1073
 
1074
1074
  &.highlighted {
1075
- background-color: $unnnic-color-teal-8;
1076
- color: $unnnic-color-fg-inverted;
1075
+ background-color: $unnnic-color-bg-accent-strong;
1076
+ color: $unnnic-color-fg-on-primary;
1077
1077
 
1078
1078
  &.left {
1079
1079
  border-top-left-radius: $unnnic-border-radius-sm;
@@ -1128,7 +1128,7 @@ onMounted(() => {
1128
1128
 
1129
1129
  &.selected {
1130
1130
  font-weight: $unnnic-font-weight-bold;
1131
- color: $unnnic-color-teal-8;
1131
+ color: $unnnic-color-fg-accent;
1132
1132
  }
1133
1133
  }
1134
1134
  }
@@ -62,7 +62,7 @@ const emit = defineEmits<{
62
62
  const emojiPickerRef = ref<HTMLElement>();
63
63
  const emojiIndex = computed(() => new EmojiIndex(data));
64
64
 
65
- const accentColor = '#00A49F'; // $unnnic-color-weni-600
65
+ const accentColor = 'var(--unnnic-color-fg-accent)';
66
66
 
67
67
  const currentLocale = computed(() => props.locale || 'pt-br');
68
68
 
@@ -125,6 +125,7 @@ const onEmojiSelect = (emoji: Emoji) => {
125
125
 
126
126
  <style lang="scss" scoped>
127
127
  @use '@/assets/scss/unnnic' as *;
128
+ @use '@/components/Input/Input.scss' as *;
128
129
 
129
130
  .emoji-picker {
130
131
  position: absolute;
@@ -146,6 +147,7 @@ const onEmojiSelect = (emoji: Emoji) => {
146
147
  border-radius: $unnnic-radius-4;
147
148
  font-family: $unnnic-font-family;
148
149
  border: 1px solid $unnnic-color-border-base;
150
+ background-color: $unnnic-color-bg-base;
149
151
  box-shadow: $unnnic-shadow-1;
150
152
  cursor: default;
151
153
  color: $unnnic-color-fg-emphasized;
@@ -153,15 +155,33 @@ const onEmojiSelect = (emoji: Emoji) => {
153
155
 
154
156
  :deep(.emoji-mart-emoji) {
155
157
  cursor: pointer;
158
+
159
+ &::before {
160
+ background-color: $unnnic-color-bg-base-soft;
161
+ }
156
162
  }
157
163
 
158
- :deep(.emoji-mart-category .emoji-mart-emoji span) {
159
- cursor: pointer;
164
+ :deep(.emoji-mart-search) {
165
+ margin: $unnnic-space-2 0;
166
+
167
+ input {
168
+ @include input-base;
169
+ }
170
+ }
171
+
172
+ :deep(.emoji-mart-category) {
173
+ .emoji-mart-emoji span {
174
+ cursor: pointer;
175
+ }
176
+
177
+ .emoji-mart-category-label {
178
+ background-color: transparent;
179
+ }
160
180
  }
161
181
 
162
182
  :deep(.emoji-mart-anchor) {
163
183
  cursor: pointer;
164
- color: #858585;
184
+ color: $unnnic-color-fg-base;
165
185
  }
166
186
 
167
187
  :deep(.emoji-mart-anchor:hover),
@@ -182,6 +202,22 @@ const onEmojiSelect = (emoji: Emoji) => {
182
202
  :deep(.emoji-type-image.emoji-set-twitter) {
183
203
  background-image: none !important;
184
204
  }
205
+
206
+ :deep(.emoji-mart-scroll) {
207
+ &::-webkit-scrollbar {
208
+ width: $unnnic-space-2;
209
+ }
210
+
211
+ &::-webkit-scrollbar-thumb {
212
+ background: $unnnic-color-border-emphasized;
213
+ border-radius: $unnnic-radius-full;
214
+ }
215
+
216
+ &::-webkit-scrollbar-track {
217
+ background: $unnnic-color-border-muted;
218
+ border-radius: $unnnic-radius-full;
219
+ }
220
+ }
185
221
  }
186
222
 
187
223
  @keyframes slideInUp {
@@ -102,7 +102,7 @@ describe('UnnnicEmojiPicker', () => {
102
102
  expect(picker.props('navPosition')).toBe('bottom');
103
103
  expect(picker.props('noResultsEmoji')).toBe('cry');
104
104
  expect(picker.props('maxFrequentRows')).toBe(3);
105
- expect(picker.props('color')).toBe('#00A49F');
105
+ expect(picker.props('color')).toBe('var(--unnnic-color-fg-accent)');
106
106
  expect(picker.props('i18n')).toBeDefined();
107
107
  });
108
108
 
@@ -106,7 +106,7 @@ withDefaults(defineProps<FormElementProps>(), {
106
106
  bottom: $unnnic-space-1 - 1px;
107
107
  width: 100%;
108
108
  height: 1px;
109
- background-color: $unnnic-color-white;
109
+ background-color: $unnnic-color-bg-base;
110
110
  }
111
111
  }
112
112
  }
@@ -1,7 +1,7 @@
1
1
  @use '@/assets/scss/unnnic' as *;
2
2
 
3
3
  @mixin input-base {
4
- background: $unnnic-color-white;
4
+ background: $unnnic-color-bg-base;
5
5
  border: 1px solid $unnnic-color-border-base;
6
6
  outline: none;
7
7
  border-radius: $unnnic-radius-2;
@@ -13,7 +13,7 @@
13
13
 
14
14
  &:focus:not(.use-focus-prop),
15
15
  &.focus {
16
- border-color: $unnnic-color-border-active;
16
+ border-color: $unnnic-color-border-accent-strong;
17
17
  }
18
18
 
19
19
  &::placeholder {
@@ -33,7 +33,7 @@
33
33
  <UnnnicIcon
34
34
  v-if="showClear"
35
35
  class="icon-clear"
36
- scheme="gray-7"
36
+ scheme="fg-base"
37
37
  icon="close"
38
38
  size="ant"
39
39
  clickable
@@ -207,7 +207,7 @@ export default {
207
207
  }
208
208
 
209
209
  &::-webkit-scrollbar-thumb {
210
- background: $unnnic-color-gray-7;
210
+ background: $unnnic-color-border-emphasized;
211
211
  border-radius: $unnnic-border-radius-pill;
212
212
  }
213
213
 
@@ -113,7 +113,7 @@ const handleNavigate = ({ item, child }) => {
113
113
  }
114
114
 
115
115
  &::-webkit-scrollbar-thumb {
116
- background: $unnnic-color-gray-7;
116
+ background: $unnnic-color-border-emphasized;
117
117
  border-radius: $unnnic-border-radius-pill;
118
118
  }
119
119
 
@@ -12,11 +12,9 @@
12
12
 
13
13
  <script>
14
14
  import { ref, provide } from 'vue';
15
- import colorsTokens from '@/assets/tokens/colors.json';
16
15
 
17
- const { gray } = colorsTokens.color;
18
- export const DEFAULT_BACKGROUND = gray[3].value;
19
- export const DEFAULT_HIGHLIGHT = gray[1].value;
16
+ export const DEFAULT_BACKGROUND = 'var(--unnnic-color-bg-muted)';
17
+ export const DEFAULT_HIGHLIGHT = 'var(--unnnic-color-bg-base-soft)';
20
18
  export const SkeletonStyle = {
21
19
  '--skeleton-bg': DEFAULT_BACKGROUND,
22
20
  '--skeleton-highlight': DEFAULT_HIGHLIGHT,
@@ -65,7 +65,7 @@ $scroll-size: 4px;
65
65
  }
66
66
 
67
67
  &::-webkit-scrollbar-thumb {
68
- background: $unnnic-color-gray-7;
68
+ background: $unnnic-color-border-emphasized;
69
69
  border-radius: $unnnic-border-radius-pill;
70
70
  }
71
71
 
@@ -17,8 +17,6 @@
17
17
  <script setup lang="ts">
18
18
  import { computed } from 'vue';
19
19
 
20
- import { color as colors } from '@/assets/tokens/colors.json';
21
-
22
20
  import UnnnicIcon from '../Icon.vue';
23
21
 
24
22
  import type { DefaultTagProps } from './types';
@@ -31,14 +29,14 @@ const props = withDefaults(defineProps<DefaultTagProps>(), {
31
29
  });
32
30
 
33
31
  const COLOR_MAPPING = [
34
- { keywords: ['green'], color: colors.green['2'].value },
35
- { keywords: ['blue'], color: colors.blue['2'].value },
36
- { keywords: ['purple'], color: colors.purple['3'].value },
37
- { keywords: ['red', 'pink'], color: colors.red['3'].value },
38
- { keywords: ['orange'], color: colors.orange['3'].value },
39
- { keywords: ['yellow'], color: colors.yellow['2'].value },
40
- { keywords: ['gray'], color: colors.gray['2'].value },
41
- { keywords: ['teal', 'weni'], color: colors.teal['2'].value },
32
+ { keywords: ['green'], color: 'var(--unnnic-color-bg-green-plain)' },
33
+ { keywords: ['blue'], color: 'var(--unnnic-color-bg-blue-plain)' },
34
+ { keywords: ['purple'], color: 'var(--unnnic-color-bg-purple-plain)' },
35
+ { keywords: ['red', 'pink'], color: 'var(--unnnic-color-bg-red-plain)' },
36
+ { keywords: ['orange'], color: 'var(--unnnic-color-bg-orange-plain)' },
37
+ { keywords: ['yellow'], color: 'var(--unnnic-color-bg-yellow-plain)' },
38
+ { keywords: ['gray'], color: 'var(--unnnic-color-bg-muted)' },
39
+ { keywords: ['teal', 'weni'], color: 'var(--unnnic-color-bg-teal-plain)' },
42
40
  ];
43
41
 
44
42
  const color = computed(() => {
@@ -48,7 +46,7 @@ const color = computed(() => {
48
46
  keywords.some((keyword) => scheme.includes(keyword)),
49
47
  );
50
48
 
51
- return match?.color ?? colors.gray['2'].value;
49
+ return match?.color ?? 'var(--unnnic-color-bg-muted)';
52
50
  });
53
51
  </script>
54
52
 
@@ -87,7 +87,7 @@
87
87
  <UnnnicIcon
88
88
  class="unnnic-template-preview__buttons__button__icon"
89
89
  :icon="getButtonIcon(button.type)"
90
- scheme="aux-blue-500"
90
+ scheme="fg-info"
91
91
  size="ant"
92
92
  />
93
93
  <p class="unnnic-template-preview__buttons__button__text">
@@ -99,10 +99,10 @@ let timeoutId: number | null = null;
99
99
 
100
100
  const typeConfig = computed(() => {
101
101
  const configMap = {
102
- informational: { icon: 'info', scheme: 'blue-500' },
103
- attention: { icon: 'error', scheme: 'yellow-500' },
104
- success: { icon: 'check_circle', scheme: 'green-500' },
105
- error: { icon: 'cancel', scheme: 'red-500' },
102
+ informational: { icon: 'info', scheme: 'fg-info' },
103
+ attention: { icon: 'error', scheme: 'fg-warning' },
104
+ success: { icon: 'check_circle', scheme: 'fg-success' },
105
+ error: { icon: 'cancel', scheme: 'fg-critical' },
106
106
  };
107
107
 
108
108
  return configMap[props.type || 'informational'] as {
@@ -53,7 +53,7 @@ describe('UnnnicToast', () => {
53
53
 
54
54
  expect(typeIcon().exists()).toBe(true);
55
55
  expect(typeIcon().props('icon')).toBe('info');
56
- expect(typeIcon().props('scheme')).toBe('blue-500');
56
+ expect(typeIcon().props('scheme')).toBe('fg-info');
57
57
  expect(typeIcon().props('size')).toBe('ant');
58
58
 
59
59
  expect(title().exists()).toBe(true);
@@ -120,10 +120,10 @@ describe('UnnnicToast', () => {
120
120
 
121
121
  test('displays correct scheme color for each type', async () => {
122
122
  const typeConfigs = [
123
- { type: 'informational', expectedScheme: 'blue-500' },
124
- { type: 'attention', expectedScheme: 'yellow-500' },
125
- { type: 'success', expectedScheme: 'green-500' },
126
- { type: 'error', expectedScheme: 'red-500' },
123
+ { type: 'informational', expectedScheme: 'fg-info' },
124
+ { type: 'attention', expectedScheme: 'fg-warning' },
125
+ { type: 'success', expectedScheme: 'fg-success' },
126
+ { type: 'error', expectedScheme: 'fg-critical' },
127
127
  ];
128
128
 
129
129
  for (const { type, expectedScheme } of typeConfigs) {
@@ -44,7 +44,7 @@
44
44
  size="sm"
45
45
  clickable
46
46
  filled
47
- scheme="white"
47
+ scheme="fg-inverted"
48
48
  @click="$emit('click:close')"
49
49
  />
50
50
  </TooltipContent>
@@ -129,7 +129,7 @@ export default {
129
129
  }
130
130
 
131
131
  &__overlay {
132
- fill: rgba($unnnic-color-gray-12, $unnnic-opacity-level-overlay);
132
+ fill: rgba($unnnic-color-bg-inverted, $unnnic-opacity-level-overlay);
133
133
  }
134
134
  }
135
135
  </style>
@@ -128,7 +128,7 @@ const ConditionalWrapper: Component = (_, { slots }) => {
128
128
  }
129
129
 
130
130
  &::-webkit-scrollbar-thumb {
131
- background: $unnnic-color-gray-7;
131
+ background: $unnnic-color-border-emphasized;
132
132
  border-radius: $unnnic-border-radius-pill;
133
133
  }
134
134
 
@@ -119,7 +119,7 @@ $popover-space: $unnnic-space-4;
119
119
  }
120
120
 
121
121
  &::-webkit-scrollbar-thumb {
122
- background: $unnnic-color-gray-7;
122
+ background: $unnnic-color-border-emphasized;
123
123
  border-radius: $unnnic-border-radius-pill;
124
124
  }
125
125
 
@@ -56,7 +56,7 @@ const props = withDefaults(defineProps<PopoverOptionProps>(), {
56
56
 
57
57
  const schemeColor = computed(() => {
58
58
  if (props.active) {
59
- return 'fg-inverted';
59
+ return 'fg-on-primary';
60
60
  }
61
61
  if (props.disabled) {
62
62
  return 'fg-muted';
@@ -113,7 +113,7 @@ const schemeColor = computed(() => {
113
113
  }
114
114
 
115
115
  &--active {
116
- color: $unnnic-color-fg-inverted;
116
+ color: $unnnic-color-fg-on-primary;
117
117
  }
118
118
  }
119
119
  }
@@ -58,8 +58,8 @@ const portalTarget = useTeleportTarget();
58
58
 
59
59
  .tooltip__content {
60
60
  display: flex;
61
- background-color: $unnnic-color-gray-900;
62
- color: $unnnic-color-white;
61
+ background-color: $unnnic-color-bg-inverted;
62
+ color: $unnnic-color-fg-inverted;
63
63
  border-radius: $unnnic-radius-1;
64
64
  padding: $unnnic-space-2;
65
65
  box-shadow: $unnnic-shadow-1;
@@ -76,8 +76,9 @@ const portalTarget = useTeleportTarget();
76
76
  .tooltip__arrow {
77
77
  width: 10px;
78
78
  height: 10px;
79
- background-color: $unnnic-color-gray-900;
80
- fill: $unnnic-color-gray-900;
79
+ background-color: $unnnic-color-bg-inverted;
80
+ fill: $unnnic-color-bg-inverted;
81
+ border-radius: calc($unnnic-radius-1 / 2);
81
82
  transform: rotate(45deg) translate(-50%, -50%);
82
83
  }
83
84
  </style>
@@ -0,0 +1,67 @@
1
+ import { computed, ref, watch, type Ref } from 'vue';
2
+ import { usePreferredColorScheme } from '@vueuse/core';
3
+
4
+ export type Theme = 'light' | 'dark';
5
+ export type ThemePreference = Theme | 'system';
6
+
7
+ const STORAGE_KEY = 'unnnic-theme';
8
+ const DARK_CLASS = 'dark';
9
+
10
+ function readStoredPreference(): ThemePreference {
11
+ try {
12
+ const stored = localStorage.getItem(STORAGE_KEY);
13
+ if (stored === 'light' || stored === 'dark' || stored === 'system') {
14
+ return stored;
15
+ }
16
+ } catch {
17
+ /* localStorage may be unavailable (SSR / iframe sandbox) */
18
+ }
19
+ return 'system';
20
+ }
21
+
22
+ function persistPreference(preference: ThemePreference): void {
23
+ try {
24
+ localStorage.setItem(STORAGE_KEY, preference);
25
+ } catch {
26
+ /* silent fail */
27
+ }
28
+ }
29
+
30
+ function applyTheme(theme: Theme): void {
31
+ document.documentElement.classList.toggle(DARK_CLASS, theme === 'dark');
32
+ }
33
+
34
+ const preference: Ref<ThemePreference> = ref(readStoredPreference());
35
+
36
+ export function useTheme() {
37
+ const osScheme = usePreferredColorScheme();
38
+
39
+ const resolvedTheme = computed<Theme>(() => {
40
+ if (preference.value !== 'system') return preference.value;
41
+ return osScheme.value === 'dark' ? 'dark' : 'light';
42
+ });
43
+
44
+ watch(
45
+ resolvedTheme,
46
+ (theme) => {
47
+ applyTheme(theme);
48
+ },
49
+ { immediate: true },
50
+ );
51
+
52
+ function setTheme(value: ThemePreference): void {
53
+ preference.value = value;
54
+ persistPreference(value);
55
+ }
56
+
57
+ function toggleTheme(): void {
58
+ setTheme(resolvedTheme.value === 'dark' ? 'light' : 'dark');
59
+ }
60
+
61
+ return {
62
+ preference,
63
+ resolvedTheme,
64
+ setTheme,
65
+ toggleTheme,
66
+ };
67
+ }
package/src/index.ts CHANGED
@@ -25,3 +25,5 @@ const Unnnic: UnnnicPlugin = {
25
25
 
26
26
  export default Unnnic;
27
27
  export * from '@/components';
28
+ export { useTheme } from '@/composables/useTheme';
29
+ export type { Theme, ThemePreference } from '@/composables/useTheme';
@@ -220,3 +220,56 @@ export const MediaLastMessage = {
220
220
  `,
221
221
  }),
222
222
  };
223
+
224
+ const lastMessageMediaFromUser = Object.fromEntries(
225
+ Object.entries(lastMessageMedia).map(([mediaType, message]) => [
226
+ mediaType,
227
+ { ...message, isFromUser: true },
228
+ ]),
229
+ );
230
+
231
+ export const MediaLastMessageFromUser = {
232
+ args: {
233
+ ...defaultArgs,
234
+ },
235
+ render: (args) => ({
236
+ setup() {
237
+ return { args, lastMessageMediaFromUser };
238
+ },
239
+ components: { unnnicChatsContact },
240
+ template: `
241
+ <div style="display: grid;">
242
+ <unnnic-chats-contact v-for="mediaType in ['geo', 'audio', 'image', 'video', 'document']" v-bind="args" :lastMessage="lastMessageMediaFromUser[mediaType]"/>
243
+ </div>
244
+ `,
245
+ }),
246
+ };
247
+
248
+ export const NewMessageIndicator = {
249
+ args: {
250
+ ...defaultArgs,
251
+ title: 'Aline',
252
+ projectName: 'Sector name',
253
+ lastMessage: {
254
+ text: 'I’d like to know if powdered dishwasher detergent is available',
255
+ },
256
+ lastInteractionTime: new Date().toISOString(),
257
+ unreadMessages: 1,
258
+ newMessageIndicator: true,
259
+ },
260
+ };
261
+
262
+ export const NewMessageIndicatorWithTooltip = {
263
+ args: {
264
+ ...defaultArgs,
265
+ title: 'Aline',
266
+ projectName: 'Sector name',
267
+ lastMessage: {
268
+ text: 'I’d like to know if powdered dishwasher detergent is available',
269
+ },
270
+ lastInteractionTime: new Date().toISOString(),
271
+ unreadMessages: 1,
272
+ newMessageIndicator: true,
273
+ newMessageIndicatorTooltip: 'New chats received',
274
+ },
275
+ };
@@ -2,7 +2,12 @@
2
2
  * Colors stories. use tags: ['!dev']
3
3
  * so they only appear in Colors.mdx via <Story of={...} />, not in the sidebar.
4
4
  */
5
- import colors from '@/assets/tokens/colors.json';
5
+ import primitives from '@/assets/tokens/colors-primitives.json';
6
+ import semantic from '@/assets/tokens/colors-semantic.json';
7
+
8
+ const colors = {
9
+ color: { ...primitives.color, ...semantic.color },
10
+ };
6
11
 
7
12
  function resolveReference(ref, root) {
8
13
  const path = ref.replace(/^\{|\}$/g, '').split('.');