@dolanske/vui 0.1.0 → 0.1.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 (161) hide show
  1. package/README.md +7 -0
  2. package/dist/components/Accordion/Accordion.vue.d.ts +45 -0
  3. package/dist/components/Accordion/AccordionGroup.vue.d.ts +32 -0
  4. package/dist/components/Alert/Alert.vue.d.ts +29 -0
  5. package/dist/components/Avatar/Avatar.vue.d.ts +9 -0
  6. package/dist/components/Badge/Badge.vue.d.ts +21 -0
  7. package/dist/components/Breadcrumbs/BreadcrumbItem.vue.d.ts +21 -0
  8. package/dist/components/Breadcrumbs/Breadcrumbs.vue.d.ts +27 -0
  9. package/dist/components/Button/Button.vue.d.ts +41 -0
  10. package/dist/components/ButtonGroup/ButtonGroup.vue.d.ts +19 -0
  11. package/dist/components/Calendar/Calendar.vue.d.ts +27 -0
  12. package/dist/components/Card/Card.vue.d.ts +25 -0
  13. package/dist/components/Checkbox/Checkbox.vue.d.ts +31 -0
  14. package/dist/components/CopyClipboard/CopyClipboard.vue.d.ts +40 -0
  15. package/dist/components/Divider/Divider.vue.d.ts +24 -0
  16. package/dist/components/Drawer/Drawer.vue.d.ts +52 -0
  17. package/dist/components/Dropdown/DropdownItem.vue.d.ts +21 -0
  18. package/dist/components/Dropdown/DropdownTitle.vue.d.ts +17 -0
  19. package/dist/components/Flex/Flex.vue.d.ts +38 -0
  20. package/dist/components/Grid/Grid.vue.d.ts +27 -0
  21. package/dist/components/Input/Counter.vue.d.ts +19 -0
  22. package/dist/components/Input/Dropzone.vue.d.ts +107 -0
  23. package/dist/components/Input/File.vue.d.ts +7 -0
  24. package/dist/components/Input/Input.vue.d.ts +54 -0
  25. package/dist/components/Input/Password.vue.d.ts +6 -0
  26. package/dist/components/Input/Textarea.vue.d.ts +30 -0
  27. package/dist/components/Kbd/Kbd.vue.d.ts +23 -0
  28. package/dist/components/Kbd/KbdGroup.vue.d.ts +31 -0
  29. package/dist/components/Modal/Confirm.vue.d.ts +45 -0
  30. package/dist/components/Modal/Modal.vue.d.ts +55 -0
  31. package/dist/components/Pagination/Pagination.vue.d.ts +42 -0
  32. package/dist/components/Pagination/pagination.d.ts +12 -0
  33. package/dist/components/Popout/Popout.vue.d.ts +34 -0
  34. package/dist/components/Progress/Progress.vue.d.ts +31 -0
  35. package/dist/components/Radio/Radio.vue.d.ts +27 -0
  36. package/dist/components/Radio/RadioGroup.vue.d.ts +40 -0
  37. package/dist/components/Select/Select.vue.d.ts +37 -0
  38. package/dist/components/Sheet/Sheet.vue.d.ts +35 -0
  39. package/dist/components/Skeleton/Skeleton.vue.d.ts +8 -0
  40. package/dist/components/Spinner/Spinner.vue.d.ts +6 -0
  41. package/dist/components/Switch/Switch.vue.d.ts +26 -0
  42. package/dist/components/Table/Cell.vue.d.ts +19 -0
  43. package/dist/components/Table/Header.vue.d.ts +29 -0
  44. package/dist/components/Table/Row.vue.d.ts +16 -0
  45. package/dist/components/Table/SelectAll.vue.d.ts +2 -0
  46. package/dist/components/Table/SelectRow.vue.d.ts +6 -0
  47. package/dist/components/Table/Table.vue.d.ts +40 -0
  48. package/dist/components/Table/table.d.ts +68 -0
  49. package/dist/components/Tabs/Tab.vue.d.ts +8 -0
  50. package/dist/components/Tabs/Tabs.vue.d.ts +43 -0
  51. package/dist/components/Toast/Toasts.vue.d.ts +2 -0
  52. package/dist/components/Toast/toast.d.ts +42 -0
  53. package/dist/components/Tooltip/Tooltip.vue.d.ts +32 -0
  54. package/dist/index.d.ts +54 -1
  55. package/dist/internal/Backdrop/Backdrop.vue.d.ts +20 -0
  56. package/dist/shared/composables.d.ts +3 -0
  57. package/dist/shared/helpers.d.ts +16 -0
  58. package/dist/shared/types.d.ts +10 -0
  59. package/dist/style.css +1 -1
  60. package/package.json +11 -9
  61. package/src/App.vue +158 -0
  62. package/src/components/Accordion/Accordion.vue +75 -0
  63. package/src/components/Accordion/AccordionGroup.vue +43 -0
  64. package/src/components/Accordion/accordion.scss +44 -0
  65. package/src/components/Alert/Alert.vue +53 -0
  66. package/src/components/Alert/alert.scss +80 -0
  67. package/src/components/Avatar/Avatar.vue +36 -0
  68. package/src/components/Avatar/avatar.scss +46 -0
  69. package/src/components/Badge/Badge.vue +21 -0
  70. package/src/components/Badge/badge.scss +89 -0
  71. package/src/components/Breadcrumbs/BreadcrumbItem.vue +26 -0
  72. package/src/components/Breadcrumbs/Breadcrumbs.vue +33 -0
  73. package/src/components/Breadcrumbs/breadcrumbs.scss +30 -0
  74. package/src/components/Button/Button.vue +90 -0
  75. package/src/components/Button/button.scss +176 -0
  76. package/src/components/ButtonGroup/ButtonGroup.vue +25 -0
  77. package/src/components/ButtonGroup/button-group.scss +51 -0
  78. package/src/components/Calendar/Calendar.vue +58 -0
  79. package/src/components/Calendar/calendar.scss +56 -0
  80. package/src/components/Card/Card.vue +48 -0
  81. package/src/components/Card/card.scss +53 -0
  82. package/src/components/Checkbox/Checkbox.vue +49 -0
  83. package/src/components/Checkbox/checkbox.scss +60 -0
  84. package/src/components/CopyClipboard/CopyClipboard.vue +82 -0
  85. package/src/components/CopyClipboard/copy-clipboard.scss +17 -0
  86. package/src/components/Divider/Divider.vue +34 -0
  87. package/src/components/Divider/divider.scss +35 -0
  88. package/src/components/Drawer/Drawer.vue +93 -0
  89. package/src/components/Drawer/drawer.scss +49 -0
  90. package/src/components/Dropdown/Dropdown.vue +100 -0
  91. package/src/components/Dropdown/DropdownItem.vue +29 -0
  92. package/src/components/Dropdown/DropdownTitle.vue +8 -0
  93. package/src/components/Dropdown/dropdown.scss +112 -0
  94. package/src/components/Flex/Flex.vue +109 -0
  95. package/src/components/Grid/Grid.vue +59 -0
  96. package/src/components/Input/Counter.vue +70 -0
  97. package/src/components/Input/Dropzone.vue +63 -0
  98. package/src/components/Input/File.vue +15 -0
  99. package/src/components/Input/Input.vue +118 -0
  100. package/src/components/Input/Password.vue +47 -0
  101. package/src/components/Input/Textarea.vue +73 -0
  102. package/src/components/Input/input.scss +199 -0
  103. package/src/components/Kbd/Kbd.vue +48 -0
  104. package/src/components/Kbd/KbdGroup.vue +31 -0
  105. package/src/components/Kbd/kbd.scss +18 -0
  106. package/src/components/Modal/Confirm.vue +56 -0
  107. package/src/components/Modal/Modal.vue +91 -0
  108. package/src/components/Modal/modal.scss +49 -0
  109. package/src/components/Pagination/Pagination.vue +74 -0
  110. package/src/components/Pagination/pagination.ts +78 -0
  111. package/src/components/Popout/Popout.vue +39 -0
  112. package/src/components/Popout/popout.scss +7 -0
  113. package/src/components/Progress/Progress.vue +84 -0
  114. package/src/components/Progress/progress.scss +41 -0
  115. package/src/components/Radio/Radio.vue +36 -0
  116. package/src/components/Radio/RadioGroup.vue +35 -0
  117. package/src/components/Radio/radio.scss +59 -0
  118. package/src/components/Select/Select.vue +180 -0
  119. package/src/components/Select/select.scss +43 -0
  120. package/src/components/Sheet/Sheet.vue +91 -0
  121. package/src/components/Sheet/sheet.scss +56 -0
  122. package/src/components/Skeleton/Skeleton.vue +46 -0
  123. package/src/components/Skeleton/skeleton.scss +14 -0
  124. package/src/components/Spinner/Spinner.vue +44 -0
  125. package/src/components/Spinner/spinner.scss +46 -0
  126. package/src/components/Switch/Switch.vue +30 -0
  127. package/src/components/Switch/switch.scss +52 -0
  128. package/src/components/Table/Cell.vue +23 -0
  129. package/src/components/Table/Header.vue +59 -0
  130. package/src/components/Table/Row.vue +9 -0
  131. package/src/components/Table/SelectAll.vue +23 -0
  132. package/src/components/Table/SelectRow.vue +29 -0
  133. package/src/components/Table/Table.vue +66 -0
  134. package/src/components/Table/table.scss +134 -0
  135. package/src/components/Table/table.ts +243 -0
  136. package/src/components/Tabs/Tab.vue +21 -0
  137. package/src/components/Tabs/Tabs.vue +76 -0
  138. package/src/components/Tabs/tabs.scss +78 -0
  139. package/src/components/Toast/Toasts.vue +47 -0
  140. package/src/components/Toast/toast.scss +41 -0
  141. package/src/components/Toast/toast.ts +92 -0
  142. package/src/components/Tooltip/Tooltip.vue +80 -0
  143. package/src/components/Tooltip/tooltip.scss +4 -0
  144. package/src/index.scss +1 -0
  145. package/src/index.ts +111 -0
  146. package/src/internal/Backdrop/Backdrop.vue +22 -0
  147. package/src/internal/Backdrop/backdrop.scss +28 -0
  148. package/src/main.ts +5 -0
  149. package/src/shared/composables.ts +18 -0
  150. package/src/shared/helpers.ts +53 -0
  151. package/src/shared/types.ts +11 -0
  152. package/src/style/animation.scss +21 -0
  153. package/src/style/core.scss +128 -0
  154. package/src/style/fonts.scss +0 -0
  155. package/src/style/layout.scss +9 -0
  156. package/src/style/media-query.scss +29 -0
  157. package/src/style/reset.scss +135 -0
  158. package/src/style/tooltip.scss +128 -0
  159. package/src/style/typography.scss +339 -0
  160. package/src/style/utils.scss +22 -0
  161. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,23 @@
1
+ <script setup lang='ts'>
2
+ import type { TableSelectionProvide } from './table'
3
+ import { inject } from 'vue'
4
+ import Button from '../Button/Button.vue'
5
+ import { TableSelectionProvideSymbol } from './table'
6
+
7
+ const {
8
+ isSelectedAll,
9
+ enabled,
10
+ selectAllRows,
11
+ } = inject(TableSelectionProvideSymbol) as TableSelectionProvide
12
+ </script>
13
+
14
+ <template>
15
+ <th v-if="enabled" class="vui-table-interactive-cell" :class="{ selected: isSelectedAll }">
16
+ <Button
17
+ square
18
+ plain
19
+ :icon="isSelectedAll ? 'ph:check-square-fill' : 'ph:square'"
20
+ @click="selectAllRows()"
21
+ />
22
+ </th>
23
+ </template>
@@ -0,0 +1,29 @@
1
+ <script setup lang='ts'>
2
+ import type { BaseRow, TableSelectionProvide } from './table'
3
+ import { computed, inject } from 'vue'
4
+ import Button from '../Button/Button.vue'
5
+ import { TableSelectionProvideSymbol } from './table'
6
+
7
+ interface Props {
8
+ row: BaseRow
9
+ }
10
+
11
+ const props = defineProps<Props>()
12
+ const {
13
+ enabled,
14
+ selectRow,
15
+ selectedRows,
16
+ } = inject(TableSelectionProvideSymbol) as TableSelectionProvide
17
+ const isSelected = computed(() => selectedRows.value.has(props.row))
18
+ </script>
19
+
20
+ <template>
21
+ <td v-if="enabled" class="vui-table-interactive-cell" :class="{ selected: isSelected }">
22
+ <Button
23
+ square
24
+ plain
25
+ :icon="isSelected ? 'ph:check-square-fill' : 'ph:square'"
26
+ @click="selectRow(props.row)"
27
+ />
28
+ </td>
29
+ </template>
@@ -0,0 +1,66 @@
1
+ <script setup lang='ts'>
2
+ import type { TableSelectionProvide } from './table'
3
+ import { inject } from 'vue'
4
+ import { TableSelectionProvideSymbol } from './table'
5
+ import './table.scss'
6
+
7
+ interface Props {
8
+ /**
9
+ * Sets the `table-layout` property
10
+ */
11
+ fixed?: boolean
12
+ /**
13
+ * Table cells with content overflowing on new line will be cropped
14
+ */
15
+ nowrap?: boolean
16
+ /**
17
+ * Add a visual separator between each row
18
+ */
19
+ separateRows?: boolean
20
+ /**
21
+ * Add a visual separator between each cell
22
+ */
23
+ separateCells?: boolean
24
+ /**
25
+ * Wrap table with a border
26
+ */
27
+ outerBorder?: boolean
28
+ }
29
+
30
+ const {
31
+ fixed,
32
+ nowrap,
33
+ separateRows = true,
34
+ separateCells = false,
35
+ outerBorder = true,
36
+ } = defineProps<Props>()
37
+ const selecting = inject(TableSelectionProvideSymbol) as TableSelectionProvide
38
+ </script>
39
+
40
+ <template>
41
+ <div
42
+ class="vui-table-container"
43
+ :class="{
44
+ fixed,
45
+ nowrap,
46
+ 'selecting': selecting.enabled,
47
+ 'separated-rows': separateRows,
48
+ 'separated-cells': separateCells,
49
+ 'outer-border': outerBorder,
50
+ }"
51
+ >
52
+ <table>
53
+ <thead>
54
+ <tr>
55
+ <slot name="header" />
56
+ </tr>
57
+ </thead>
58
+ <tbody>
59
+ <slot name="body" />
60
+ </tbody>
61
+ </table>
62
+ <div class="vui-table-pagination-wrap">
63
+ <slot name="pagination" />
64
+ </div>
65
+ </div>
66
+ </template>
@@ -0,0 +1,134 @@
1
+ .vui-table-container {
2
+ display: block;
3
+ width: 100%;
4
+ border-radius: var(--border-radius-m);
5
+
6
+ &.fixed table {
7
+ table-layout: fixed;
8
+ }
9
+
10
+ &.outer-border {
11
+ border: 1px solid var(--color-border);
12
+ }
13
+
14
+ &.nowrap table tr {
15
+ td {
16
+ white-space: nowrap;
17
+ overflow: hidden;
18
+ text-overflow: ellipsis;
19
+ }
20
+ }
21
+
22
+ &.separated-rows table {
23
+ th,
24
+ td {
25
+ border-bottom: 1px solid var(--color-border);
26
+ }
27
+ }
28
+
29
+ &.separated-cells table {
30
+ th,
31
+ td {
32
+ border-right: 1px solid var(--color-border);
33
+
34
+ &:last-of-type {
35
+ border-right: none;
36
+ }
37
+ }
38
+ }
39
+
40
+ table {
41
+ width: 100%;
42
+ border-collapse: collapse;
43
+ text-indent: 0;
44
+ border: none;
45
+ overflow: unset;
46
+
47
+ tr {
48
+ border: none;
49
+
50
+ &:has(.vui-table-interactive-cell.selected) {
51
+ td {
52
+ background-color: var(--color-bg-raised);
53
+ }
54
+ }
55
+ }
56
+
57
+ th,
58
+ td {
59
+ font-size: var(--font-size-ms);
60
+ border: none;
61
+ border-left: none !important;
62
+ transition: var(--transition-quick);
63
+ position: relative;
64
+ z-index: 1;
65
+
66
+ // Fixed width, only houses a checkbox component
67
+ &.vui-table-interactive-cell {
68
+ --checkbox-cell-width: calc(calc(var(--space-xs) * 2) + 32px);
69
+ width: var(--checkbox-cell-width);
70
+ min-width: var(--checkbox-cell-width);
71
+ max-width: var(--checkbox-cell-width);
72
+ height: auto;
73
+ padding-inline: var(--space-xs);
74
+ padding-block: 0;
75
+
76
+ &.selected .vui-button.icon .vui-button-slot svg path {
77
+ color: var(--color-text) !important;
78
+ }
79
+
80
+ .vui-button {
81
+ vertical-align: middle;
82
+ display: inline-flex;
83
+
84
+ &.icon .vui-button-slot svg {
85
+ width: 22px !important;
86
+ height: 22px !important;
87
+
88
+ path {
89
+ color: var(--color-text-light);
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ .vui-cell-context {
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ z-index: 2;
100
+ position: absolute;
101
+ inset: 0;
102
+ padding: var(--space-xs);
103
+ background-color: var(--color-bg);
104
+ left: unset;
105
+ }
106
+ }
107
+
108
+ th {
109
+ .vui-table-th-content {
110
+ color: var(--color-text-light);
111
+ font-size: var(--font-size-xs);
112
+ text-transform: uppercase;
113
+ font-weight: 500;
114
+ display: flex;
115
+ justify-content: flex-start;
116
+ align-items: center;
117
+ gap: var(--space-xs);
118
+
119
+ .vui-table-sort-button:not(.active) svg path {
120
+ color: var(--color-text-lighter);
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ .vui-table-pagination-wrap {
127
+ padding: var(--space-s) var(--space-m);
128
+
129
+ p {
130
+ font-size: var(--font-size-s);
131
+ color: var(--color-text-lighter);
132
+ }
133
+ }
134
+ }
@@ -0,0 +1,243 @@
1
+ import type { ComputedRef, InjectionKey, MaybeRefOrGetter, Ref } from 'vue'
2
+ import type { DeepRequired } from '../../shared/types'
3
+ import { computed, provide, readonly, ref, toValue } from 'vue'
4
+ import { searchString } from '../../shared/helpers'
5
+ import { paginate } from '../Pagination/pagination'
6
+
7
+ export type BaseRow = Record<string, string | number>
8
+
9
+ export interface TableSelectionProvide {
10
+ selectedRows: Ref<Set<BaseRow>>
11
+ selectRow: (row: BaseRow) => void
12
+ selectAllRows: () => void
13
+ enabled: ComputedRef<boolean>
14
+ isSelectedAll: ComputedRef<boolean>
15
+ }
16
+
17
+ export const TableSelectionProvideSymbol = Symbol('select-row-provide') as InjectionKey<TableSelectionProvide>
18
+
19
+ interface Sorting<K> {
20
+ key?: K
21
+ type: 'asc' | 'desc'
22
+ }
23
+
24
+ export interface Header {
25
+ label: string
26
+ sortToggle: () => void
27
+ sortKey?: 'asc' | 'desc'
28
+
29
+ }
30
+
31
+ interface TableOptionsInput {
32
+ pagination?: {
33
+ enabled?: boolean
34
+ perPage?: number
35
+ maxPages?: number
36
+ }
37
+ select?: boolean
38
+ }
39
+
40
+ // eslint-disable-next-line ts/explicit-function-return-type
41
+ export function defineTable<const Dataset extends Array<BaseRow>>(
42
+ computedDataset: MaybeRefOrGetter<Dataset>,
43
+ tableOptions?: TableOptionsInput,
44
+ ) {
45
+ const $data = computed(() => toValue(computedDataset))
46
+
47
+ //
48
+ // Reactive options + defaults
49
+ const options = ref(Object.assign({
50
+ pagination: {
51
+ enabled: false,
52
+ perPage: 10,
53
+ maxPages: 3,
54
+ },
55
+ select: false,
56
+ }, tableOptions) as DeepRequired<TableOptionsInput>)
57
+
58
+ //
59
+ // Pagination
60
+ const currentPage = ref(1)
61
+
62
+ const pagination = computed(() => paginate(
63
+ $data.value.length,
64
+ currentPage.value,
65
+ options.value.pagination?.perPage,
66
+ options.value.pagination?.maxPages,
67
+ ))
68
+
69
+ const canNextPage = computed(() => pagination.value.currentPage < pagination.value.endPage)
70
+ const canPrevPage = computed(() => pagination.value.currentPage > pagination.value.startPage)
71
+
72
+ /**
73
+ * Sets the currently active data page. Please note that you provide the page
74
+ * number, no its index. So this is 1-indexed input
75
+ *
76
+ * @param page Page number
77
+ */
78
+ const setPage = (page: number): void => {
79
+ if ((page > currentPage.value && canNextPage.value)
80
+ || (page < currentPage.value && canPrevPage.value)) {
81
+ currentPage.value = page
82
+ }
83
+ }
84
+
85
+ //
86
+ // Sorting
87
+
88
+ const sorting = ref<Sorting<Ref<keyof Dataset[number]>>>({
89
+ key: undefined,
90
+ type: 'asc',
91
+ })
92
+
93
+ const setSort = (key: keyof Dataset[number], type: 'asc' | 'desc' | 'toggle' = 'asc'): void => {
94
+ sorting.value.key = key
95
+ sorting.value.type = type === 'toggle'
96
+ // Toggle between descending & ascending whenever the set sort fn is called
97
+ ? sorting.value.type === 'asc' ? 'desc' : 'asc'
98
+ : type
99
+ }
100
+
101
+ const clearSort = (): void => {
102
+ sorting.value.key = undefined
103
+ sorting.value.type = 'asc'
104
+ }
105
+
106
+ //
107
+ // Searching
108
+ const search = ref<string>()
109
+
110
+ //
111
+ // Dataset formatting
112
+ const filteredRows = computed(() => {
113
+ const searchValue = search.value
114
+ let final = $data.value
115
+
116
+ if (searchValue) {
117
+ final = final.filter((row: Dataset[number]) => {
118
+ const matches = Object
119
+ .values(row)
120
+ .map(row => `${row}`)
121
+ return searchString(matches, searchValue)
122
+ }) as Dataset
123
+ }
124
+
125
+ const key = sorting.value.key
126
+
127
+ if (key) {
128
+ final = final.toSorted((a: Dataset[number], b: Dataset[number]) => {
129
+ const aValue = a[key]
130
+ const bValue = b[key]
131
+ return sorting.value.type === 'asc'
132
+ ? aValue > bValue ? 1 : -1
133
+ : aValue > bValue ? -1 : 1
134
+ }) as Dataset
135
+ }
136
+
137
+ return final
138
+ })
139
+
140
+ const headers = computed(() => Object
141
+ .keys($data.value[0])
142
+ .map((key) => {
143
+ return {
144
+ label: key,
145
+ sortKey: sorting.value.key === key && sorting.value.type,
146
+ sortToggle: () => {
147
+ // 3-way toggle asc -> desc -> turn off (reset to undefined)
148
+ if (sorting.value.key === key) {
149
+ switch (sorting.value.type) {
150
+ case 'asc': {
151
+ sorting.value.type = 'desc'
152
+ break
153
+ }
154
+ case 'desc': {
155
+ sorting.value.key = undefined
156
+ sorting.value.key = 'asc'
157
+ break
158
+ }
159
+ default: {
160
+ sorting.value.key = key
161
+ sorting.value.type = 'asc'
162
+ break
163
+ }
164
+ }
165
+ }
166
+ else {
167
+ setSort(key)
168
+ }
169
+ },
170
+ } as Header
171
+ }),
172
+ )
173
+
174
+ const rows = computed(() => {
175
+ if (options.value.pagination?.enabled === true) {
176
+ return filteredRows.value.slice(
177
+ pagination.value.startIndex,
178
+ pagination.value.endIndex + 1,
179
+ ) as Dataset
180
+ }
181
+ return filteredRows.value as Dataset
182
+ })
183
+
184
+ //
185
+ // Row selecting
186
+ const selectedRows = ref<Set<BaseRow>>(new Set() as Set<BaseRow>)
187
+ const selectingEnabled = computed(() => options.value.select)
188
+
189
+ /**
190
+ * Accepts either an existing index of a row within the dataset or the dataset
191
+ * row itself. If the item is already selected, it will be deselected.
192
+ *
193
+ * @param row {Number | RowObject}
194
+ */
195
+ function selectRow(row: Dataset[number]): void {
196
+ if (selectedRows.value.has(row)) {
197
+ selectedRows.value.delete(row)
198
+ }
199
+ else {
200
+ selectedRows.value.add(row)
201
+ }
202
+ }
203
+
204
+ const isSelectedAll = computed(() => $data.value.length === selectedRows.value.size)
205
+
206
+ function selectAllRows(): void {
207
+ if (isSelectedAll.value) {
208
+ // If the selected indexes have the same length as the data array, we can
209
+ // assume all of them are selected. Therefore we toggle it by deselecting
210
+ // all of them
211
+ selectedRows.value = new Set()
212
+ }
213
+ else {
214
+ selectedRows.value = new Set($data.value.map(row => row))
215
+ }
216
+ }
217
+
218
+ provide(TableSelectionProvideSymbol, {
219
+ selectedRows,
220
+ selectRow,
221
+ selectAllRows,
222
+ enabled: selectingEnabled,
223
+ isSelectedAll,
224
+ })
225
+
226
+ return {
227
+ setSort,
228
+ clearSort,
229
+ search,
230
+ rows: readonly(rows),
231
+ allRows: readonly(filteredRows),
232
+ selectedRows: readonly(selectedRows),
233
+ headers: readonly(headers),
234
+ pagination,
235
+ canPrevPage,
236
+ canNextPage,
237
+ setPage,
238
+ options,
239
+ selectRow,
240
+ selectAllRows,
241
+ isSelectedAll,
242
+ }
243
+ }
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import { Icon } from '@iconify/vue'
3
+ import { computed } from 'vue'
4
+
5
+ export interface TabProps {
6
+ disabled?: boolean
7
+ id?: string
8
+ label: string
9
+ icon?: string
10
+ }
11
+
12
+ const props = defineProps<TabProps>()
13
+ const id = computed(() => props.id ?? props.label)
14
+ </script>
15
+
16
+ <template>
17
+ <button class="vui-tab" :data-tab-id="id" :class="{ disabled: props.disabled }">
18
+ <Icon v-if="props.icon" :icon="props.icon" />
19
+ {{ props.label }}
20
+ </button>
21
+ </template>
@@ -0,0 +1,76 @@
1
+ <script setup lang="ts">
2
+ import type { TabProps } from './Tab.vue'
3
+ import { useEventListener } from '@vueuse/core'
4
+ import { onMounted, useTemplateRef, type VNode, watch } from 'vue'
5
+ import './tabs.scss'
6
+
7
+ interface Props {
8
+ variant?: 'default' | 'filled'
9
+ expand?: boolean
10
+ disabled?: boolean
11
+ }
12
+
13
+ const {
14
+ expand,
15
+ disabled,
16
+ variant = 'default',
17
+ } = defineProps<Props>()
18
+
19
+ const slots = defineSlots<{
20
+ default: () => Array<VNode & { props: TabProps }>
21
+ }>()
22
+
23
+ const active = defineModel()
24
+
25
+ // Underline calculation
26
+ const underline = useTemplateRef('underline')
27
+ const tabs = useTemplateRef('tabs')
28
+
29
+ function computeUnderlinePosition() {
30
+ if (tabs.value && underline.value) {
31
+ const activeBounds = tabs.value.querySelector('.vui-tab.active')?.getBoundingClientRect()
32
+ const parentBounds = tabs.value.getBoundingClientRect()
33
+ if (!activeBounds || !parentBounds)
34
+ return
35
+
36
+ underline.value.style.width = `${activeBounds.width}px`
37
+ underline.value.style.left = `${activeBounds.left - parentBounds.left}px`
38
+ }
39
+ }
40
+
41
+ onMounted(() => {
42
+ watch(
43
+ [active, () => expand],
44
+ computeUnderlinePosition,
45
+ {
46
+ immediate: true,
47
+ flush: 'post',
48
+ },
49
+ )
50
+ })
51
+
52
+ useEventListener(window, 'resize', computeUnderlinePosition)
53
+ </script>
54
+
55
+ <template>
56
+ <div
57
+ ref="tabs"
58
+ class="vui-tabs"
59
+ :class="[
60
+ { expand, disabled },
61
+ variant === 'default'
62
+ ? ''
63
+ : `vui-tabs-variant-${variant}`,
64
+ ]"
65
+ >
66
+ <Component
67
+ :is="vnode"
68
+ v-for="vnode of slots.default()"
69
+ :key="vnode.props.id"
70
+ :class="{ active: vnode.props.id === active }"
71
+ @click="active = vnode.props.id"
72
+ />
73
+
74
+ <div ref="underline" class="vui-tab-underline" />
75
+ </div>
76
+ </template>
@@ -0,0 +1,78 @@
1
+ .vui-tabs {
2
+ display: flex;
3
+ width: 100%;
4
+ gap: 4px;
5
+ border-bottom: 1px solid var(--color-border);
6
+ position: relative;
7
+
8
+ &.vui-tabs-variant-filled {
9
+ background-color: var(--color-bg-raised);
10
+ border-bottom: none;
11
+ z-index: 1;
12
+ border-radius: var(--border-radius-m);
13
+ padding-inline: 4px;
14
+
15
+ .vui-tab-underline {
16
+ border-bottom: none;
17
+ background-color: var(--color-bg-lowered);
18
+ top: 4px;
19
+ bottom: 4px;
20
+ z-index: -1;
21
+ border-radius: var(--border-radius-m);
22
+ }
23
+ }
24
+
25
+ &.disabled .vui-tab,
26
+ .vui-tab.disabled {
27
+ pointer-events: none;
28
+ color: var(--color-text-lighter);
29
+
30
+ &.active {
31
+ color: var(--color-text-lighter) !important;
32
+ }
33
+ }
34
+
35
+ &.disabled {
36
+ .vui-tab-underline {
37
+ border-color: var(--color-text-lighter);
38
+ }
39
+ }
40
+
41
+ &.expand .vui-tab {
42
+ flex: 1;
43
+ }
44
+
45
+ .vui-tab {
46
+ display: flex;
47
+ height: 40px;
48
+ // place-content: center;
49
+ align-items: center;
50
+ justify-content: center;
51
+ padding: 0 10px;
52
+ font-size: var(--font-size-ms);
53
+ position: relative;
54
+ color: var(--color-text-light);
55
+ transition: var(--transition);
56
+ cursor: default;
57
+ gap: var(--space-xs);
58
+ border-radius: var(--border-radius-m);
59
+
60
+ svg {
61
+ width: 20px;
62
+ height: 20px;
63
+ }
64
+
65
+ &:hover,
66
+ &.active {
67
+ color: var(--color-text);
68
+ }
69
+ }
70
+
71
+ .vui-tab-underline {
72
+ transition: var(--transition);
73
+ display: block;
74
+ border-bottom: 1px solid var(--color-text);
75
+ position: absolute;
76
+ bottom: 0;
77
+ }
78
+ }