@sewadah/selaras 0.2.0 → 0.3.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 (176) hide show
  1. package/README.md +36 -7
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +31 -15
  4. package/dist/runtime/components/Autocomplete.vue +3 -1
  5. package/dist/runtime/components/Badge.d.vue.ts +1 -1
  6. package/dist/runtime/components/Badge.vue.d.ts +1 -1
  7. package/dist/runtime/components/Calendar.d.vue.ts +69 -0
  8. package/dist/runtime/components/Calendar.vue +163 -0
  9. package/dist/runtime/components/Calendar.vue.d.ts +69 -0
  10. package/dist/runtime/components/Callout.vue +12 -1
  11. package/dist/runtime/components/CodeGroup.vue +13 -3
  12. package/dist/runtime/components/CodeTree.d.vue.ts +2 -0
  13. package/dist/runtime/components/CodeTree.vue +52 -1
  14. package/dist/runtime/components/CodeTree.vue.d.ts +2 -0
  15. package/dist/runtime/components/ColorPicker.d.vue.ts +8 -0
  16. package/dist/runtime/components/ColorPicker.vue +66 -45
  17. package/dist/runtime/components/ColorPicker.vue.d.ts +8 -0
  18. package/dist/runtime/components/Column.js +1 -1
  19. package/dist/runtime/components/CommandPalette.d.vue.ts +3 -1
  20. package/dist/runtime/components/CommandPalette.vue +5 -0
  21. package/dist/runtime/components/CommandPalette.vue.d.ts +3 -1
  22. package/dist/runtime/components/ContentNavigation.d.vue.ts +8 -1
  23. package/dist/runtime/components/ContentNavigation.vue +9 -2
  24. package/dist/runtime/components/ContentNavigation.vue.d.ts +8 -1
  25. package/dist/runtime/components/ContentToc.vue +14 -7
  26. package/dist/runtime/components/ContextMenu.d.vue.ts +8 -1
  27. package/dist/runtime/components/ContextMenu.vue +16 -4
  28. package/dist/runtime/components/ContextMenu.vue.d.ts +8 -1
  29. package/dist/runtime/components/DatePicker.d.vue.ts +10 -3
  30. package/dist/runtime/components/DatePicker.vue +24 -16
  31. package/dist/runtime/components/DatePicker.vue.d.ts +10 -3
  32. package/dist/runtime/components/Dropdown.d.vue.ts +33 -6
  33. package/dist/runtime/components/Dropdown.vue +143 -11
  34. package/dist/runtime/components/Dropdown.vue.d.ts +33 -6
  35. package/dist/runtime/components/FileTree.d.vue.ts +2 -0
  36. package/dist/runtime/components/FileTree.vue +8 -4
  37. package/dist/runtime/components/FileTree.vue.d.ts +2 -0
  38. package/dist/runtime/components/FileUpload.vue +1 -1
  39. package/dist/runtime/components/FormField.d.vue.ts +4 -0
  40. package/dist/runtime/components/FormField.vue +19 -4
  41. package/dist/runtime/components/FormField.vue.d.ts +4 -0
  42. package/dist/runtime/components/Input.vue +3 -2
  43. package/dist/runtime/components/Kbd.vue +12 -2
  44. package/dist/runtime/components/NavigationMenu.d.vue.ts +10 -10
  45. package/dist/runtime/components/NavigationMenu.vue +13 -7
  46. package/dist/runtime/components/NavigationMenu.vue.d.ts +10 -10
  47. package/dist/runtime/components/NavigationMenuAccordionItem.vue +5 -1
  48. package/dist/runtime/components/NavigationMenuFlyoutList.vue +7 -4
  49. package/dist/runtime/components/PinInput.vue +1 -1
  50. package/dist/runtime/components/Popover.d.vue.ts +8 -2
  51. package/dist/runtime/components/Popover.vue +9 -4
  52. package/dist/runtime/components/Popover.vue.d.ts +8 -2
  53. package/dist/runtime/components/ProsePre.d.vue.ts +6 -2
  54. package/dist/runtime/components/ProsePre.vue +11 -4
  55. package/dist/runtime/components/ProsePre.vue.d.ts +6 -2
  56. package/dist/runtime/components/Select.vue +3 -1
  57. package/dist/runtime/components/SplitterPanel.d.vue.ts +2 -2
  58. package/dist/runtime/components/SplitterPanel.vue.d.ts +2 -2
  59. package/dist/runtime/components/Table.vue +95 -26
  60. package/dist/runtime/components/Tabs.d.vue.ts +44 -4
  61. package/dist/runtime/components/Tabs.vue +171 -18
  62. package/dist/runtime/components/Tabs.vue.d.ts +44 -4
  63. package/dist/runtime/components/Textarea.vue +3 -2
  64. package/dist/runtime/components/Timeline.d.vue.ts +19 -0
  65. package/dist/runtime/components/Timeline.vue +99 -0
  66. package/dist/runtime/components/Timeline.vue.d.ts +19 -0
  67. package/dist/runtime/components/Toast.d.vue.ts +10 -1
  68. package/dist/runtime/components/Toast.vue +94 -8
  69. package/dist/runtime/components/Toast.vue.d.ts +10 -1
  70. package/dist/runtime/components/Tooltip.d.vue.ts +8 -2
  71. package/dist/runtime/components/Tooltip.vue +9 -4
  72. package/dist/runtime/components/Tooltip.vue.d.ts +8 -2
  73. package/dist/runtime/composables/use-form-field.d.ts +1 -0
  74. package/dist/runtime/composables/use-table.d.ts +3 -0
  75. package/dist/runtime/composables/use-table.js +10 -4
  76. package/dist/runtime/composables/use-toast.js +5 -1
  77. package/dist/runtime/default-colors.css +1 -1
  78. package/dist/runtime/directives/hotkey.d.ts +13 -0
  79. package/dist/runtime/directives/hotkey.js +107 -0
  80. package/dist/runtime/directives/index.d.ts +2 -0
  81. package/dist/runtime/directives/index.js +2 -0
  82. package/dist/runtime/directives/mask.d.ts +30 -0
  83. package/dist/runtime/directives/mask.js +165 -0
  84. package/dist/runtime/internal/ComboboxSelectBase.d.vue.ts +29 -25
  85. package/dist/runtime/internal/ComboboxSelectBase.vue +104 -20
  86. package/dist/runtime/internal/ComboboxSelectBase.vue.d.ts +29 -25
  87. package/dist/runtime/internal/ComboboxSelectBody.d.vue.ts +16 -11
  88. package/dist/runtime/internal/ComboboxSelectBody.vue +49 -10
  89. package/dist/runtime/internal/ComboboxSelectBody.vue.d.ts +16 -11
  90. package/dist/runtime/internal/DatePickerCalendarBody.vue +1 -1
  91. package/dist/runtime/internal/DatePickerRangeCalendarBody.vue +1 -1
  92. package/dist/runtime/internal/ShortcutHint.d.vue.ts +6 -0
  93. package/dist/runtime/internal/ShortcutHint.vue +13 -0
  94. package/dist/runtime/internal/ShortcutHint.vue.d.ts +6 -0
  95. package/dist/runtime/internal/ToastItemRenderer.d.vue.ts +9 -1
  96. package/dist/runtime/internal/ToastItemRenderer.vue +55 -8
  97. package/dist/runtime/internal/ToastItemRenderer.vue.d.ts +9 -1
  98. package/dist/runtime/internal/combobox-select.js +2 -1
  99. package/dist/runtime/internal/programmatic-services.d.ts +2 -0
  100. package/dist/runtime/internal/programmatic-services.js +8 -0
  101. package/dist/runtime/internal/toast-stack.d.ts +36 -0
  102. package/dist/runtime/internal/toast-stack.js +36 -0
  103. package/dist/runtime/structural.css +1 -1
  104. package/dist/runtime/table.d.ts +1 -1
  105. package/dist/runtime/theme/alert-dialog.js +1 -1
  106. package/dist/runtime/theme/avatar.js +7 -7
  107. package/dist/runtime/theme/badge.js +2 -2
  108. package/dist/runtime/theme/calendar.d.ts +86 -0
  109. package/dist/runtime/theme/calendar.js +19 -0
  110. package/dist/runtime/theme/callout.d.ts +0 -12
  111. package/dist/runtime/theme/callout.js +8 -8
  112. package/dist/runtime/theme/checkbox.js +1 -1
  113. package/dist/runtime/theme/code-tree.js +1 -1
  114. package/dist/runtime/theme/command-palette.js +1 -1
  115. package/dist/runtime/theme/content-navigation.d.ts +3 -0
  116. package/dist/runtime/theme/content-navigation.js +1 -0
  117. package/dist/runtime/theme/date-picker.js +1 -1
  118. package/dist/runtime/theme/drawer.js +1 -1
  119. package/dist/runtime/theme/dropdown.d.ts +21 -0
  120. package/dist/runtime/theme/dropdown.js +15 -2
  121. package/dist/runtime/theme/file-tree.js +1 -1
  122. package/dist/runtime/theme/file-upload.js +2 -2
  123. package/dist/runtime/theme/form-field.d.ts +18 -0
  124. package/dist/runtime/theme/form-field.js +6 -1
  125. package/dist/runtime/theme/icon.js +1 -1
  126. package/dist/runtime/theme/input-number.js +1 -1
  127. package/dist/runtime/theme/input.js +1 -1
  128. package/dist/runtime/theme/modal.js +1 -1
  129. package/dist/runtime/theme/pin-input.js +1 -1
  130. package/dist/runtime/theme/progress.js +2 -2
  131. package/dist/runtime/theme/prose.d.ts +6 -0
  132. package/dist/runtime/theme/prose.js +2 -0
  133. package/dist/runtime/theme/radio-group.js +4 -4
  134. package/dist/runtime/theme/rating.js +1 -1
  135. package/dist/runtime/theme/select.js +2 -2
  136. package/dist/runtime/theme/separator.js +1 -1
  137. package/dist/runtime/theme/slideover.js +1 -1
  138. package/dist/runtime/theme/slider.js +2 -2
  139. package/dist/runtime/theme/splitter-resize-handle.js +1 -1
  140. package/dist/runtime/theme/switch.js +1 -1
  141. package/dist/runtime/theme/table.d.ts +27 -0
  142. package/dist/runtime/theme/table.js +11 -2
  143. package/dist/runtime/theme/tabs.d.ts +87 -3
  144. package/dist/runtime/theme/tabs.js +46 -22
  145. package/dist/runtime/theme/textarea.js +1 -1
  146. package/dist/runtime/theme/timeline.d.ts +251 -0
  147. package/dist/runtime/theme/timeline.js +84 -0
  148. package/dist/runtime/theme/toast.d.ts +92 -0
  149. package/dist/runtime/theme/toast.js +29 -7
  150. package/dist/runtime/theme-config.d.ts +12 -1
  151. package/dist/runtime/types/index.d.ts +6 -1
  152. package/dist/runtime/utils/arrow.d.ts +21 -0
  153. package/dist/runtime/utils/arrow.js +10 -0
  154. package/dist/runtime/utils/color-registry.d.ts +3 -1
  155. package/dist/runtime/utils/color-registry.js +4 -0
  156. package/dist/runtime/utils/file-icons.d.ts +8 -0
  157. package/dist/runtime/utils/file-icons.js +133 -0
  158. package/dist/runtime/utils/functional-tokens.d.ts +1 -1
  159. package/dist/runtime/utils/functional-tokens.js +1 -1
  160. package/dist/runtime/utils/icons.d.ts +2 -0
  161. package/dist/runtime/utils/icons.js +2 -0
  162. package/dist/runtime/utils/messages.d.ts +6 -0
  163. package/dist/runtime/utils/messages.js +6 -0
  164. package/dist/runtime/utils/navigation-menu.d.ts +10 -0
  165. package/dist/runtime/utils/navigation-menu.js +3 -0
  166. package/dist/runtime/utils/overlay.d.ts +17 -0
  167. package/dist/runtime/utils/overlay.js +7 -0
  168. package/dist/runtime/utils/seed-colors.d.ts +2 -1
  169. package/dist/runtime/utils/seed-colors.js +24 -16
  170. package/dist/runtime/utils/select-contracts.d.ts +7 -1
  171. package/dist/runtime/utils/table-columns.js +1 -1
  172. package/dist/runtime/utils/table-contracts.d.ts +9 -1
  173. package/dist/runtime/utils/timeline-contracts.d.ts +42 -0
  174. package/dist/runtime/utils/timeline-contracts.js +0 -0
  175. package/dist/tokens/default-colors.tokens.json +100 -100
  176. package/package.json +21 -9
@@ -15,11 +15,14 @@ import Checkbox from "./Checkbox.vue";
15
15
  import Icon from "./Icon.vue";
16
16
  import Input from "./Input.vue";
17
17
  import Pagination from "./Pagination.vue";
18
+ import Popover from "./Popover.vue";
18
19
  defineOptions({ inheritAttrs: false });
19
20
  const props = defineProps({
20
21
  data: { type: Array, required: true },
21
22
  columns: { type: Array, required: false },
22
23
  selectable: { type: Boolean, required: false },
24
+ rowHover: { type: Boolean, required: false },
25
+ selectOnRowClick: { type: Boolean, required: false },
23
26
  pageSize: { type: Number, required: false },
24
27
  loading: { type: Boolean, required: false },
25
28
  sorting: { type: null, required: false },
@@ -32,6 +35,8 @@ const props = defineProps({
32
35
  gridlines: { type: Boolean, required: false },
33
36
  striped: { type: Boolean, required: false },
34
37
  expandable: { type: Boolean, required: false },
38
+ tree: { type: Boolean, required: false },
39
+ getSubRows: { type: Function, required: false },
35
40
  expanded: { type: null, required: false },
36
41
  columnVisibility: { type: null, required: false },
37
42
  columnToggle: { type: Boolean, required: false },
@@ -56,7 +61,8 @@ const ui = computed(() => theme.value({
56
61
  color: effectiveColor.value,
57
62
  gridlines: props.gridlines,
58
63
  striped: props.striped,
59
- scrollable: !!props.scrollHeight
64
+ scrollable: !!props.scrollHeight,
65
+ rowHover: props.rowHover
60
66
  }));
61
67
  const selectColumn = {
62
68
  id: "__select__",
@@ -88,7 +94,7 @@ const expandColumn = {
88
94
  };
89
95
  const columns = computed(() => {
90
96
  const base = props.columns ?? convertChildrenToColumns(slots.default?.());
91
- const withExpand = props.expandable ? [expandColumn, ...base] : base;
97
+ const withExpand = props.expandable && !props.tree ? [expandColumn, ...base] : base;
92
98
  return props.selectable ? [selectColumn, ...withExpand] : withExpand;
93
99
  });
94
100
  const columnPinning = computed(() => collectColumnPinning(columns.value));
@@ -111,7 +117,7 @@ const theadProps = computed(() => resolveSlot(ui.value.thead, props.ui?.thead));
111
117
  const trProps = computed(() => resolveSlot(ui.value.tr, props.ui?.tr));
112
118
  const instance = getCurrentInstance();
113
119
  function isRowClickable() {
114
- return !!instance?.vnode.props?.onRowClick;
120
+ return !!props.selectable && props.selectOnRowClick || !!instance?.vnode.props?.onRowClick;
115
121
  }
116
122
  function bodyRowProps(rowOriginal) {
117
123
  const extraClass = [
@@ -122,7 +128,13 @@ function bodyRowProps(rowOriginal) {
122
128
  const rowStyle = props.rowStyle?.(rowOriginal);
123
129
  return rowStyle ? { ...base, style: { ...base.style, ...rowStyle } } : base;
124
130
  }
125
- function onRowClick(rowOriginal, event) {
131
+ function onRowClick(rowOriginal, event, toggleSelection) {
132
+ const target = event.target;
133
+ const clickedControl = target instanceof Element && target.closest(
134
+ 'a, button, input, select, textarea, [role="button"], [role="checkbox"], [role="radio"], [role="link"], [contenteditable="true"], [data-row-click-ignore]'
135
+ );
136
+ if (props.selectable && props.selectOnRowClick && !clickedControl)
137
+ toggleSelection?.();
126
138
  emit("rowClick", rowOriginal, event);
127
139
  }
128
140
  function onRowContextmenu(rowOriginal, event) {
@@ -135,6 +147,9 @@ const tdProps = computed(() => resolveSlot(ui.value.td, props.ui?.td));
135
147
  const tfootProps = computed(() => resolveSlot(ui.value.tfoot, props.ui?.tfoot));
136
148
  const emptyStateProps = computed(() => resolveSlot(ui.value.emptyState, props.ui?.emptyState));
137
149
  const filterInputProps = computed(() => resolveSlot(ui.value.filterInput, props.ui?.filterInput));
150
+ const filterButtonProps = computed(() => resolveSlot(ui.value.filterButton, props.ui?.filterButton));
151
+ const filterIconProps = computed(() => resolveSlot(ui.value.filterIcon, props.ui?.filterIcon));
152
+ const filterPopoverProps = computed(() => resolveSlot(ui.value.filterPopover, props.ui?.filterPopover));
138
153
  const paginationWrapperProps = computed(() => resolveSlot(ui.value.paginationWrapper, props.ui?.paginationWrapper));
139
154
  const paginationInfoProps = computed(() => resolveSlot(ui.value.paginationInfo, props.ui?.paginationInfo));
140
155
  const paginationButtonsProps = computed(() => resolveSlot(ui.value.paginationButtons, props.ui?.paginationButtons));
@@ -148,6 +163,8 @@ const columnToggleItemProps = computed(() => resolveSlot(ui.value.columnToggleIt
148
163
  const hasFooter = computed(
149
164
  () => table.getFooterGroups().some((group) => group.headers.some((header) => header.column.columnDef.footer))
150
165
  );
166
+ const treeColumnId = computed(() => table.getVisibleLeafColumns().find((column) => !column.id.startsWith("__"))?.id);
167
+ const treeCellProps = computed(() => resolveSlot(ui.value.treeCell, props.ui?.treeCell));
151
168
  const headerRefs = /* @__PURE__ */ new Map();
152
169
  function setHeaderRef(id, el) {
153
170
  if (el)
@@ -255,26 +272,56 @@ defineExpose({
255
272
  @keydown.space.prevent="header.column.getToggleSortingHandler()?.($event)"
256
273
  >
257
274
  <template v-if="!header.isPlaceholder || header.rowSpan > 1">
258
- <FlexRender :header="header" />
259
- <Icon
260
- v-if="header.column.getIsSorted() === 'asc'"
261
- :name="icons.sortAscending"
262
- v-bind="sortIconProps"
263
- />
264
- <Icon
265
- v-else-if="header.column.getIsSorted() === 'desc'"
266
- :name="icons.sortDescending"
267
- v-bind="sortIconProps"
268
- />
269
- <div v-if="header.column.getCanFilter()">
270
- <Input
271
- size="sm"
272
- :model-value="header.column.getFilterValue() ?? ''"
273
- :placeholder="messages.filterPlaceholder"
274
- v-bind="filterInputProps"
275
- @click.stop
276
- @update:model-value="(value) => header.column.setFilterValue(value)"
275
+ <div :class="applyClassPrefix('inline-flex items-center gap-1')">
276
+ <FlexRender :header="header" />
277
+ <Icon
278
+ v-if="header.column.getCanSort()"
279
+ :name="header.column.getIsSorted() === 'asc' ? icons.sortAscending : header.column.getIsSorted() === 'desc' ? icons.sortDescending : icons.sortUnsorted"
280
+ :data-sort-state="header.column.getIsSorted() || 'none'"
281
+ v-bind="sortIconProps"
277
282
  />
283
+ <Popover v-if="header.column.getCanFilter()" side="bottom" align="start" :ui="{ content: filterPopoverProps }">
284
+ <Button
285
+ v-bind="filterButtonProps"
286
+ variant="ghost"
287
+ :color="header.column.getIsFiltered() ? 'primary' : 'neutral'"
288
+ size="sm"
289
+ square
290
+ :aria-label="messages.filterColumn(typeof header.column.columnDef.header === 'string' ? header.column.columnDef.header : header.column.id)"
291
+ :aria-pressed="header.column.getIsFiltered()"
292
+ @click.stop
293
+ @keydown.stop
294
+ >
295
+ <Icon :name="icons.filter" v-bind="filterIconProps" />
296
+ </Button>
297
+ <template #content>
298
+ <div :class="applyClassPrefix('flex flex-col gap-2')">
299
+ <div :class="applyClassPrefix('flex items-center justify-between gap-2')">
300
+ <span :class="applyClassPrefix('text-sm font-medium text-[var(--selaras-resolved-text-default)]')">
301
+ {{ messages.filterColumn(typeof header.column.columnDef.header === "string" ? header.column.columnDef.header : header.column.id) }}
302
+ </span>
303
+ <Button
304
+ variant="text"
305
+ color="neutral"
306
+ size="sm"
307
+ :disabled="!header.column.getIsFiltered()"
308
+ @click.stop="header.column.setFilterValue(void 0)"
309
+ >
310
+ {{ messages.clear }}
311
+ </Button>
312
+ </div>
313
+ <Input
314
+ size="sm"
315
+ :model-value="header.column.getFilterValue() ?? ''"
316
+ :placeholder="messages.filterPlaceholder"
317
+ :aria-label="messages.filterColumn(typeof header.column.columnDef.header === 'string' ? header.column.columnDef.header : header.column.id)"
318
+ v-bind="filterInputProps"
319
+ @click.stop
320
+ @update:model-value="(value) => header.column.setFilterValue(value)"
321
+ />
322
+ </div>
323
+ </template>
324
+ </Popover>
278
325
  </div>
279
326
  </template>
280
327
  </th>
@@ -289,7 +336,7 @@ defineExpose({
289
336
  <template v-for="row in visibleRows" :key="row.id">
290
337
  <tr
291
338
  v-bind="bodyRowProps(row.original)"
292
- @click="onRowClick(row.original, $event)"
339
+ @click="onRowClick(row.original, $event, row.getCanSelect() ? () => row.toggleSelected() : void 0)"
293
340
  @contextmenu="onRowContextmenu(row.original, $event)"
294
341
  >
295
342
  <td
@@ -299,10 +346,32 @@ defineExpose({
299
346
  :data-pinned="cell.column.getIsPinned() || void 0"
300
347
  :style="pinnedStyle(cell)"
301
348
  >
302
- <FlexRender :cell="cell" />
349
+ <div
350
+ v-if="tree && cell.column.id === treeColumnId"
351
+ v-bind="treeCellProps"
352
+ :style="{ paddingInlineStart: `${row.depth * 1.25}rem` }"
353
+ >
354
+ <button
355
+ v-if="row.getCanExpand()"
356
+ type="button"
357
+ v-bind="resolveSlot(ui.expandButton, props.ui?.expandButton)"
358
+ :aria-label="row.getIsExpanded() ? messages.collapseRow : messages.expandRow"
359
+ :aria-expanded="row.getIsExpanded()"
360
+ @click.stop="row.toggleExpanded()"
361
+ >
362
+ <Icon
363
+ :name="icons.chevronRight"
364
+ v-bind="resolveSlot(ui.expandChevron, props.ui?.expandChevron)"
365
+ :data-expanded="row.getIsExpanded() || void 0"
366
+ />
367
+ </button>
368
+ <span v-else :class="resolveSlot(ui.expandButton, props.ui?.expandButton).class" aria-hidden="true" />
369
+ <FlexRender :cell="cell" />
370
+ </div>
371
+ <FlexRender v-else :cell="cell" />
303
372
  </td>
304
373
  </tr>
305
- <tr v-if="row.getIsExpanded()" v-bind="expandedRowProps">
374
+ <tr v-if="row.getIsExpanded() && !tree" v-bind="expandedRowProps">
306
375
  <td :colspan="row.getVisibleCells().length" v-bind="expandedCellProps">
307
376
  <slot name="expanded" :row="row.original" />
308
377
  </td>
@@ -15,6 +15,12 @@ export interface TabsProps {
15
15
  variant?: TabsVariants['variant'];
16
16
  defaultValue?: string;
17
17
  modelValue?: string;
18
+ /** Direction of the tab list and its keyboard navigation. @default 'horizontal' */
19
+ orientation?: 'horizontal' | 'vertical';
20
+ /** Unmount inactive panels. Set to false to preserve their content and local state. @default true */
21
+ unmountOnHide?: boolean;
22
+ /** Enables horizontal scrolling and overflow controls when the tabs do not fit. */
23
+ scrollable?: boolean;
18
24
  /** The active tab's text and underline accent. @default 'primary' */
19
25
  color?: ColorRole;
20
26
  ui?: UiProp<TabsThemeSlots>;
@@ -22,14 +28,48 @@ export interface TabsProps {
22
28
  export interface TabsEmits {
23
29
  'update:modelValue': [value: string];
24
30
  }
25
- declare var __VLS_27: {
31
+ declare function scrollLeft(): void;
32
+ declare function scrollRight(): void;
33
+ declare var __VLS_10: {
34
+ buttonProps: {
35
+ type: "button";
36
+ disabled: boolean;
37
+ 'aria-label': string;
38
+ onClick: typeof scrollLeft;
39
+ };
40
+ scroll: typeof scrollLeft;
41
+ disabled: boolean;
42
+ ariaLabel: string;
43
+ }, __VLS_19: {
44
+ class: unknown[];
45
+ }, __VLS_43: {
26
46
  item: TabItem;
27
47
  index: number;
28
- }, __VLS_41: string, __VLS_42: {};
48
+ }, __VLS_50: {
49
+ buttonProps: {
50
+ type: "button";
51
+ disabled: boolean;
52
+ 'aria-label': string;
53
+ onClick: typeof scrollRight;
54
+ };
55
+ scroll: typeof scrollRight;
56
+ disabled: boolean;
57
+ ariaLabel: string;
58
+ }, __VLS_59: {
59
+ class: unknown[];
60
+ }, __VLS_73: string, __VLS_74: {};
29
61
  type __VLS_Slots = {} & {
30
- [K in NonNullable<typeof __VLS_41>]?: (props: typeof __VLS_42) => any;
62
+ [K in NonNullable<typeof __VLS_73>]?: (props: typeof __VLS_74) => any;
63
+ } & {
64
+ 'scroll-left'?: (props: typeof __VLS_10) => any;
65
+ } & {
66
+ 'scroll-left-icon'?: (props: typeof __VLS_19) => any;
67
+ } & {
68
+ label?: (props: typeof __VLS_43) => any;
69
+ } & {
70
+ 'scroll-right'?: (props: typeof __VLS_50) => any;
31
71
  } & {
32
- label?: (props: typeof __VLS_27) => any;
72
+ 'scroll-right-icon'?: (props: typeof __VLS_59) => any;
33
73
  };
34
74
  declare const __VLS_base: import("vue").DefineComponent<TabsProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
35
75
  "update:modelValue": (value: string) => any;
@@ -1,9 +1,12 @@
1
1
  <script setup>
2
2
  import { TabsContent, TabsIndicator, TabsList, TabsRoot, TabsTrigger } from "reka-ui";
3
- import { computed } from "vue";
3
+ import { computed, getCurrentInstance, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
4
+ import { useIcons } from "../composables/use-icons";
5
+ import { useMessages } from "../composables/use-messages";
4
6
  import { tabsTheme } from "../theme/tabs";
5
7
  import { resolveRegisteredColorRole } from "../utils/registered-colors";
6
8
  import { resolveSlot, useComponentTheme, useRootProps } from "../utils/ui";
9
+ import Button from "./Button.vue";
7
10
  import Icon from "./Icon.vue";
8
11
  defineOptions({ inheritAttrs: false });
9
12
  const props = defineProps({
@@ -11,19 +14,131 @@ const props = defineProps({
11
14
  variant: { type: null, required: false },
12
15
  defaultValue: { type: String, required: false },
13
16
  modelValue: { type: String, required: false },
17
+ orientation: { type: String, required: false, default: "horizontal" },
18
+ unmountOnHide: { type: Boolean, required: false, default: true },
19
+ scrollable: { type: Boolean, required: false },
14
20
  color: { type: null, required: false },
15
21
  ui: { type: Object, required: false }
16
22
  });
17
- defineEmits(["update:modelValue"]);
23
+ const emit = defineEmits(["update:modelValue"]);
18
24
  const theme = useComponentTheme("tabs", tabsTheme);
25
+ const icons = useIcons();
26
+ const messages = useMessages();
19
27
  const effectiveColor = computed(() => resolveRegisteredColorRole(props.color ?? "primary", "primary"));
20
- const ui = computed(() => theme.value({ variant: props.variant, color: effectiveColor.value }));
28
+ const horizontalScrollable = computed(() => props.scrollable && props.orientation === "horizontal");
29
+ const ui = computed(() => theme.value({ variant: props.variant, color: effectiveColor.value, orientation: props.orientation, scrollable: horizontalScrollable.value }));
21
30
  const rootProps = useRootProps(() => ui.value.root, () => props.ui?.root);
31
+ const scrollRootProps = computed(() => resolveSlot(ui.value.scrollRoot, props.ui?.scrollRoot));
32
+ const scrollViewportProps = computed(() => resolveSlot(ui.value.scrollViewport, props.ui?.scrollViewport));
33
+ const scrollButtonProps = computed(() => resolveSlot(ui.value.scrollButton, props.ui?.scrollButton));
34
+ const scrollIconProps = computed(() => resolveSlot(ui.value.scrollIcon, props.ui?.scrollIcon));
22
35
  const listProps = computed(() => resolveSlot(ui.value.list, props.ui?.list));
23
36
  const triggerProps = computed(() => resolveSlot(ui.value.trigger, props.ui?.trigger));
24
37
  const iconProps = computed(() => resolveSlot(ui.value.icon, props.ui?.icon));
25
38
  const indicatorProps = computed(() => resolveSlot(ui.value.indicator, props.ui?.indicator));
26
39
  const contentProps = computed(() => resolveSlot(ui.value.content, props.ui?.content));
40
+ const scrollRootEl = ref();
41
+ const scrollViewportEl = ref();
42
+ const hasOverflow = ref(false);
43
+ const canScrollLeft = ref(false);
44
+ const canScrollRight = ref(false);
45
+ const leftButtonProps = computed(() => ({
46
+ "type": "button",
47
+ "disabled": !canScrollLeft.value,
48
+ "aria-label": messages.value.scrollTabsLeft,
49
+ "onClick": scrollLeft
50
+ }));
51
+ const rightButtonProps = computed(() => ({
52
+ "type": "button",
53
+ "disabled": !canScrollRight.value,
54
+ "aria-label": messages.value.scrollTabsRight,
55
+ "onClick": scrollRight
56
+ }));
57
+ let resizeObserver;
58
+ function tabList() {
59
+ return scrollViewportEl.value?.querySelector('[role="tablist"]');
60
+ }
61
+ function updateScrollState() {
62
+ const root = scrollRootEl.value;
63
+ const viewport = scrollViewportEl.value;
64
+ const list = tabList();
65
+ if (!horizontalScrollable.value || !root || !viewport || !list) {
66
+ hasOverflow.value = false;
67
+ canScrollLeft.value = false;
68
+ canScrollRight.value = false;
69
+ return;
70
+ }
71
+ hasOverflow.value = list.scrollWidth > root.clientWidth + 1;
72
+ const listRect = list.getBoundingClientRect();
73
+ const viewportRect = viewport.getBoundingClientRect();
74
+ canScrollLeft.value = hasOverflow.value && listRect.left < viewportRect.left - 1;
75
+ canScrollRight.value = hasOverflow.value && listRect.right > viewportRect.right + 1;
76
+ }
77
+ function scrollBehavior() {
78
+ return window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches ? "auto" : "smooth";
79
+ }
80
+ function scrollTabs(direction) {
81
+ const viewport = scrollViewportEl.value;
82
+ if (!viewport)
83
+ return;
84
+ viewport.scrollBy({ left: direction * viewport.clientWidth * 0.8, behavior: scrollBehavior() });
85
+ }
86
+ function scrollLeft() {
87
+ scrollTabs(-1);
88
+ }
89
+ function scrollRight() {
90
+ scrollTabs(1);
91
+ }
92
+ function scrollActiveIntoView(behavior = "auto") {
93
+ if (!horizontalScrollable.value)
94
+ return;
95
+ const viewport = scrollViewportEl.value;
96
+ const active = tabList()?.querySelector('[role="tab"][data-state="active"]');
97
+ if (!viewport || !active)
98
+ return;
99
+ const viewportRect = viewport.getBoundingClientRect();
100
+ const activeRect = active.getBoundingClientRect();
101
+ const delta = activeRect.left < viewportRect.left ? activeRect.left - viewportRect.left : activeRect.right > viewportRect.right ? activeRect.right - viewportRect.right : 0;
102
+ if (delta)
103
+ viewport.scrollBy({ left: delta, behavior });
104
+ }
105
+ const instance = getCurrentInstance();
106
+ const isControlled = () => Object.hasOwn(instance?.vnode.props ?? {}, "modelValue") || Object.hasOwn(instance?.vnode.props ?? {}, "model-value");
107
+ async function onValueChange(value) {
108
+ emit("update:modelValue", value);
109
+ if (isControlled())
110
+ return;
111
+ await nextTick();
112
+ scrollActiveIntoView(scrollBehavior());
113
+ }
114
+ function onResize() {
115
+ updateScrollState();
116
+ scrollActiveIntoView();
117
+ }
118
+ onMounted(async () => {
119
+ await nextTick();
120
+ if (typeof ResizeObserver !== "undefined") {
121
+ resizeObserver = new ResizeObserver(onResize);
122
+ for (const element of [scrollRootEl.value, scrollViewportEl.value, tabList()]) {
123
+ if (element)
124
+ resizeObserver.observe(element);
125
+ }
126
+ }
127
+ window.addEventListener("resize", onResize);
128
+ onResize();
129
+ });
130
+ onUnmounted(() => {
131
+ resizeObserver?.disconnect();
132
+ window.removeEventListener("resize", onResize);
133
+ });
134
+ watch(() => props.modelValue, async () => {
135
+ await nextTick();
136
+ scrollActiveIntoView(scrollBehavior());
137
+ });
138
+ watch(horizontalScrollable, async () => {
139
+ await nextTick();
140
+ onResize();
141
+ });
27
142
  function itemValue(item, index) {
28
143
  return item.value ?? String(index);
29
144
  }
@@ -33,25 +148,63 @@ function itemValue(item, index) {
33
148
  <TabsRoot
34
149
  :default-value="defaultValue"
35
150
  :model-value="modelValue"
151
+ :orientation="orientation"
152
+ :unmount-on-hide="unmountOnHide"
36
153
  :data-selaras-color="effectiveColor"
37
154
  v-bind="rootProps"
38
- @update:model-value="(value) => $emit('update:modelValue', value)"
155
+ @update:model-value="(value) => onValueChange(value)"
39
156
  >
40
- <TabsList v-bind="listProps">
41
- <TabsTrigger
42
- v-for="(item, index) in items"
43
- :key="itemValue(item, index)"
44
- :value="itemValue(item, index)"
45
- :disabled="item.disabled"
46
- v-bind="triggerProps"
157
+ <div ref="scrollRootEl" v-bind="scrollRootProps">
158
+ <slot
159
+ v-if="horizontalScrollable && hasOverflow"
160
+ name="scroll-left"
161
+ :button-props="leftButtonProps"
162
+ :scroll="scrollLeft"
163
+ :disabled="leftButtonProps.disabled"
164
+ :aria-label="leftButtonProps['aria-label']"
165
+ >
166
+ <Button variant="ghost" color="neutral" size="sm" square :ui="{ base: scrollButtonProps }" v-bind="leftButtonProps">
167
+ <template #icon="{ class: iconClass }">
168
+ <slot name="scroll-left-icon" :class="[iconClass, scrollIconProps.class]">
169
+ <Icon :name="icons.chevronLeft" aria-hidden="true" v-bind="scrollIconProps" :class="iconClass" />
170
+ </slot>
171
+ </template>
172
+ </Button>
173
+ </slot>
174
+ <div ref="scrollViewportEl" v-bind="scrollViewportProps" @scroll="updateScrollState">
175
+ <TabsList v-bind="listProps">
176
+ <TabsTrigger
177
+ v-for="(item, index) in items"
178
+ :key="itemValue(item, index)"
179
+ :value="itemValue(item, index)"
180
+ :disabled="item.disabled"
181
+ v-bind="triggerProps"
182
+ >
183
+ <Icon v-if="item.icon" :name="item.icon" v-bind="iconProps" />
184
+ <slot name="label" :item="item" :index="index">
185
+ {{ item.label }}
186
+ </slot>
187
+ </TabsTrigger>
188
+ <TabsIndicator aria-hidden="true" v-bind="indicatorProps" />
189
+ </TabsList>
190
+ </div>
191
+ <slot
192
+ v-if="horizontalScrollable && hasOverflow"
193
+ name="scroll-right"
194
+ :button-props="rightButtonProps"
195
+ :scroll="scrollRight"
196
+ :disabled="rightButtonProps.disabled"
197
+ :aria-label="rightButtonProps['aria-label']"
47
198
  >
48
- <Icon v-if="item.icon" :name="item.icon" v-bind="iconProps" />
49
- <slot name="label" :item="item" :index="index">
50
- {{ item.label }}
51
- </slot>
52
- </TabsTrigger>
53
- <TabsIndicator aria-hidden="true" v-bind="indicatorProps" />
54
- </TabsList>
199
+ <Button variant="ghost" color="neutral" size="sm" square :ui="{ base: scrollButtonProps }" v-bind="rightButtonProps">
200
+ <template #icon="{ class: iconClass }">
201
+ <slot name="scroll-right-icon" :class="[iconClass, scrollIconProps.class]">
202
+ <Icon :name="icons.chevronRight" aria-hidden="true" v-bind="scrollIconProps" :class="iconClass" />
203
+ </slot>
204
+ </template>
205
+ </Button>
206
+ </slot>
207
+ </div>
55
208
  <TabsContent
56
209
  v-for="(item, index) in items"
57
210
  :key="itemValue(item, index)"
@@ -15,6 +15,12 @@ export interface TabsProps {
15
15
  variant?: TabsVariants['variant'];
16
16
  defaultValue?: string;
17
17
  modelValue?: string;
18
+ /** Direction of the tab list and its keyboard navigation. @default 'horizontal' */
19
+ orientation?: 'horizontal' | 'vertical';
20
+ /** Unmount inactive panels. Set to false to preserve their content and local state. @default true */
21
+ unmountOnHide?: boolean;
22
+ /** Enables horizontal scrolling and overflow controls when the tabs do not fit. */
23
+ scrollable?: boolean;
18
24
  /** The active tab's text and underline accent. @default 'primary' */
19
25
  color?: ColorRole;
20
26
  ui?: UiProp<TabsThemeSlots>;
@@ -22,14 +28,48 @@ export interface TabsProps {
22
28
  export interface TabsEmits {
23
29
  'update:modelValue': [value: string];
24
30
  }
25
- declare var __VLS_27: {
31
+ declare function scrollLeft(): void;
32
+ declare function scrollRight(): void;
33
+ declare var __VLS_10: {
34
+ buttonProps: {
35
+ type: "button";
36
+ disabled: boolean;
37
+ 'aria-label': string;
38
+ onClick: typeof scrollLeft;
39
+ };
40
+ scroll: typeof scrollLeft;
41
+ disabled: boolean;
42
+ ariaLabel: string;
43
+ }, __VLS_19: {
44
+ class: unknown[];
45
+ }, __VLS_43: {
26
46
  item: TabItem;
27
47
  index: number;
28
- }, __VLS_41: string, __VLS_42: {};
48
+ }, __VLS_50: {
49
+ buttonProps: {
50
+ type: "button";
51
+ disabled: boolean;
52
+ 'aria-label': string;
53
+ onClick: typeof scrollRight;
54
+ };
55
+ scroll: typeof scrollRight;
56
+ disabled: boolean;
57
+ ariaLabel: string;
58
+ }, __VLS_59: {
59
+ class: unknown[];
60
+ }, __VLS_73: string, __VLS_74: {};
29
61
  type __VLS_Slots = {} & {
30
- [K in NonNullable<typeof __VLS_41>]?: (props: typeof __VLS_42) => any;
62
+ [K in NonNullable<typeof __VLS_73>]?: (props: typeof __VLS_74) => any;
63
+ } & {
64
+ 'scroll-left'?: (props: typeof __VLS_10) => any;
65
+ } & {
66
+ 'scroll-left-icon'?: (props: typeof __VLS_19) => any;
67
+ } & {
68
+ label?: (props: typeof __VLS_43) => any;
69
+ } & {
70
+ 'scroll-right'?: (props: typeof __VLS_50) => any;
31
71
  } & {
32
- label?: (props: typeof __VLS_27) => any;
72
+ 'scroll-right-icon'?: (props: typeof __VLS_59) => any;
33
73
  };
34
74
  declare const __VLS_base: import("vue").DefineComponent<TabsProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
35
75
  "update:modelValue": (value: string) => any;
@@ -29,6 +29,7 @@ const props = defineProps({
29
29
  });
30
30
  const emit = defineEmits(["update:modelValue"]);
31
31
  const field = useFormField();
32
+ const effectivePlaceholder = computed(() => props.placeholder || (field?.floatingLabel.value ? " " : void 0));
32
33
  const textareaId = computed(() => props.id ?? field?.id);
33
34
  const textareaInvalid = computed(() => props.invalid || (field?.invalid.value ?? false));
34
35
  const describedBy = computed(() => field?.describedBy.value);
@@ -78,7 +79,7 @@ watch(() => props.modelValue, resize);
78
79
  </script>
79
80
 
80
81
  <template>
81
- <div :data-selaras-color="textareaInvalid ? 'danger' : effectiveColor" v-bind="rootProps">
82
+ <div :data-selaras-color="textareaInvalid ? 'danger' : effectiveColor" :data-selaras-field-leading="icon ? '' : void 0" v-bind="rootProps">
82
83
  <Icon v-if="icon" :name="icon" v-bind="resolveSlot(ui.leadingIcon, props.ui?.leadingIcon)" />
83
84
  <textarea
84
85
  :id="textareaId"
@@ -86,7 +87,7 @@ watch(() => props.modelValue, resize);
86
87
  data-ui-group-item
87
88
  :value="modelValue"
88
89
  :name="name ?? field?.name"
89
- :placeholder="placeholder"
90
+ :placeholder="effectivePlaceholder"
90
91
  :rows="rows"
91
92
  :disabled="disabled"
92
93
  :aria-invalid="textareaInvalid || void 0"
@@ -0,0 +1,19 @@
1
+ import type { TimelineItem, TimelineProps, TimelineSlots } from '../utils/timeline-contracts.js';
2
+ declare const __VLS_export: <Item extends TimelineItem>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<TimelineProps<Item>> & (typeof globalThis extends {
4
+ __VLS_PROPS_FALLBACK: infer P;
5
+ } ? P : {});
6
+ expose: (exposed: {}) => void;
7
+ attrs: any;
8
+ slots: TimelineSlots<Item>;
9
+ emit: {};
10
+ }>) => import("vue").VNode & {
11
+ __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
12
+ };
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
15
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
16
+ [K in keyof T]: T[K];
17
+ } : {
18
+ [K in keyof T as K]: T[K];
19
+ }) & {};