@gitlab/duo-ui 8.3.0 → 8.5.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.
@@ -0,0 +1,27 @@
1
+ function getOrdinalSuffix(day) {
2
+ if (day > 3 && day < 21) return 'th';
3
+ switch (day % 10) {
4
+ case 1:
5
+ return 'st';
6
+ case 2:
7
+ return 'nd';
8
+ case 3:
9
+ return 'rd';
10
+ default:
11
+ return 'th';
12
+ }
13
+ }
14
+ function formatDate(dateStr) {
15
+ const date = new Date(dateStr);
16
+ const day = date.getDate();
17
+ const suffix = getOrdinalSuffix(day);
18
+ const month = new Intl.DateTimeFormat('en-US', {
19
+ month: 'long'
20
+ }).format(date);
21
+ const year = new Intl.DateTimeFormat('en-US', {
22
+ year: 'numeric'
23
+ }).format(date);
24
+ return `${month} ${day}${suffix}, ${year}`;
25
+ }
26
+
27
+ export { formatDate, getOrdinalSuffix };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/duo-ui",
3
- "version": "8.3.0",
3
+ "version": "8.5.0",
4
4
  "description": "Duo UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -1,5 +1,11 @@
1
1
  <script>
2
- import { GlButton, GlAlert, GlExperimentBadge, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
2
+ import {
3
+ GlButton,
4
+ GlAlert,
5
+ GlExperimentBadge,
6
+ GlSafeHtmlDirective as SafeHtml,
7
+ GlTooltipDirective,
8
+ } from '@gitlab/ui';
3
9
  import { translate } from '../../../../utils/i18n';
4
10
  import { VIEW_TYPES } from './constants';
5
11
 
@@ -7,6 +13,8 @@ export const i18n = {
7
13
  CHAT_BACK_LABEL: translate('DuoChat.chatBackLabel', 'Back to History'),
8
14
  CHAT_CLOSE_LABEL: translate('DuoChat.closeChatHeaderLabel', 'Close Chat'),
9
15
  CHAT_NEW_LABEL: translate('DuoChat.chatNewLabel', 'New Chat'),
16
+ CHAT_NEW_TOOLTIP: translate('DuoChat.chatNewToolTip', 'New Chat'),
17
+ CHAT_HISTORY_TOOLTIP: translate('DuoChat.chatHistoryToolTip', 'Chat History'),
10
18
  CHAT_TITLE: translate('DuoChat.chatTitle', 'GitLab Duo Chat'),
11
19
  };
12
20
 
@@ -20,6 +28,7 @@ export default {
20
28
  },
21
29
  directives: {
22
30
  SafeHtml,
31
+ GlTooltip: GlTooltipDirective,
23
32
  },
24
33
  props: {
25
34
  title: {
@@ -71,22 +80,27 @@ export default {
71
80
  data-testid="chat-header"
72
81
  :class="{
73
82
  'gl-z-200': !shouldRenderResizable,
83
+ 'gl-z-1': shouldRenderResizable,
74
84
  }"
75
85
  class="duo-chat-drawer-header duo-chat-drawer-header-sticky gl-border-0 gl-bg-default !gl-p-0"
76
86
  >
77
87
  <div class="drawer-title gl-flex gl-items-center gl-justify-start gl-p-5">
78
88
  <gl-button
79
89
  v-if="isMultithreaded && currentView === VIEW_TYPES.CHAT"
90
+ v-gl-tooltip
91
+ :title="$options.i18n.CHAT_HISTORY_TOOLTIP"
80
92
  data-testid="chat-back-button"
81
93
  category="primary"
82
94
  size="medium"
83
- icon="go-back"
95
+ icon="history"
84
96
  class="gl-mr-3"
85
97
  :aria-label="$options.i18n.CHAT_BACK_LABEL"
86
98
  @click="$emit('go-back')"
87
99
  />
88
100
  <gl-button
89
101
  v-if="isMultithreaded && (activeThreadId || currentView === VIEW_TYPES.LIST)"
102
+ v-gl-tooltip
103
+ :title="$options.i18n.CHAT_NEW_TOOLTIP"
90
104
  data-testid="chat-new-button"
91
105
  category="primary"
92
106
  size="medium"
@@ -0,0 +1,122 @@
1
+ <script>
2
+ import { GlButton, GlIcon } from '@gitlab/ui';
3
+ import { translate } from '../../../../utils/i18n';
4
+ import { formatDate } from '../../../../utils/date';
5
+ import DuoChatThreadsEmpty from './duo_chat_threads_empty.vue';
6
+
7
+ const i18n = {
8
+ CHAT_HISTORY_INFO: translate(
9
+ 'DuoChat.chatHistoryInfo',
10
+ 'Chats with no activity in the last 30 days are deleted.'
11
+ ),
12
+ THREAD_DELETE_LABEL: translate('DuoChat.threadDeleteLabel', 'Delete this thread.'),
13
+ };
14
+
15
+ export default {
16
+ name: 'DuoChatHistory',
17
+
18
+ components: {
19
+ GlButton,
20
+ GlIcon,
21
+ DuoChatThreadsEmpty,
22
+ },
23
+
24
+ props: {
25
+ threads: {
26
+ type: Array,
27
+ required: true,
28
+ },
29
+ },
30
+
31
+ computed: {
32
+ formatDate() {
33
+ return formatDate;
34
+ },
35
+
36
+ groupedThreads() {
37
+ return this.threads.reduce((threadsGroupedByDate, thread) => {
38
+ const date = new Date(thread.createdAt);
39
+ const dateKey = date.toISOString().split('T')[0];
40
+
41
+ return {
42
+ ...threadsGroupedByDate,
43
+ [dateKey]: [...(threadsGroupedByDate[dateKey] || []), thread],
44
+ };
45
+ }, {});
46
+ },
47
+
48
+ hasThreads() {
49
+ return this.threads.length > 0;
50
+ },
51
+ },
52
+
53
+ methods: {
54
+ onNewChat() {
55
+ this.$emit('new-chat');
56
+ },
57
+
58
+ onSelectThread(thread) {
59
+ this.$emit('select-thread', thread);
60
+ },
61
+
62
+ onDeleteThread(threadId, event) {
63
+ event.stopPropagation();
64
+ this.$emit('delete-thread', threadId);
65
+ },
66
+
67
+ onClose() {
68
+ this.$emit('close');
69
+ },
70
+ },
71
+ i18n,
72
+ };
73
+ </script>
74
+
75
+ <template>
76
+ <div class="gl-h-full gl-p-5">
77
+ <div
78
+ data-testid="chat-threads-info-banner"
79
+ class="gl-bg-gray-50 gl-text-gray-500 gl-p-4 gl-mb-5 gl-rounded-base"
80
+ >
81
+ <p class="gl-m-0 gl-flex">
82
+ <gl-icon class="gl-mr-4" name="bulb" />{{ $options.i18n.CHAT_HISTORY_INFO }}
83
+ </p>
84
+ </div>
85
+
86
+ <template v-if="hasThreads">
87
+ <div v-for="(threadsForDate, date) in groupedThreads" :key="date">
88
+ <div data-testid="chat-threads-date-header" class="gl-font-bold gl-neutral-900 gl-mb-4">
89
+ {{ formatDate(date) }}
90
+ </div>
91
+
92
+ <div>
93
+ <div
94
+ v-for="thread in threadsForDate"
95
+ :key="thread.id"
96
+ class="gl-flex gl-align-center gl-mb-4"
97
+ >
98
+ <div
99
+ tabindex="0"
100
+ data-testid="chat-threads-thread-box"
101
+ class="thread-box hover:gl-bg-gray-50 focus:gl-bg-gray-50 gl-text-ellipsis gl-overflow-hidden gl-rounded-base gl-cursor-pointer gl-rounded-base gl-p-4 gl-w-full gl-whitespace-nowrap"
102
+ @click="onSelectThread(thread)"
103
+ >
104
+ {{ thread.title || 'Untitled Chat' }}
105
+ </div>
106
+
107
+ <gl-button
108
+ data-testid="chat-threads-delete-thread-button"
109
+ icon="remove"
110
+ category="tertiary"
111
+ class="gl-neutral-900 !gl-p-4"
112
+ size="small"
113
+ :aria-label="$options.i18n.THREAD_DELETE_LABEL"
114
+ @click="$emit('delete-thread', thread.id)"
115
+ />
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </template>
120
+ <duo-chat-threads-empty v-else />
121
+ </div>
122
+ </template>
@@ -0,0 +1,40 @@
1
+ <script>
2
+ import ScheduleIllustration from '@gitlab/svgs/dist/illustrations/schedule-md.svg';
3
+ import { translate } from '../../../../utils/i18n';
4
+
5
+ const i18n = {
6
+ EMPTY_HISTORY_TITLE: translate('DuoChat.emptyHistoryTitle', 'See your chat history'),
7
+ EMPTY_HISTORY_COPY: translate(
8
+ 'DuoChat.emptyHistoryCopy',
9
+ 'Your previous chats will appear here.'
10
+ ),
11
+ EMPTY_HISTORY_ALT: translate(
12
+ 'DuoChat.emptyHistoryAlt',
13
+ 'Clock icon with circular arrow, indicating chat history or time-based functionality'
14
+ ),
15
+ };
16
+
17
+ export default {
18
+ name: 'DuoChatHistoryEmpty',
19
+
20
+ i18n,
21
+ ScheduleIllustration,
22
+ };
23
+ </script>
24
+
25
+ <template>
26
+ <div class="gl-h-full gl-flex gl-flex-col gl-align-center gl-items-center gl-justify-center">
27
+ <img
28
+ :src="$options.ScheduleIllustration"
29
+ :alt="$options.i18n.EMPTY_HISTORY_ALT"
30
+ class="gl-mb-5"
31
+ data-testid="empty-state-illustration"
32
+ />
33
+ <h3 class="gl-font-bold gl-neutral-900 gl-mb-4" data-testid="empty-state-title">
34
+ {{ $options.i18n.EMPTY_HISTORY_TITLE }}
35
+ </h3>
36
+ <p class="gl-align-center" data-testid="empty-state-description">
37
+ {{ $options.i18n.EMPTY_HISTORY_COPY }}
38
+ </p>
39
+ </div>
40
+ </template>