@aleph-alpha/chat-kit 1.14.0 → 1.15.1

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 (40) hide show
  1. package/dist/{CkInput.vue_vue_type_script_setup_true_lang-BVZ7FG88.js → CkInput.vue_vue_type_script_setup_true_lang-DedLzDoO.js} +157 -82
  2. package/dist/components/base/CkAgentMessage/CkAgentMessage.vue.d.ts.map +1 -1
  3. package/dist/components/base/CkHistoryItem/CkHistoryItem.stories.d.ts.map +1 -1
  4. package/dist/components/base/CkHistoryItem/CkHistoryItem.vue.d.ts.map +1 -1
  5. package/dist/components/base/CkHistoryWrapper/CkHistoryWrapper.stories.d.ts.map +1 -1
  6. package/dist/components/composed/CkHistory/CkHistory.stories.d.ts +15 -0
  7. package/dist/components/composed/CkHistory/CkHistory.stories.d.ts.map +1 -1
  8. package/dist/components/composed/CkHistory/CkHistory.vue.d.ts +6 -0
  9. package/dist/components/composed/CkHistory/CkHistory.vue.d.ts.map +1 -1
  10. package/dist/components/composed/CkHistory/types.d.ts +33 -0
  11. package/dist/components/composed/CkHistory/types.d.ts.map +1 -1
  12. package/dist/components/index.js +1 -1
  13. package/dist/handlers/index.js +1 -1
  14. package/dist/handlers/useChatKitLabels.d.ts +5 -0
  15. package/dist/handlers/useChatKitLabels.d.ts.map +1 -1
  16. package/dist/helpers/formatRelativeTime.d.ts +10 -0
  17. package/dist/helpers/formatRelativeTime.d.ts.map +1 -0
  18. package/dist/helpers/index.d.ts +1 -0
  19. package/dist/helpers/index.d.ts.map +1 -1
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +3 -2
  23. package/dist/{useChatKitLabels-BxNkTzr2.js → useChatKitLabels-BC6ItJc6.js} +2 -1
  24. package/package.json +1 -1
  25. package/src/components/base/CkAgentMessage/CkAgentMessage.spec.ts +6 -12
  26. package/src/components/base/CkAgentMessage/CkAgentMessage.vue +24 -15
  27. package/src/components/base/CkHistoryItem/CkHistoryItem.spec.ts +0 -2
  28. package/src/components/base/CkHistoryItem/CkHistoryItem.stories.ts +0 -6
  29. package/src/components/base/CkHistoryItem/CkHistoryItem.vue +2 -3
  30. package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.stories.ts +23 -23
  31. package/src/components/composed/CkConversation/CkConversation.spec.ts +8 -35
  32. package/src/components/composed/CkHistory/CkHistory.spec.ts +107 -0
  33. package/src/components/composed/CkHistory/CkHistory.stories.ts +91 -3
  34. package/src/components/composed/CkHistory/CkHistory.vue +94 -44
  35. package/src/components/composed/CkHistory/types.ts +33 -0
  36. package/src/handlers/useChatKitLabels.spec.ts +1 -0
  37. package/src/handlers/useChatKitLabels.ts +6 -0
  38. package/src/helpers/formatRelativeTime.ts +49 -0
  39. package/src/helpers/index.ts +1 -0
  40. package/src/index.ts +3 -0
@@ -3,23 +3,32 @@ defineOptions({ name: 'CkAgentMessage' });
3
3
  </script>
4
4
 
5
5
  <template>
6
- <div class="flex w-full items-start gap-2">
7
- <div
8
- v-if="$slots.icon"
9
- class="flex flex-shrink-0 items-center justify-center"
10
- >
11
- <slot name="icon" />
12
- </div>
13
- <div class="text-core-content-primary flex min-w-0 flex-1 flex-col gap-2">
14
- <span v-if="$slots.header" class="text-sm font-bold">
6
+ <div
7
+ class="border-border-surface-default flex w-full flex-col gap-6 border-b pb-6"
8
+ >
9
+ <div v-if="$slots.icon || $slots.header" class="flex items-center gap-3">
10
+ <div
11
+ v-if="$slots.icon"
12
+ data-testid="ck-agent-message-icon"
13
+ class="flex flex-shrink-0 items-center justify-center"
14
+ >
15
+ <slot name="icon" />
16
+ </div>
17
+ <span
18
+ v-if="$slots.header"
19
+ data-testid="ck-agent-message-header"
20
+ class="body-14-regular text-content-on-surface-muted min-w-0 flex-1 [word-break:break-word]"
21
+ >
15
22
  <slot name="header" />
16
23
  </span>
17
- <div>
18
- <slot />
19
- </div>
20
- <div v-if="$slots.footer" class="flex items-center">
21
- <slot name="footer" />
22
- </div>
24
+ </div>
25
+
26
+ <div class="text-content-on-surface-primary text-base leading-7">
27
+ <slot />
28
+ </div>
29
+
30
+ <div v-if="$slots.footer" class="flex items-center gap-4">
31
+ <slot name="footer" />
23
32
  </div>
24
33
  </div>
25
34
  </template>
@@ -9,13 +9,11 @@ describe('CkHistoryItem', () => {
9
9
  props: {
10
10
  title: 'Thread A',
11
11
  metaStart: 'Mon',
12
- metaEnd: 'Berlin',
13
12
  },
14
13
  });
15
14
 
16
15
  expect(screen.getByText('Thread A')).toBeInTheDocument();
17
16
  expect(screen.getByText('Mon')).toBeInTheDocument();
18
- expect(screen.getByText('Berlin')).toBeInTheDocument();
19
17
  });
20
18
 
21
19
  it('emits action-click when the action button is clicked', async () => {
@@ -18,7 +18,6 @@ const meta: Meta<typeof CkHistoryItem> = {
18
18
  args: {
19
19
  title: 'Project Proposal Overview',
20
20
  metaStart: 'Today',
21
- metaEnd: 'Innsbruck',
22
21
  actionTooltip: 'More actions',
23
22
  },
24
23
  argTypes: {
@@ -58,7 +57,6 @@ function handleActionClick() {
58
57
  <CkHistoryItem
59
58
  title="Project Proposal Overview"
60
59
  meta-start="Today"
61
- meta-end="Innsbruck"
62
60
  action-tooltip="More actions"
63
61
  @action-click="handleActionClick"
64
62
  />
@@ -72,7 +70,6 @@ import { CkHistoryItem } from '@aleph-alpha/chat-kit';
72
70
  <CkHistoryItem
73
71
  title="Project Proposal Overview"
74
72
  meta-start="Today"
75
- meta-end="Innsbruck"
76
73
  action-tooltip="More actions"
77
74
  is-active
78
75
  />
@@ -86,7 +83,6 @@ import { CkHistoryItem } from '@aleph-alpha/chat-kit';
86
83
  <CkHistoryItem
87
84
  title="Project Proposal Overview"
88
85
  meta-start="Today"
89
- meta-end="Innsbruck"
90
86
  action-tooltip="More actions"
91
87
  :show-action-on-hover="false"
92
88
  />
@@ -108,7 +104,6 @@ import { CkHistoryItem } from '@aleph-alpha/chat-kit';
108
104
  <CkHistoryItem
109
105
  title="Project Proposal Overview"
110
106
  meta-start="Today"
111
- meta-end="Innsbruck"
112
107
  action-popover
113
108
  action-aria-label="Chat options"
114
109
  >
@@ -182,7 +177,6 @@ export const AlwaysShowAction: Story = {
182
177
  export const NoMeta: Story = {
183
178
  args: {
184
179
  metaStart: '',
185
- metaEnd: '',
186
180
  },
187
181
  render: renderStory,
188
182
  parameters: { docs: { source: { code: noMetaTemplateSource } } },
@@ -122,11 +122,10 @@ const actionColumnClass = computed(() => [
122
122
  * underneath the icon.
123
123
  */
124
124
  const actionButtonClass = computed(() => [
125
- 'relative z-10 inline-flex size-8 shrink-0 items-center justify-center rounded-sm border border-transparent text-content-on-surface-primary outline-hidden transition-colors',
125
+ 'relative z-10 inline-flex size-8 shrink-0 items-center justify-center rounded-sm border-0 text-content-on-surface-primary outline-hidden transition-colors',
126
126
  '[&_svg]:size-4 [&_svg]:shrink-0',
127
127
  'focus-visible:ring-0 focus-visible:outline-none',
128
- pointerOnAction.value &&
129
- 'bg-background-surface-muted-subtle border-border-button-outlined',
128
+ pointerOnAction.value && 'bg-background-surface-muted-subtle',
130
129
  ]);
131
130
 
132
131
  // Popover panel surface is `background/surface/secondary` to match the
@@ -33,10 +33,14 @@ import { CkHistoryWrapper, CkHistoryItem } from '@aleph-alpha/chat-kit';
33
33
 
34
34
  <template>
35
35
  <CkHistoryWrapper>
36
- <CkHistoryItem title="Project Proposal Overview" meta-start="Today" meta-end="Innsbruck" />
37
- <CkHistoryItem title="Project Proposal Overview" meta-start="Today" meta-end="Innsbruck" is-active />
38
- <CkHistoryItem title="Project Proposal Overview" meta-start="Today" meta-end="Innsbruck" />
39
- <CkHistoryItem title="Project Proposal Overview" meta-start="Today" meta-end="Innsbruck" />
36
+ <CkHistoryItem title="Project Proposal Overview" meta-start="Today" is-active />
37
+ <CkHistoryItem title="Customer onboarding flow" meta-start="Yesterday" />
38
+ <CkHistoryItem title="Refactor notes" meta-start="2 days ago" />
39
+ <CkHistoryItem title="Quarterly review prep" meta-start="3 days ago" />
40
+ <CkHistoryItem title="Sprint retro notes" meta-start="4 days ago" />
41
+ <CkHistoryItem title="Travel plans 2026" meta-start="1 week ago" />
42
+ <CkHistoryItem title="API design review" meta-start="2 weeks ago" />
43
+ <CkHistoryItem title="Bug triage session" meta-start="3 weeks ago" />
40
44
  </CkHistoryWrapper>
41
45
  </template>`;
42
46
 
@@ -48,7 +52,10 @@ import { CkHistoryWrapper, CkHistoryItem } from '@aleph-alpha/chat-kit';
48
52
  <CkHistoryWrapper :with-gap="false">
49
53
  <CkHistoryItem title="Thread A" meta-start="Today" />
50
54
  <CkHistoryItem title="Thread B" meta-start="Yesterday" />
51
- <CkHistoryItem title="Thread C" meta-start="Last week" />
55
+ <CkHistoryItem title="Thread C" meta-start="2 days ago" />
56
+ <CkHistoryItem title="Thread D" meta-start="3 days ago" />
57
+ <CkHistoryItem title="Thread E" meta-start="4 days ago" />
58
+ <CkHistoryItem title="Thread F" meta-start="1 week ago" />
52
59
  </CkHistoryWrapper>
53
60
  </template>`;
54
61
 
@@ -65,24 +72,15 @@ export const Default: Story = {
65
72
  {
66
73
  title: 'Project Proposal Overview',
67
74
  metaStart: 'Today',
68
- metaEnd: 'Innsbruck',
69
- },
70
- {
71
- title: 'Project Proposal Overview',
72
- metaStart: 'Today',
73
- metaEnd: 'Innsbruck',
74
75
  isActive: true,
75
76
  },
76
- {
77
- title: 'Project Proposal Overview',
78
- metaStart: 'Today',
79
- metaEnd: 'Innsbruck',
80
- },
81
- {
82
- title: 'Project Proposal Overview',
83
- metaStart: 'Today',
84
- metaEnd: 'Innsbruck',
85
- },
77
+ { title: 'Customer onboarding flow', metaStart: 'Yesterday' },
78
+ { title: 'Refactor notes', metaStart: '2 days ago' },
79
+ { title: 'Quarterly review prep', metaStart: '3 days ago' },
80
+ { title: 'Sprint retro notes', metaStart: '4 days ago' },
81
+ { title: 'Travel plans 2026', metaStart: '1 week ago' },
82
+ { title: 'API design review', metaStart: '2 weeks ago' },
83
+ { title: 'Bug triage session', metaStart: '3 weeks ago' },
86
84
  ];
87
85
 
88
86
  function handleActionClick() {
@@ -99,7 +97,6 @@ export const Default: Story = {
99
97
  :key="index"
100
98
  :title="item.title"
101
99
  :meta-start="item.metaStart"
102
- :meta-end="item.metaEnd"
103
100
  :is-active="item.isActive"
104
101
  action-tooltip="More actions"
105
102
  @action-click="handleActionClick"
@@ -128,7 +125,10 @@ export const WithoutGap: Story = {
128
125
  const items = [
129
126
  { title: 'Thread A', metaStart: 'Today' },
130
127
  { title: 'Thread B', metaStart: 'Yesterday' },
131
- { title: 'Thread C', metaStart: 'Last week' },
128
+ { title: 'Thread C', metaStart: '2 days ago' },
129
+ { title: 'Thread D', metaStart: '3 days ago' },
130
+ { title: 'Thread E', metaStart: '4 days ago' },
131
+ { title: 'Thread F', metaStart: '1 week ago' },
132
132
  ];
133
133
  return { args, items };
134
134
  },
@@ -132,56 +132,36 @@ describe('CkConversation', () => {
132
132
  });
133
133
 
134
134
  it('hides the icon area when no agentAvatarImage, no agentAvatarFallback, and no agent-message-icon slot', () => {
135
- const { container } = render(CkConversation, {
135
+ render(CkConversation, {
136
136
  props: { messages: [agentMessage()] },
137
137
  });
138
138
 
139
- const agentMessageEl = container.querySelector(
140
- '.flex.w-full.items-start.gap-2',
141
- );
142
- expect(agentMessageEl).toBeInTheDocument();
143
139
  expect(
144
- agentMessageEl?.querySelector(
145
- '.flex.flex-shrink-0.items-center.justify-center',
146
- ),
140
+ screen.queryByTestId('ck-agent-message-icon'),
147
141
  ).not.toBeInTheDocument();
148
142
  });
149
143
 
150
144
  it('shows the icon area when agentAvatarFallback is provided', () => {
151
- const { container } = render(CkConversation, {
145
+ render(CkConversation, {
152
146
  props: { messages: [agentMessage()], agentAvatarFallback: 'AI' },
153
147
  });
154
148
 
155
- const agentMessageEl = container.querySelector(
156
- '.flex.w-full.items-start.gap-2',
157
- );
158
- expect(
159
- agentMessageEl?.querySelector(
160
- '.flex.flex-shrink-0.items-center.justify-center',
161
- ),
162
- ).toBeInTheDocument();
149
+ expect(screen.getByTestId('ck-agent-message-icon')).toBeInTheDocument();
163
150
  });
164
151
 
165
152
  it('shows the icon area when agentAvatarImage URL is provided', () => {
166
- const { container } = render(CkConversation, {
153
+ render(CkConversation, {
167
154
  props: {
168
155
  messages: [agentMessage()],
169
156
  agentAvatarImage: 'https://example.com/icon.png',
170
157
  },
171
158
  });
172
159
 
173
- const agentMessageEl = container.querySelector(
174
- '.flex.w-full.items-start.gap-2',
175
- );
176
- expect(
177
- agentMessageEl?.querySelector(
178
- '.flex.flex-shrink-0.items-center.justify-center',
179
- ),
180
- ).toBeInTheDocument();
160
+ expect(screen.getByTestId('ck-agent-message-icon')).toBeInTheDocument();
181
161
  });
182
162
 
183
163
  it('shows the icon area when agent-message-icon slot is provided', () => {
184
- const { container } = render(CkConversation, {
164
+ render(CkConversation, {
185
165
  props: { messages: [agentMessage()] },
186
166
  slots: {
187
167
  'agent-message-icon': '<span data-testid="custom-icon">🤖</span>',
@@ -189,14 +169,7 @@ describe('CkConversation', () => {
189
169
  });
190
170
 
191
171
  expect(screen.getByTestId('custom-icon')).toBeInTheDocument();
192
- const agentMessageEl = container.querySelector(
193
- '.flex.w-full.items-start.gap-2',
194
- );
195
- expect(
196
- agentMessageEl?.querySelector(
197
- '.flex.flex-shrink-0.items-center.justify-center',
198
- ),
199
- ).toBeInTheDocument();
172
+ expect(screen.getByTestId('ck-agent-message-icon')).toBeInTheDocument();
200
173
  });
201
174
  });
202
175
 
@@ -170,12 +170,119 @@ describe('CkHistory', () => {
170
170
  expect(emitted().select).toBeUndefined();
171
171
  });
172
172
 
173
+ it('renders a relative time label under each row by default', () => {
174
+ const conversations = [
175
+ makeConversation({ id: 'c-1', updatedAt: NOW - 60 * 60 * 24 }),
176
+ makeConversation({ id: 'c-2', updatedAt: NOW - 60 * 60 * 24 * 3 }),
177
+ ];
178
+
179
+ render(CkHistory, {
180
+ props: {
181
+ conversations,
182
+ hrefResolver,
183
+ timeFormatter: (s: number) => {
184
+ const days = Math.round((NOW - s) / (60 * 60 * 24));
185
+ return days === 1 ? 'Yesterday' : `${days} days ago`;
186
+ },
187
+ },
188
+ });
189
+
190
+ expect(screen.getByText('Yesterday')).toBeInTheDocument();
191
+ expect(screen.getByText('3 days ago')).toBeInTheDocument();
192
+ });
193
+
194
+ it('hides the time label when timeFormatter returns an empty string', () => {
195
+ const conversations = [makeConversation({ id: 'c-1', updatedAt: NOW })];
196
+
197
+ const { container } = render(CkHistory, {
198
+ props: {
199
+ conversations,
200
+ hrefResolver,
201
+ timeFormatter: () => '',
202
+ },
203
+ });
204
+
205
+ const metaElements = container.querySelectorAll('.label-12-medium');
206
+ expect(metaElements).toHaveLength(0);
207
+ });
208
+
209
+ describe('collapsed limit / Show all', () => {
210
+ const manyConversations = Array.from({ length: 8 }, (_, i) =>
211
+ makeConversation({ id: `c-${i + 1}`, title: `Thread ${i + 1}` }),
212
+ );
213
+
214
+ it('shows only collapsedLimit rows and a "Show all" button when there are more conversations', () => {
215
+ render(CkHistory, {
216
+ props: {
217
+ conversations: manyConversations,
218
+ hrefResolver,
219
+ collapsedLimit: 5,
220
+ },
221
+ });
222
+
223
+ const links = screen.getAllByRole('link');
224
+ expect(links).toHaveLength(5);
225
+ expect(screen.getByTestId('ck-history-show-all')).toBeInTheDocument();
226
+ });
227
+
228
+ it('does not show the "Show all" button when conversations fit within the limit', () => {
229
+ render(CkHistory, {
230
+ props: {
231
+ conversations: manyConversations.slice(0, 3),
232
+ hrefResolver,
233
+ collapsedLimit: 5,
234
+ },
235
+ });
236
+
237
+ expect(
238
+ screen.queryByTestId('ck-history-show-all'),
239
+ ).not.toBeInTheDocument();
240
+ });
241
+
242
+ it('reveals all conversations when "Show all" is clicked', async () => {
243
+ const user = userEvent.setup();
244
+
245
+ render(CkHistory, {
246
+ props: {
247
+ conversations: manyConversations,
248
+ hrefResolver,
249
+ collapsedLimit: 5,
250
+ },
251
+ });
252
+
253
+ expect(screen.getAllByRole('link')).toHaveLength(5);
254
+
255
+ await user.click(screen.getByTestId('ck-history-show-all'));
256
+
257
+ expect(screen.getAllByRole('link')).toHaveLength(8);
258
+ expect(
259
+ screen.queryByTestId('ck-history-show-all'),
260
+ ).not.toBeInTheDocument();
261
+ });
262
+
263
+ it('shows all conversations when collapsedLimit is 0', () => {
264
+ render(CkHistory, {
265
+ props: {
266
+ conversations: manyConversations,
267
+ hrefResolver,
268
+ collapsedLimit: 0,
269
+ },
270
+ });
271
+
272
+ expect(screen.getAllByRole('link')).toHaveLength(8);
273
+ expect(
274
+ screen.queryByTestId('ck-history-show-all'),
275
+ ).not.toBeInTheDocument();
276
+ });
277
+ });
278
+
173
279
  describe('#item slot', () => {
174
280
  interface ItemSlotProps {
175
281
  conversation: Conversation;
176
282
  href: string;
177
283
  isActive: boolean;
178
284
  title: string;
285
+ time: string | undefined;
179
286
  select: (event: MouseEvent) => void;
180
287
  edit: () => void;
181
288
  delete: () => void;
@@ -35,14 +35,38 @@ const sampleConversations: Conversation[] = [
35
35
  id: 'c-5',
36
36
  title: 'Quarterly review prep',
37
37
  createdAt: NOW - ONE_DAY * 30,
38
- updatedAt: NOW - ONE_DAY * 5,
38
+ updatedAt: NOW - ONE_DAY * 3,
39
39
  },
40
40
  {
41
41
  id: 'c-6',
42
42
  title: 'Travel plans 2026',
43
43
  createdAt: NOW - ONE_DAY * 60,
44
+ updatedAt: NOW - ONE_DAY * 4,
45
+ },
46
+ {
47
+ id: 'c-7',
48
+ title: 'Sprint retro notes',
49
+ createdAt: NOW - ONE_DAY * 20,
50
+ updatedAt: NOW - ONE_DAY * 5,
51
+ },
52
+ {
53
+ id: 'c-8',
54
+ title: 'API design review',
55
+ createdAt: NOW - ONE_DAY * 25,
56
+ updatedAt: NOW - ONE_DAY * 7,
57
+ },
58
+ {
59
+ id: 'c-9',
60
+ title: 'Bug triage session',
61
+ createdAt: NOW - ONE_DAY * 40,
44
62
  updatedAt: NOW - ONE_DAY * 14,
45
63
  },
64
+ {
65
+ id: 'c-10',
66
+ title: 'Team standup summary',
67
+ createdAt: NOW - ONE_DAY * 50,
68
+ updatedAt: NOW - ONE_DAY * 21,
69
+ },
46
70
  ];
47
71
 
48
72
  const defaultHrefResolver = (id: string) => `/chat/${id}`;
@@ -66,6 +90,8 @@ const meta: Meta<typeof CkHistory> = {
66
90
  activeId: { control: 'text' },
67
91
  showAction: { control: 'boolean' },
68
92
  showActionOnHover: { control: 'boolean' },
93
+ collapsedLimit: { control: 'number' },
94
+ maxHeight: { control: 'text' },
69
95
  },
70
96
  };
71
97
 
@@ -184,7 +210,7 @@ function onDelete(c: Conversation) { /* confirm + delete */ }
184
210
  @delete="onDelete"
185
211
  >
186
212
  <template
187
- #item="{ conversation, isActive, title, edit, delete: del }"
213
+ #item="{ conversation, isActive, title, time, edit, delete: del }"
188
214
  >
189
215
  <RouterLink
190
216
  :to="{ name: 'chat', params: { id: conversation.id } }"
@@ -196,6 +222,7 @@ function onDelete(c: Conversation) { /* confirm + delete */ }
196
222
  :href="href"
197
223
  :is-active="isActive"
198
224
  :title="title"
225
+ :meta-start="time"
199
226
  action-popover
200
227
  @click="navigate"
201
228
  >
@@ -209,6 +236,22 @@ function onDelete(c: Conversation) { /* confirm + delete */ }
209
236
  </CkHistory>
210
237
  </template>`;
211
238
 
239
+ const withoutTimeTemplateSource = `<script setup lang="ts">
240
+ import { CkHistory, type Conversation } from '@aleph-alpha/chat-kit';
241
+
242
+ const conversations: Conversation[] = [/* ... */];
243
+ const hrefResolver = (id: string) => \`/chat/\${id}\`;
244
+ </script>
245
+
246
+ <template>
247
+ <CkHistory
248
+ :conversations="conversations"
249
+ :href-resolver="hrefResolver"
250
+ active-id="c-2"
251
+ :time-formatter="undefined"
252
+ />
253
+ </template>`;
254
+
212
255
  const renderStory = (args: Story['args']) => ({
213
256
  components: { CkHistory },
214
257
  setup() {
@@ -329,13 +372,14 @@ export const WithCustomItem: Story = {
329
372
  @select="onSelect"
330
373
  >
331
374
  <template
332
- #item="{ href, isActive, title, select, edit, delete: del }"
375
+ #item="{ href, isActive, title, time, select, edit, delete: del }"
333
376
  >
334
377
  <CkHistoryItem
335
378
  as="a"
336
379
  :href="href"
337
380
  :is-active="isActive"
338
381
  :title="title"
382
+ :meta-start="time"
339
383
  action-popover
340
384
  action-aria-label="Conversation options"
341
385
  @click="select($event)"
@@ -371,3 +415,47 @@ export const WithCustomItem: Story = {
371
415
  docs: { source: { code: withCustomItemTemplateSource } },
372
416
  },
373
417
  };
418
+
419
+ /**
420
+ * Hides the relative time label by setting `time-formatter` to `undefined`.
421
+ * Rows display only the title (the pre-v1.14 default).
422
+ */
423
+ export const WithoutTime: Story = {
424
+ args: {
425
+ conversations: sampleConversations,
426
+ hrefResolver: defaultHrefResolver,
427
+ activeId: 'c-2',
428
+ timeFormatter: undefined,
429
+ },
430
+ render: renderStory,
431
+ parameters: { docs: { source: { code: withoutTimeTemplateSource } } },
432
+ };
433
+
434
+ /**
435
+ * With 10 conversations and a `collapsedLimit` of 5, only the first 5 rows
436
+ * are shown initially. A "Show all" button at the bottom reveals the rest
437
+ * and makes the list scrollable (capped at `maxHeight`).
438
+ */
439
+ export const CollapsedWithShowAll: Story = {
440
+ args: {
441
+ conversations: sampleConversations,
442
+ hrefResolver: defaultHrefResolver,
443
+ activeId: 'c-2',
444
+ collapsedLimit: 5,
445
+ maxHeight: '24rem',
446
+ },
447
+ render: renderStory,
448
+ };
449
+
450
+ /**
451
+ * All conversations visible without collapse — set `collapsedLimit` to `0`.
452
+ */
453
+ export const NoCollapse: Story = {
454
+ args: {
455
+ conversations: sampleConversations,
456
+ hrefResolver: defaultHrefResolver,
457
+ activeId: 'c-2',
458
+ collapsedLimit: 0,
459
+ },
460
+ render: renderStory,
461
+ };