@aleph-alpha/ui-library 1.18.0 → 1.20.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.
@@ -22,6 +22,9 @@
22
22
  import type { UiDataTableProps } from './types';
23
23
  import { DATA_TABLE_LABELS, DEFAULT_COLUMN_SIZE } from './constants';
24
24
 
25
+ const INTERACTIVE_SELECTOR =
26
+ 'button, a, input, textarea, select, [role="button"], [role="link"], [role="menuitem"], [role="checkbox"], [role="switch"], [contenteditable="true"]';
27
+
25
28
  defineOptions({
26
29
  name: 'UiDataTable',
27
30
  });
@@ -68,6 +71,14 @@
68
71
  },
69
72
  });
70
73
 
74
+ function handleRowClick(event: MouseEvent | KeyboardEvent, original: TData) {
75
+ if (!props.onRowClick) return;
76
+ const target = event.target as HTMLElement;
77
+ if (target.closest?.(INTERACTIVE_SELECTOR)) return;
78
+ event.preventDefault();
79
+ props.onRowClick(original);
80
+ }
81
+
71
82
  defineExpose({
72
83
  table,
73
84
  });
@@ -102,6 +113,7 @@
102
113
  v-for="row in table.getRowModel().rows"
103
114
  :key="row.id"
104
115
  :data-state="row.getIsSelected() ? 'selected' : undefined"
116
+ :class="props.onRowClick ? 'cursor-pointer' : undefined"
105
117
  >
106
118
  <UiTableCell
107
119
  v-for="cell in row.getVisibleCells()"
@@ -111,6 +123,9 @@
111
123
  ? { width: `${cell.column.getSize()}px` }
112
124
  : undefined
113
125
  "
126
+ @click="handleRowClick($event, row.original)"
127
+ @keydown.enter="handleRowClick($event, row.original)"
128
+ @keydown.space="handleRowClick($event, row.original)"
114
129
  >
115
130
  <FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
116
131
  </UiTableCell>
@@ -1,12 +1,6 @@
1
1
  <script setup lang="ts" generic="TData, TValue">
2
2
  import { UiButton } from '@/components/UiButton';
3
3
  import { UiIcon } from '@/components/UiIcon';
4
- import {
5
- UiDropdownMenu,
6
- UiDropdownMenuContent,
7
- UiDropdownMenuItem,
8
- UiDropdownMenuTrigger,
9
- } from '@/components/UiDropdownMenu';
10
4
  import { cn } from '@/lib/utils';
11
5
  import { computed } from 'vue';
12
6
  import { DATA_TABLE_LABELS } from './constants';
@@ -24,8 +18,6 @@
24
18
  const props = defineProps<UiDataTableColumnHeaderProps<TData, TValue>>();
25
19
 
26
20
  const labels = computed(() => ({
27
- sortAscending: props.labels?.sortAscending ?? DATA_TABLE_LABELS.sortAscending,
28
- sortDescending: props.labels?.sortDescending ?? DATA_TABLE_LABELS.sortDescending,
29
21
  sortedAscending: props.labels?.sortedAscending ?? DATA_TABLE_LABELS.sortedAscending,
30
22
  sortedDescending: props.labels?.sortedDescending ?? DATA_TABLE_LABELS.sortedDescending,
31
23
  clickToSort: props.labels?.clickToSort ?? DATA_TABLE_LABELS.clickToSort,
@@ -37,57 +29,39 @@
37
29
  if (sortState === 'desc') return `${props.title}, ${labels.value.sortedDescending}`;
38
30
  return `${props.title}, ${labels.value.clickToSort}`;
39
31
  });
32
+
33
+ function handleSort() {
34
+ const current = props.column.getIsSorted();
35
+ if (current === false) {
36
+ props.column.toggleSorting(false); // -> asc
37
+ } else if (current === 'asc') {
38
+ props.column.toggleSorting(true); // -> desc
39
+ } else {
40
+ props.column.clearSorting(); // -> unsorted
41
+ }
42
+ }
40
43
  </script>
41
44
 
42
45
  <template>
43
46
  <div v-if="column.getCanSort()" :class="cn('flex items-center space-x-2', $attrs.class ?? '')">
44
- <UiDropdownMenu>
45
- <UiDropdownMenuTrigger as-child>
46
- <UiButton
47
- variant="ghost"
48
- size="sm"
49
- class="h-8 data-[state=open]:bg-accent"
50
- :aria-label="sortLabel"
51
- >
52
- <span>{{ title }}</span>
53
- <UiIcon
54
- v-if="column.getIsSorted() === 'desc'"
55
- name="arrow-down"
56
- :size="16"
57
- class="ml-2"
58
- aria-hidden="true"
59
- />
60
- <UiIcon
61
- v-else-if="column.getIsSorted() === 'asc'"
62
- name="arrow-up"
63
- :size="16"
64
- class="ml-2"
65
- aria-hidden="true"
66
- />
67
- <UiIcon v-else name="chevrons-up-down" :size="16" class="ml-2" aria-hidden="true" />
68
- </UiButton>
69
- </UiDropdownMenuTrigger>
70
- <UiDropdownMenuContent align="start">
71
- <UiDropdownMenuItem @click="column.toggleSorting(false)">
72
- <UiIcon
73
- name="arrow-up"
74
- :size="14"
75
- class="mr-2 text-muted-foreground/70"
76
- aria-hidden="true"
77
- />
78
- {{ labels.sortAscending }}
79
- </UiDropdownMenuItem>
80
- <UiDropdownMenuItem @click="column.toggleSorting(true)">
81
- <UiIcon
82
- name="arrow-down"
83
- :size="14"
84
- class="mr-2 text-muted-foreground/70"
85
- aria-hidden="true"
86
- />
87
- {{ labels.sortDescending }}
88
- </UiDropdownMenuItem>
89
- </UiDropdownMenuContent>
90
- </UiDropdownMenu>
47
+ <UiButton variant="ghost" size="sm" class="h-8" :aria-label="sortLabel" @click="handleSort">
48
+ <span>{{ title }}</span>
49
+ <UiIcon
50
+ v-if="column.getIsSorted() === 'desc'"
51
+ name="arrow-down"
52
+ :size="16"
53
+ class="ml-2"
54
+ aria-hidden="true"
55
+ />
56
+ <UiIcon
57
+ v-else-if="column.getIsSorted() === 'asc'"
58
+ name="arrow-up"
59
+ :size="16"
60
+ class="ml-2"
61
+ aria-hidden="true"
62
+ />
63
+ <UiIcon v-else name="chevrons-up-down" :size="16" class="ml-2" aria-hidden="true" />
64
+ </UiButton>
91
65
  </div>
92
66
 
93
67
  <div v-else :class="$attrs.class">
@@ -1,6 +1,6 @@
1
- import { render, within } from '@testing-library/vue';
1
+ import { fireEvent, render, within } from '@testing-library/vue';
2
2
  import userEvent from '@testing-library/user-event';
3
- import { describe, expect, test } from 'vitest';
3
+ import { describe, expect, test, vi } from 'vitest';
4
4
  import { h, ref, nextTick } from 'vue';
5
5
  import type { ColumnDef } from '@tanstack/vue-table';
6
6
  import UiDataTable from '../UiDataTable.vue';
@@ -137,35 +137,117 @@ describe('UiDataTable', () => {
137
137
  });
138
138
 
139
139
  describe('Sorting', () => {
140
- test('sorts data when sortable column header is clicked', async () => {
140
+ test('sorts ascending on first click', async () => {
141
141
  const user = userEvent.setup();
142
142
  const { getByRole, getAllByRole } = render(UiDataTable, {
143
143
  props: { columns: sortableColumns, data: testData },
144
144
  });
145
145
 
146
- // Click the Name column header button to open dropdown
146
+ // Click the Name column header button to sort ascending
147
147
  await user.click(getByRole('button', { name: /Name/i }));
148
- // Click ascending option
149
- await user.click(getByRole('menuitem', { name: /Asc/i }));
150
148
 
151
149
  const cells = getAllByRole('cell');
152
150
  // First data row should be Alice (alphabetically first)
153
151
  expect(cells[0]).toHaveTextContent('Alice');
154
152
  });
155
153
 
156
- test('sorts descending when desc option is selected', async () => {
154
+ test('sorts descending on second click', async () => {
157
155
  const user = userEvent.setup();
158
156
  const { getByRole, getAllByRole } = render(UiDataTable, {
159
157
  props: { columns: sortableColumns, data: testData },
160
158
  });
161
159
 
160
+ // First click: ascending
161
+ await user.click(getByRole('button', { name: /Name/i }));
162
+ // Second click: descending
162
163
  await user.click(getByRole('button', { name: /Name/i }));
163
- await user.click(getByRole('menuitem', { name: /Desc/i }));
164
164
 
165
165
  const cells = getAllByRole('cell');
166
166
  // First data row should be Charlie (alphabetically last)
167
167
  expect(cells[0]).toHaveTextContent('Charlie');
168
168
  });
169
+
170
+ test('clears sorting on third click', async () => {
171
+ const user = userEvent.setup();
172
+ const { getByRole, getAllByRole } = render(UiDataTable, {
173
+ props: { columns: sortableColumns, data: testData },
174
+ });
175
+
176
+ const initialCells = getAllByRole('cell');
177
+ const initialFirst = initialCells[0].textContent;
178
+
179
+ const headerButton = getByRole('button', { name: /Name/i });
180
+ // First click: ascending
181
+ await user.click(headerButton);
182
+ // Second click: descending
183
+ await user.click(headerButton);
184
+ // Third click: clear sorting (back to original order)
185
+ await user.click(headerButton);
186
+
187
+ const finalCells = getAllByRole('cell');
188
+ expect(finalCells[0]).toHaveTextContent(initialFirst ?? '');
189
+ });
190
+ });
191
+
192
+ describe('Row Click', () => {
193
+ test('calls onRowClick with row data when a row is clicked', async () => {
194
+ const onRowClick = vi.fn();
195
+ const { getByText } = render(UiDataTable, {
196
+ props: { columns: basicColumns, data: testData, onRowClick },
197
+ });
198
+
199
+ // Click cell content - handler is on <td>, event bubbles from text
200
+ await fireEvent.click(getByText('Alice'));
201
+
202
+ expect(onRowClick).toHaveBeenCalledWith(testData[0]);
203
+ });
204
+
205
+ test('adds cursor-pointer class when onRowClick is provided', () => {
206
+ const { container } = render(UiDataTable, {
207
+ props: { columns: basicColumns, data: testData, onRowClick: () => {} },
208
+ });
209
+
210
+ const dataRows = container.querySelectorAll('tbody tr');
211
+ expect(dataRows[0]).toHaveClass('cursor-pointer');
212
+ });
213
+
214
+ test('does not add cursor-pointer when onRowClick is not provided', () => {
215
+ const { container } = render(UiDataTable, {
216
+ props: { columns: basicColumns, data: testData },
217
+ });
218
+
219
+ const dataRows = container.querySelectorAll('tbody tr');
220
+ expect(dataRows[0]).not.toHaveClass('cursor-pointer');
221
+ });
222
+
223
+ test('does not fire onRowClick when clicking interactive elements inside a row', async () => {
224
+ const user = userEvent.setup();
225
+ const onRowClick = vi.fn();
226
+ const { getByLabelText } = render({
227
+ components: { UiDataTable },
228
+ template: '<UiDataTable :columns="columns" :data="data" :on-row-click="onRowClick" />',
229
+ setup() {
230
+ return { columns: selectableColumns, data: testData, onRowClick };
231
+ },
232
+ });
233
+
234
+ await nextTick();
235
+
236
+ // Click the checkbox (interactive element) - should NOT trigger onRowClick
237
+ await user.click(getByLabelText('Select row Alice'));
238
+ expect(onRowClick).not.toHaveBeenCalled();
239
+ });
240
+
241
+ test('activates onRowClick via keyboard on cell', async () => {
242
+ const onRowClick = vi.fn();
243
+ const { getByText } = render(UiDataTable, {
244
+ props: { columns: basicColumns, data: testData, onRowClick },
245
+ });
246
+
247
+ await fireEvent.keyDown(getByText('Alice').closest('td')!, { key: 'Enter' });
248
+
249
+ expect(onRowClick).toHaveBeenCalledWith(testData[0]);
250
+ });
169
251
  });
170
252
 
171
253
  describe('Filtering', () => {
@@ -1,4 +1,4 @@
1
- import { render, waitFor } from '@testing-library/vue';
1
+ import { render } from '@testing-library/vue';
2
2
  import userEvent from '@testing-library/user-event';
3
3
  import { describe, expect, test, vi } from 'vitest';
4
4
  import UiDataTableColumnHeader from '../UiDataTableColumnHeader.vue';
@@ -10,6 +10,7 @@ function createMockColumn(options: { canSort?: boolean; isSorted?: false | 'asc'
10
10
  getCanSort: vi.fn(() => canSort),
11
11
  getIsSorted: vi.fn(() => isSorted),
12
12
  toggleSorting: vi.fn(),
13
+ clearSorting: vi.fn(),
13
14
  };
14
15
  }
15
16
 
@@ -34,13 +35,12 @@ describe('UiDataTableColumnHeader', () => {
34
35
  expect(queryByRole('button')).not.toBeInTheDocument();
35
36
  });
36
37
 
37
- test('renders as button with dropdown trigger when column is sortable', () => {
38
+ test('renders as button when column is sortable', () => {
38
39
  const column = createMockColumn({ canSort: true });
39
40
  const { getByRole } = render(UiDataTableColumnHeader, {
40
41
  props: { column, title: 'Name' },
41
42
  });
42
43
 
43
- // Button should be accessible by role and name
44
44
  expect(getByRole('button', { name: /Name/i })).toBeInTheDocument();
45
45
  });
46
46
 
@@ -55,36 +55,42 @@ describe('UiDataTableColumnHeader', () => {
55
55
  });
56
56
  });
57
57
 
58
- describe('Dropdown Menu', () => {
59
- test('triggers ascending sort when Asc option is clicked', async () => {
58
+ describe('Click-to-Sort', () => {
59
+ test('sorts ascending when unsorted column is clicked', async () => {
60
60
  const user = userEvent.setup();
61
- const column = createMockColumn();
61
+ const column = createMockColumn({ isSorted: false });
62
62
  const { getByRole } = render(UiDataTableColumnHeader, {
63
63
  props: { column, title: 'Name' },
64
64
  });
65
65
 
66
66
  await user.click(getByRole('button'));
67
- await waitFor(() => expect(getByRole('menu')).toBeInTheDocument());
68
-
69
- await user.click(getByRole('menuitem', { name: /Asc/i }));
70
67
 
71
68
  expect(column.toggleSorting).toHaveBeenCalledWith(false);
72
69
  });
73
70
 
74
- test('triggers descending sort when Desc option is clicked', async () => {
71
+ test('sorts descending when ascending column is clicked', async () => {
75
72
  const user = userEvent.setup();
76
- const column = createMockColumn();
73
+ const column = createMockColumn({ isSorted: 'asc' });
77
74
  const { getByRole } = render(UiDataTableColumnHeader, {
78
75
  props: { column, title: 'Name' },
79
76
  });
80
77
 
81
78
  await user.click(getByRole('button'));
82
- await waitFor(() => expect(getByRole('menu')).toBeInTheDocument());
83
-
84
- await user.click(getByRole('menuitem', { name: /Desc/i }));
85
79
 
86
80
  expect(column.toggleSorting).toHaveBeenCalledWith(true);
87
81
  });
82
+
83
+ test('clears sorting when descending column is clicked', async () => {
84
+ const user = userEvent.setup();
85
+ const column = createMockColumn({ isSorted: 'desc' });
86
+ const { getByRole } = render(UiDataTableColumnHeader, {
87
+ props: { column, title: 'Name' },
88
+ });
89
+
90
+ await user.click(getByRole('button'));
91
+
92
+ expect(column.clearSorting).toHaveBeenCalled();
93
+ });
88
94
  });
89
95
 
90
96
  describe('Accessibility', () => {
@@ -115,73 +121,30 @@ describe('UiDataTableColumnHeader', () => {
115
121
  });
116
122
 
117
123
  describe('i18n Support', () => {
118
- test('uses default labels when labels prop is not provided', async () => {
119
- const user = userEvent.setup();
120
- const column = createMockColumn();
121
- const { getByRole } = render(UiDataTableColumnHeader, {
122
- props: { column, title: 'Name' },
123
- });
124
-
125
- await user.click(getByRole('button'));
126
-
127
- await waitFor(() => {
128
- expect(getByRole('menuitem', { name: 'Asc' })).toBeInTheDocument();
129
- expect(getByRole('menuitem', { name: 'Desc' })).toBeInTheDocument();
130
- });
131
- });
132
-
133
- test('applies custom labels when provided', async () => {
134
- const user = userEvent.setup();
124
+ test('uses custom aria labels when provided', () => {
135
125
  const column = createMockColumn({ isSorted: 'asc' });
136
126
  const { getByRole } = render(UiDataTableColumnHeader, {
137
127
  props: {
138
128
  column,
139
129
  title: 'Name',
140
- labels: {
141
- sortAscending: 'Aufsteigend',
142
- sortDescending: 'Absteigend',
143
- sortedAscending: 'aufsteigend sortiert',
144
- },
130
+ labels: { sortedAscending: 'aufsteigend sortiert' },
145
131
  },
146
132
  });
147
133
 
148
- // Check custom aria-label
149
134
  expect(getByRole('button')).toHaveAttribute('aria-label', 'Name, aufsteigend sortiert');
150
-
151
- // Check custom menu labels
152
- await user.click(getByRole('button'));
153
-
154
- await waitFor(() => {
155
- expect(getByRole('menuitem', { name: 'Aufsteigend' })).toBeInTheDocument();
156
- expect(getByRole('menuitem', { name: 'Absteigend' })).toBeInTheDocument();
157
- });
158
135
  });
159
136
 
160
- test('merges partial custom labels with defaults', async () => {
161
- const user = userEvent.setup();
137
+ test('uses custom clickToSort label in aria-label', () => {
162
138
  const column = createMockColumn({ isSorted: false });
163
139
  const { getByRole } = render(UiDataTableColumnHeader, {
164
140
  props: {
165
141
  column,
166
- title: 'Status',
167
- labels: {
168
- sortAscending: 'Sort A-Z',
169
- // Other labels should fall back to defaults
170
- },
142
+ title: 'Email',
143
+ labels: { clickToSort: 'zum Sortieren klicken' },
171
144
  },
172
145
  });
173
146
 
174
- // clickToSort should use default
175
- expect(getByRole('button')).toHaveAttribute('aria-label', 'Status, click to sort');
176
-
177
- await user.click(getByRole('button'));
178
-
179
- await waitFor(() => {
180
- // Custom label
181
- expect(getByRole('menuitem', { name: 'Sort A-Z' })).toBeInTheDocument();
182
- // Default label (not overridden)
183
- expect(getByRole('menuitem', { name: 'Desc' })).toBeInTheDocument();
184
- });
147
+ expect(getByRole('button')).toHaveAttribute('aria-label', 'Email, zum Sortieren klicken');
185
148
  });
186
149
 
187
150
  test('uses custom sortedDescending label in aria-label', () => {
@@ -190,28 +153,11 @@ describe('UiDataTableColumnHeader', () => {
190
153
  props: {
191
154
  column,
192
155
  title: 'Amount',
193
- labels: {
194
- sortedDescending: 'absteigend sortiert',
195
- },
156
+ labels: { sortedDescending: 'absteigend sortiert' },
196
157
  },
197
158
  });
198
159
 
199
160
  expect(getByRole('button')).toHaveAttribute('aria-label', 'Amount, absteigend sortiert');
200
161
  });
201
-
202
- test('uses custom clickToSort label in aria-label', () => {
203
- const column = createMockColumn({ isSorted: false });
204
- const { getByRole } = render(UiDataTableColumnHeader, {
205
- props: {
206
- column,
207
- title: 'Email',
208
- labels: {
209
- clickToSort: 'zum Sortieren klicken',
210
- },
211
- },
212
- });
213
-
214
- expect(getByRole('button')).toHaveAttribute('aria-label', 'Email, zum Sortieren klicken');
215
- });
216
162
  });
217
163
  });
@@ -36,6 +36,11 @@ export type UiDataTableProps<TData, TValue> = {
36
36
  * @example "400px"
37
37
  */
38
38
  tableMinHeight?: string;
39
+ /**
40
+ * Callback when a data row is clicked. Receives the row's original data item.
41
+ * @example (item) => router.push(`/details/${item.id}`)
42
+ */
43
+ onRowClick?: (item: TData) => void;
39
44
  };
40
45
 
41
46
  /**
@@ -245,7 +245,7 @@ describe('UiDatePicker', () => {
245
245
 
246
246
  describe('lazy loading optimization', () => {
247
247
  test('lazily loads UiCalendar in single mode when popover opens', async () => {
248
- const { getByRole, queryByRole, findByRole } = render(UiDatePicker, {
248
+ const { getByRole, queryByRole, findByRole, findAllByRole } = render(UiDatePicker, {
249
249
  props: { mode: 'single', modelValue: new CalendarDate(2025, 1, 15) },
250
250
  });
251
251
 
@@ -255,13 +255,14 @@ describe('UiDatePicker', () => {
255
255
  // Open the popover
256
256
  await userEvent.click(getByRole('button'));
257
257
 
258
- // Calendar should be rendered after async load (findByRole waits for element)
258
+ // Calendar should be rendered after async load (find* waits for element)
259
259
  await findByRole('dialog');
260
- await findByRole('grid');
260
+ const cells = await findAllByRole('gridcell');
261
+ expect(cells.length).toBeGreaterThan(0);
261
262
  });
262
263
 
263
264
  test('lazily loads UiRangeCalendar in range mode when popover opens', async () => {
264
- const { getByRole, queryByRole, findByRole } = render(UiDatePicker, {
265
+ const { getByRole, queryByRole, findByRole, findAllByRole } = render(UiDatePicker, {
265
266
  props: { mode: 'range' },
266
267
  });
267
268
 
@@ -271,9 +272,10 @@ describe('UiDatePicker', () => {
271
272
  // Open the popover
272
273
  await userEvent.click(getByRole('button'));
273
274
 
274
- // RangeCalendar should be rendered after async load (findByRole waits for element)
275
+ // RangeCalendar should be rendered after async load (find* waits for element)
275
276
  await findByRole('dialog');
276
- await findByRole('grid');
277
+ const cells = await findAllByRole('gridcell');
278
+ expect(cells.length).toBeGreaterThan(0);
277
279
  });
278
280
 
279
281
  test('single mode calendar is fully functional after lazy load', async () => {
@@ -73,8 +73,8 @@ export const Surface: Story = {
73
73
  };
74
74
 
75
75
  /**
76
- * Hover overlay colors applied to interactive elements on mouse hover.
77
- * These create visual feedback without changing the element's background color.
76
+ * Hover overlay tokens used by `v-hover` (`subtle` / `default` / `strong`).
77
+ * Same three tiers as interactive overlay layers; use these utilities when you need a solid swatch.
78
78
  */
79
79
  export const Hover: Story = {
80
80
  render: () => ({
@@ -90,31 +90,31 @@ export const Hover: Story = {
90
90
 
91
91
  <div class="mb-6">
92
92
  <h2 class="text-xl font-bold text-content-on-surface-primary mb-1">hover</h2>
93
- <p class="text-sm text-content-on-surface-muted">Overlay colors for hover states on interactive elements. Click class name to copy.</p>
93
+ <p class="text-sm text-content-on-surface-muted">Overlay hover tiers (<code class="text-xs">subtle</code>, <code class="text-xs">default</code>, <code class="text-xs">strong</code>). Matches <code class="text-xs">v-hover</code>. Click class name to copy.</p>
94
94
  </div>
95
95
 
96
96
  <div class="flex flex-wrap gap-6">
97
97
  <div class="flex flex-col items-center gap-1.5">
98
- <div class="w-16 h-16 rounded-md bg-overlay-hover-primary-button-hover border border-border-surface-default"></div>
98
+ <div class="w-16 h-16 rounded-md bg-overlay-hover-subtle border border-border-surface-default"></div>
99
99
  <div class="text-center">
100
- <span class="text-xs text-content-on-surface-primary block font-medium">primary-button-hover</span>
101
- <code class="copy-btn text-[10px] text-content-on-surface-muted block mt-0.5 px-1 py-0.5 rounded" onclick="copyToClipboard('bg-overlay-hover-primary-button-hover', this)">bg-overlay-hover-primary-button-hover</code>
100
+ <span class="text-xs text-content-on-surface-primary block font-medium">subtle</span>
101
+ <code class="copy-btn text-[10px] text-content-on-surface-muted block mt-0.5 px-1 py-0.5 rounded" onclick="copyToClipboard('bg-overlay-hover-subtle', this)">bg-overlay-hover-subtle</code>
102
102
  </div>
103
103
  </div>
104
104
 
105
105
  <div class="flex flex-col items-center gap-1.5">
106
- <div class="w-16 h-16 rounded-md bg-overlay-hover-secondary-button-hover border border-border-surface-default"></div>
106
+ <div class="w-16 h-16 rounded-md bg-overlay-hover-default border border-border-surface-default"></div>
107
107
  <div class="text-center">
108
- <span class="text-xs text-content-on-surface-primary block font-medium">secondary-button-hover</span>
109
- <code class="copy-btn text-[10px] text-content-on-surface-muted block mt-0.5 px-1 py-0.5 rounded" onclick="copyToClipboard('bg-overlay-hover-secondary-button-hover', this)">bg-overlay-hover-secondary-button-hover</code>
108
+ <span class="text-xs text-content-on-surface-primary block font-medium">default</span>
109
+ <code class="copy-btn text-[10px] text-content-on-surface-muted block mt-0.5 px-1 py-0.5 rounded" onclick="copyToClipboard('bg-overlay-hover-default', this)">bg-overlay-hover-default</code>
110
110
  </div>
111
111
  </div>
112
112
 
113
113
  <div class="flex flex-col items-center gap-1.5">
114
- <div class="w-16 h-16 rounded-md bg-overlay-hover-default-hover border border-border-surface-default"></div>
114
+ <div class="w-16 h-16 rounded-md bg-overlay-hover-strong border border-border-surface-default"></div>
115
115
  <div class="text-center">
116
- <span class="text-xs text-content-on-surface-primary block font-medium">default-hover</span>
117
- <code class="copy-btn text-[10px] text-content-on-surface-muted block mt-0.5 px-1 py-0.5 rounded" onclick="copyToClipboard('bg-overlay-hover-default-hover', this)">bg-overlay-hover-default-hover</code>
116
+ <span class="text-xs text-content-on-surface-primary block font-medium">strong</span>
117
+ <code class="copy-btn text-[10px] text-content-on-surface-muted block mt-0.5 px-1 py-0.5 rounded" onclick="copyToClipboard('bg-overlay-hover-strong', this)">bg-overlay-hover-strong</code>
118
118
  </div>
119
119
  </div>
120
120
  </div>