@xy-planning-network/trees 0.4.0-rc-7 → 0.4.2

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 (61) hide show
  1. package/README.md +236 -53
  2. package/dist/trees.es.js +1069 -330
  3. package/dist/trees.umd.js +6 -6
  4. package/package.json +6 -4
  5. package/src/lib-components/forms/BaseInput.vue +83 -0
  6. package/src/lib-components/forms/Checkbox.vue +46 -0
  7. package/src/lib-components/forms/DateRangePicker.vue +65 -0
  8. package/src/lib-components/forms/InputHelp.vue +24 -0
  9. package/src/lib-components/forms/InputLabel.vue +23 -0
  10. package/src/lib-components/forms/MultiCheckboxes.vue +55 -0
  11. package/src/lib-components/forms/Radio.vue +58 -0
  12. package/src/lib-components/forms/Select.vue +65 -0
  13. package/src/lib-components/forms/TextArea.vue +50 -0
  14. package/src/lib-components/forms/Toggle.vue +25 -0
  15. package/src/lib-components/forms/YesOrNoRadio.vue +70 -0
  16. package/src/lib-components/layout/DateFilter.vue +54 -0
  17. package/src/lib-components/layout/SidebarLayout.vue +239 -0
  18. package/src/lib-components/layout/StackedLayout.vue +172 -0
  19. package/src/lib-components/lists/Cards.vue +33 -0
  20. package/src/lib-components/lists/DetailList.vue +114 -0
  21. package/src/lib-components/lists/DownloadCell.vue +12 -0
  22. package/src/lib-components/lists/StaticTable.vue +83 -0
  23. package/src/lib-components/lists/Table.vue +291 -0
  24. package/src/lib-components/navigation/ActionsDropdown.vue +78 -0
  25. package/src/lib-components/navigation/Paginator.vue +111 -0
  26. package/src/lib-components/navigation/Steps.vue +83 -0
  27. package/src/lib-components/navigation/Tabs.vue +92 -0
  28. package/src/lib-components/overlays/ContentModal.vue +95 -0
  29. package/src/lib-components/overlays/Flash.vue +131 -0
  30. package/src/lib-components/overlays/Modal.vue +133 -0
  31. package/src/lib-components/overlays/Popover/Popover.vue +229 -0
  32. package/src/lib-components/overlays/Popover/PopoverContent.vue +8 -0
  33. package/src/lib-components/overlays/Slideover.vue +87 -0
  34. package/src/lib-components/overlays/Spinner.vue +149 -0
  35. package/src/lib-components/overlays/Tooltip.vue +34 -0
  36. package/types/components.d.ts +6 -2
  37. package/types/composables/date.d.ts +4 -0
  38. package/types/composables/nav.d.ts +13 -0
  39. package/types/composables/overlay.d.ts +4 -0
  40. package/types/composables/table.d.ts +32 -0
  41. package/types/composables/user.d.ts +6 -0
  42. package/types/global.d.ts +5 -2
  43. package/types/helpers/Debounce.d.ts +1 -0
  44. package/types/helpers/Throttle.d.ts +1 -0
  45. package/types/lib-components/forms/Select.vue.d.ts +2 -2
  46. package/types/lib-components/index.d.ts +9 -9
  47. package/types/lib-components/layout/DateFilter.vue.d.ts +1 -4
  48. package/types/lib-components/layout/SidebarLayout.vue.d.ts +1 -1
  49. package/types/lib-components/layout/StackedLayout.vue.d.ts +2 -2
  50. package/types/lib-components/lists/StaticTable.vue.d.ts +1 -1
  51. package/types/lib-components/lists/Table.vue.d.ts +1 -1
  52. package/types/lib-components/navigation/ActionsDropdown.vue.d.ts +2 -2
  53. package/types/lib-components/navigation/Paginator.vue.d.ts +1 -6
  54. package/types/lib-components/overlays/Flash.vue.d.ts +0 -4
  55. package/types/lib-components/overlays/Popover/Popover.vue.d.ts +23 -0
  56. package/types/lib-components/overlays/Popover/PopoverContent.vue.d.ts +2 -0
  57. package/types/lib-components/overlays/Tooltip.vue.d.ts +23 -0
  58. package/types/index.d.ts +0 -3
  59. package/types/nav.d.ts +0 -8
  60. package/types/table.d.ts +0 -36
  61. package/types/users.d.ts +0 -10
@@ -0,0 +1,291 @@
1
+ <script setup lang="ts">
2
+ import { AxiosResponse } from "axios"
3
+ import {
4
+ ComponentPublicInstance,
5
+ computed,
6
+ getCurrentInstance,
7
+ ref,
8
+ watch,
9
+ } from "vue"
10
+ import DateRangePicker from "../forms/DateRangePicker.vue"
11
+ import Paginator from "../navigation/Paginator.vue"
12
+ import BaseAPI from "../../api/base"
13
+ import * as TableTypes from "@/composables/table"
14
+
15
+ const props = withDefaults(
16
+ defineProps<{
17
+ clickable?: boolean
18
+ loader?: boolean
19
+ tableData: TableTypes.Dynamic
20
+ }>(),
21
+ {
22
+ clickable: false,
23
+ loader: true,
24
+ }
25
+ )
26
+
27
+ const currentSort = ref(
28
+ props.tableData.defaultSort ? props.tableData.defaultSort : ""
29
+ )
30
+ const currentSortDirection = ref(
31
+ props.tableData.defaultSortDirection
32
+ ? props.tableData.defaultSortDirection
33
+ : "desc"
34
+ )
35
+ const dateRange = ref({
36
+ minDate: 0,
37
+ maxDate: 0,
38
+ })
39
+ const items = ref<any[]>([])
40
+ const pagination = ref({
41
+ page: 1,
42
+ perPage: 10,
43
+ totalItems: 0,
44
+ totalPages: 0,
45
+ })
46
+ const query = ref("")
47
+ const cellValue = (
48
+ item: Record<string, any>,
49
+ col: TableTypes.Column
50
+ ): string => {
51
+ if (col.key) {
52
+ // NOTE(dlk): supports dot notation for nested keys
53
+ return col.key.split(".").reduce((o, i) => o[i], item as any)
54
+ }
55
+
56
+ if (col.presenter) {
57
+ // TODO: discuss this pattern. Current usage can be replaced with modules.
58
+ // https://v3.vuejs.org/api/composition-api.html#getcurrentinstance
59
+ const internalInstance = getCurrentInstance()
60
+ return col.presenter(
61
+ item,
62
+ internalInstance?.proxy as ComponentPublicInstance
63
+ )
64
+ }
65
+
66
+ return ""
67
+ }
68
+ const dateRangeChanged = (newDateRange: {
69
+ minDate: number
70
+ maxDate: number
71
+ }): void => {
72
+ pagination.value.page = 1
73
+ dateRange.value = newDateRange
74
+ loadAndRender()
75
+ }
76
+ const handleSort = (selectedSort: string): void => {
77
+ if (currentSort.value == selectedSort) {
78
+ currentSortDirection.value =
79
+ currentSortDirection.value === "desc" ? "asc" : "desc"
80
+ } else {
81
+ currentSort.value = selectedSort
82
+ currentSortDirection.value = "desc"
83
+ }
84
+
85
+ loadAndRender()
86
+ }
87
+ const loadAndRender = (): void => {
88
+ const params = {
89
+ minDate: dateRange.value.minDate,
90
+ maxDate: dateRange.value.maxDate,
91
+ page: pagination.value.page,
92
+ perPage: pagination.value.perPage,
93
+ sort: currentSort.value,
94
+ sortDir: currentSortDirection.value,
95
+ q: query.value,
96
+ }
97
+
98
+ BaseAPI.get(props.tableData.url, { skipLoader: !props.loader }, params).then(
99
+ (success: AxiosResponse) => {
100
+ pagination.value = {
101
+ page: success.data.page,
102
+ perPage: success.data.perPage,
103
+ totalItems: success.data.totalItems,
104
+ totalPages: success.data.totalPages,
105
+ }
106
+ items.value = success.data.items
107
+ },
108
+ () => {
109
+ window.VueBus.emit(
110
+ "Flash-show-generic-error",
111
+ "membership@xyplanningnetwork.com"
112
+ )
113
+ }
114
+ )
115
+ }
116
+
117
+ const reloadTable = (): void => {
118
+ pagination.value.page = 1
119
+ loadAndRender()
120
+ }
121
+
122
+ const hasContent = computed((): boolean => {
123
+ return items.value.length ? true : false
124
+ })
125
+
126
+ watch(
127
+ () => props.tableData.refreshTrigger,
128
+ () => {
129
+ // This lets parent components trigger a refresh of the current page depending on external actions.
130
+ loadAndRender()
131
+ }
132
+ )
133
+
134
+ watch(
135
+ () => props.tableData.reloadTrigger,
136
+ () => {
137
+ // This lets parent components trigger a reload of page 1 depending on external actions.
138
+ reloadTable()
139
+ }
140
+ )
141
+
142
+ // onCreated
143
+ loadAndRender()
144
+ </script>
145
+ <template>
146
+ <div>
147
+ <div
148
+ class="flex flex-col mb-4 space-y-4 lg:space-y-0 lg:flex-row lg:justify-between"
149
+ >
150
+ <div class="w-full max-w-lg lg:max-w-xs" v-if="tableData.search">
151
+ <label for="search" class="sr-only">Search</label>
152
+ <div class="relative">
153
+ <div
154
+ class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"
155
+ >
156
+ <svg
157
+ class="w-5 h-5 text-gray-400"
158
+ fill="currentColor"
159
+ viewBox="0 0 20 20"
160
+ >
161
+ <path
162
+ fill-rule="evenodd"
163
+ d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
164
+ clip-rule="evenodd"
165
+ />
166
+ </svg>
167
+ </div>
168
+ <input
169
+ class="pl-10"
170
+ type="search"
171
+ v-model.trim="query"
172
+ placeholder="Search"
173
+ @change="reloadTable()"
174
+ />
175
+ </div>
176
+ </div>
177
+ <div class="w-full max-w-lg lg:max-w-xs" v-if="tableData.dateSearch">
178
+ <DateRangePicker
179
+ v-model="dateRange"
180
+ @update:modelValue="dateRangeChanged"
181
+ />
182
+ </div>
183
+ </div>
184
+
185
+ <div
186
+ class="relative z-0 min-w-full align-middle border-b border-gray-200 shadow sm:rounded-lg overflow-x-auto"
187
+ >
188
+ <table class="min-w-full">
189
+ <thead>
190
+ <tr>
191
+ <th
192
+ class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-900 uppercase border-b border-gray-200 bg-gray-50 leading-4"
193
+ v-for="(col, idx) in tableData.columns"
194
+ :key="idx"
195
+ >
196
+ <span v-if="!!col.display.length">{{ col.display }}</span>
197
+ <span
198
+ class="cursor-pointer"
199
+ @click.prevent="handleSort(col.sort as string)"
200
+ v-if="col.sort"
201
+ >
202
+ <svg
203
+ xmlns="http://www.w3.org/2000/svg"
204
+ fill="none"
205
+ viewBox="0 0 24 24"
206
+ stroke="currentColor"
207
+ class="h-5 inline"
208
+ v-if="currentSort !== col.sort"
209
+ >
210
+ <path
211
+ stroke-linecap="round"
212
+ stroke-linejoin="round"
213
+ stroke-width="2"
214
+ d="M8 9l4-4 4 4m0 6l-4 4-4-4"
215
+ />
216
+ </svg>
217
+ <svg
218
+ xmlns="http://www.w3.org/2000/svg"
219
+ viewBox="0 0 20 20"
220
+ fill="currentColor"
221
+ class="h-5 inline"
222
+ v-else-if="currentSortDirection == 'desc'"
223
+ >
224
+ <path
225
+ fill-rule="evenodd"
226
+ d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z"
227
+ clip-rule="evenodd"
228
+ />
229
+ </svg>
230
+ <svg
231
+ xmlns="http://www.w3.org/2000/svg"
232
+ viewBox="0 0 20 20"
233
+ fill="currentColor"
234
+ class="h-5 inline"
235
+ v-else
236
+ >
237
+ <path
238
+ fill-rule="evenodd"
239
+ d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
240
+ clip-rule="evenodd"
241
+ />
242
+ </svg>
243
+ </span>
244
+ </th>
245
+ </tr>
246
+ </thead>
247
+
248
+ <tbody class="bg-white">
249
+ <tr
250
+ v-for="(item, rowIdx) in items"
251
+ :key="item.id ? item.id : rowIdx"
252
+ @click="$emit('handleClick', item)"
253
+ :class="{ 'cursor-pointer': clickable }"
254
+ >
255
+ <td
256
+ class="px-6 py-4 text-sm text-gray-700 whitespace-nowrap border-b border-gray-200 leading-5"
257
+ v-for="(col, colIdx) in tableData.columns"
258
+ :key="rowIdx + '-' + colIdx"
259
+ :class="col.class"
260
+ >
261
+ <component
262
+ :is="col.component"
263
+ v-if="col.component"
264
+ :props-data="item"
265
+ :current-user="tableData.currentUser"
266
+ :attribute="col.key"
267
+ :items="col.items"
268
+ ></component>
269
+ <div v-else v-text="cellValue(item, col)"></div>
270
+ </td>
271
+ </tr>
272
+
273
+ <tr v-if="!hasContent">
274
+ <td
275
+ :colspan="tableData.columns.length"
276
+ class="px-6 py-4 text-sm text-gray-700 whitespace-nowrap border-b border-gray-200 leading-5"
277
+ >
278
+ No items were found!
279
+ </td>
280
+ </tr>
281
+ </tbody>
282
+ </table>
283
+ </div>
284
+
285
+ <Paginator
286
+ v-model="pagination"
287
+ @update:modelValue="loadAndRender()"
288
+ v-if="hasContent"
289
+ />
290
+ </div>
291
+ </template>
@@ -0,0 +1,78 @@
1
+ <script setup lang="ts">
2
+ import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/vue"
3
+ import { DotsVerticalIcon } from "@heroicons/vue/solid"
4
+ import { onMounted, ref } from "vue"
5
+ import * as TableTypes from "@/composables/table"
6
+ import User from "@/composables/user"
7
+
8
+ const props = defineProps<{
9
+ currentUser: User
10
+ items: TableTypes.MenuItem[]
11
+ propsData: any
12
+ }>()
13
+
14
+ const hasActionItems = ref(false)
15
+
16
+ const emitEvent = (event: string): void => {
17
+ window.VueBus.emit(event, props.propsData)
18
+ }
19
+
20
+ const show = (item: TableTypes.MenuItem): boolean => {
21
+ if (!item.show) return true
22
+ return item.show(props.propsData, props.currentUser)
23
+ }
24
+
25
+ onMounted(() => {
26
+ for (let item of props.items) {
27
+ if (!item.show) {
28
+ hasActionItems.value = true
29
+ return
30
+ }
31
+
32
+ const showActionItem = item.show(props.propsData, props.currentUser)
33
+ if (showActionItem) {
34
+ hasActionItems.value = true
35
+ return
36
+ }
37
+ }
38
+ })
39
+ </script>
40
+ <template>
41
+ <Menu as="div" class="relative flex justify-end items-center">
42
+ <MenuButton
43
+ class="w-8 h-8 bg-white inline-flex items-center justify-center text-gray-700 rounded-full hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
44
+ :disabled="!hasActionItems"
45
+ >
46
+ <span class="sr-only">Open options</span>
47
+ <DotsVerticalIcon class="w-5 h-5" aria-hidden="true" />
48
+ </MenuButton>
49
+ <transition
50
+ enter-active-class="transition ease-out duration-100"
51
+ enter-from-class="transform opacity-0 scale-95"
52
+ enter-to-class="transform opacity-100 scale-100"
53
+ leave-active-class="transition ease-in duration-75"
54
+ leave-from-class="transform opacity-100 scale-100"
55
+ leave-to-class="transform opacity-0 scale-95"
56
+ >
57
+ <MenuItems
58
+ class="z-10 mx-3 origin-top-right absolute right-7 top-0 w-48 mt-1 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-200 focus:outline-none"
59
+ >
60
+ <div class="py-1">
61
+ <template v-for="(item, idx) in items" :key="idx">
62
+ <MenuItem as="div" v-slot="{ active }" v-if="show(item)">
63
+ <button
64
+ type="submit"
65
+ :class="[
66
+ active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
67
+ 'block w-full text-left px-4 py-2 text-sm font-semibold',
68
+ ]"
69
+ v-text="item.label"
70
+ @click="emitEvent(item.event)"
71
+ ></button>
72
+ </MenuItem>
73
+ </template>
74
+ </div>
75
+ </MenuItems>
76
+ </transition>
77
+ </Menu>
78
+ </template>
@@ -0,0 +1,111 @@
1
+ <script setup lang="ts">
2
+ import { Pagination } from "@/composables/nav"
3
+ import { computed } from "vue"
4
+
5
+ const props = defineProps<{
6
+ modelValue: Pagination
7
+ }>()
8
+
9
+ const emit = defineEmits<{
10
+ (e: "update:modelValue", pagination: Pagination): void
11
+ }>()
12
+
13
+ const changePage = (page: number): void => {
14
+ emit("update:modelValue", {
15
+ ...props.modelValue,
16
+ page: page,
17
+ })
18
+ }
19
+
20
+ const pageShortcuts = computed((): number[] => {
21
+ const shortcuts: number[] = []
22
+
23
+ // If total pages is less than or equal to 4, just return 1, 2, 3, 4
24
+ if (props.modelValue.totalPages <= 4) {
25
+ for (let i = 0; i < props.modelValue.totalPages; i++) {
26
+ shortcuts.push(i + 1)
27
+ }
28
+ return shortcuts
29
+ }
30
+
31
+ // If there are more than 3 pages left, show these
32
+ // e.g. [4, 5, 6, 7] when there are 8 total pages and the current page is 4
33
+ const pagesLeft: number = props.modelValue.totalPages - props.modelValue.page
34
+ if (pagesLeft >= 3) {
35
+ for (let i = 0; i < 4; i++) {
36
+ shortcuts.push(props.modelValue.page + i)
37
+ }
38
+ return shortcuts
39
+ }
40
+
41
+ // If there are less than 3 pages left, count backwards from the last page
42
+ // e.g. [5, 6, 7, 8] when on page 5, 6, 7, and 8 and there are 8 total pages
43
+ for (let i = 0; i < 4; i++) {
44
+ shortcuts.unshift(props.modelValue.totalPages - i)
45
+ }
46
+ return shortcuts
47
+ })
48
+ </script>
49
+ <template>
50
+ <div class="px-4 flex items-center justify-between sm:px-0">
51
+ <div class="w-0 flex-1 flex">
52
+ <a
53
+ href="#"
54
+ class="-mt-px border-t-2 border-transparent pt-4 pr-1 inline-flex items-center text-sm leading-5 font-medium focus:outline-none focus:text-gray-700 focus:border-gray-400"
55
+ @click.prevent="changePage(modelValue.page - 1)"
56
+ :class="
57
+ modelValue.page == 1
58
+ ? 'text-gray-500 cursor-not-allowed pointer-events-none'
59
+ : 'text-gray-700 hover:text-gray-900 hover:border-gray-300'
60
+ "
61
+ >
62
+ <svg class="mr-3 h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
63
+ <path
64
+ fill-rule="evenodd"
65
+ d="M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z"
66
+ clip-rule="evenodd"
67
+ />
68
+ </svg>
69
+ Previous
70
+ </a>
71
+ </div>
72
+
73
+ <div class="hidden md:flex">
74
+ <a
75
+ href="#"
76
+ class="-mt-px border-t-2 pt-4 px-4 inline-flex items-center text-sm leading-5 font-medium"
77
+ v-for="i in pageShortcuts"
78
+ :key="i"
79
+ v-text="i"
80
+ :class="
81
+ modelValue.page === i
82
+ ? 'border-blue-500 text-blue-600 focus:outline-none focus:text-blue-800 focus:border-blue-700'
83
+ : 'border-transparent text-gray-700 hover:text-gray-900 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-400'
84
+ "
85
+ @click.prevent="changePage(i)"
86
+ ></a>
87
+ </div>
88
+
89
+ <div class="w-0 flex-1 flex justify-end">
90
+ <a
91
+ href="#"
92
+ class="-mt-px border-t-2 border-transparent pt-4 pl-1 inline-flex items-center text-sm leading-5 font-medium focus:outline-none focus:text-gray-700 focus:border-gray-400"
93
+ @click.prevent="changePage(modelValue.page + 1)"
94
+ :class="
95
+ modelValue.page >= modelValue.totalPages
96
+ ? 'text-gray-500 cursor-not-allowed pointer-events-none'
97
+ : 'text-gray-700 hover:text-gray-900 hover:border-gray-300'
98
+ "
99
+ >
100
+ Next
101
+ <svg class="ml-3 h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
102
+ <path
103
+ fill-rule="evenodd"
104
+ d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
105
+ clip-rule="evenodd"
106
+ />
107
+ </svg>
108
+ </a>
109
+ </div>
110
+ </div>
111
+ </template>
@@ -0,0 +1,83 @@
1
+ <script setup lang="ts">
2
+ // TODO: think about whether there is value in updating and emiting step with next/previous
3
+ // TODO: add to docs
4
+
5
+ withDefaults(
6
+ defineProps<{
7
+ hideActions?: boolean
8
+ hidePrevious?: boolean
9
+ nextText?: string
10
+ previousText?: string
11
+ step: number
12
+ total: number
13
+ }>(),
14
+ {
15
+ hideActions: false,
16
+ hidePrevious: false,
17
+ nextText: "Next",
18
+ previousText: "Back",
19
+ }
20
+ )
21
+
22
+ const emit = defineEmits(["next", "previous"])
23
+
24
+ const next = (): void => {
25
+ emit("next")
26
+ }
27
+
28
+ const previous = (): void => {
29
+ emit("previous")
30
+ }
31
+ </script>
32
+ <template>
33
+ <div>
34
+ <nav class="flex items-center justify-center space-x-8">
35
+ <p class="font-medium">Step {{ step }} of {{ total }}</p>
36
+ <ul class="flex items-center space-x-5">
37
+ <li v-for="index in total" :key="index">
38
+ <span
39
+ class="block w-2.5 h-2.5 bg-xy-green rounded-full hover:bg-green-900 focus:bg-green-900"
40
+ v-if="step > index"
41
+ ></span>
42
+
43
+ <div
44
+ class="relative flex items-center justify-center"
45
+ v-else-if="step === index"
46
+ >
47
+ <span class="absolute w-5 h-5 p-px flex">
48
+ <span class="w-full h-full rounded-full bg-green-100"></span>
49
+ </span>
50
+ <span
51
+ class="relative block w-2.5 h-2.5 bg-xy-green rounded-full"
52
+ ></span>
53
+ </div>
54
+
55
+ <span
56
+ href="#"
57
+ class="block w-2.5 h-2.5 bg-gray-200 rounded-full hover:bg-gray-400 focus:bg-gray-400"
58
+ v-else
59
+ ></span>
60
+ </li>
61
+ </ul>
62
+ </nav>
63
+
64
+ <div class="flex flex-shrink-0" v-if="!hideActions">
65
+ <span class="inline-flex rounded-md shadow-sm" v-if="!hidePrevious">
66
+ <button
67
+ type="button"
68
+ class="xy-btn-white"
69
+ @click="previous"
70
+ v-text="previousText"
71
+ ></button>
72
+ </span>
73
+ <span class="ml-3 inline-flex rounded-md shadow-sm">
74
+ <button
75
+ type="button"
76
+ class="xy-btn"
77
+ @click="next"
78
+ v-text="nextText"
79
+ ></button>
80
+ </span>
81
+ </div>
82
+ </div>
83
+ </template>
@@ -0,0 +1,92 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue"
3
+
4
+ const props = withDefaults(
5
+ defineProps<{
6
+ modelValue: string
7
+ pillDesign?: boolean
8
+ tabs: Array<{
9
+ label: string
10
+ value: string
11
+ }>
12
+ }>(),
13
+ {
14
+ pillDesign: false,
15
+ }
16
+ )
17
+
18
+ const emit = defineEmits<{
19
+ (e: "update:modelValue", val: string): void
20
+ }>()
21
+
22
+ const updateModelValue = (modelValue: string): void => {
23
+ emit("update:modelValue", modelValue)
24
+ }
25
+
26
+ const classes = (currentTab: string, pastFirstTab: boolean): string => {
27
+ let c = ""
28
+
29
+ if (props.pillDesign) {
30
+ c =
31
+ "px-12 py-2 font-semibold text-md leading-5 rounded-t-md focus:outline-none "
32
+
33
+ if (props.modelValue === currentTab) {
34
+ c = c + "focus:bg-white text-gray-700 bg-white border-b-2 border-blue-500"
35
+ } else {
36
+ c =
37
+ c +
38
+ "text-gray-700 hover:text-gray-900 focus:text-gray-900 focus:bg-gray-100 border border-gray-200"
39
+ }
40
+
41
+ return c
42
+ }
43
+
44
+ c =
45
+ "px-1 py-4 text-sm font-semibold border-b-2 whitespace-nowrap leading-5 focus:outline-none "
46
+ if (props.modelValue === currentTab) {
47
+ c =
48
+ c +
49
+ "border-blue-500 text-xy-blue focus:text-blue-800 focus:border-blue-700"
50
+ } else {
51
+ c =
52
+ c +
53
+ "border-transparent text-gray-700 hover:text-gray-900 hover:border-gray-300 focus:text-gray-900 focus:border-gray-300"
54
+ }
55
+
56
+ if (pastFirstTab) c = c + " ml-8"
57
+
58
+ return c
59
+ }
60
+
61
+ const notPillDesign = computed((): boolean => {
62
+ return !props.pillDesign
63
+ })
64
+ </script>
65
+ <template>
66
+ <div>
67
+ <div class="sm:hidden" :class="{ 'mb-4': pillDesign }">
68
+ <label for="tabs" class="sr-only">Select a tab</label>
69
+ <Select
70
+ name="tabs"
71
+ :modelValue="modelValue"
72
+ @update:modelValue="updateModelValue($event)"
73
+ :options="tabs"
74
+ />
75
+ </div>
76
+ <div class="hidden sm:block">
77
+ <div :class="{ 'border-b border-gray-200': notPillDesign }">
78
+ <nav class="flex" :class="[pillDesign ? 'ml-8' : '-mb-px']">
79
+ <a
80
+ href="#"
81
+ :class="classes(tab.value, idx > 0)"
82
+ v-for="(tab, idx) in tabs"
83
+ :key="idx"
84
+ v-text="tab.label"
85
+ @click.prevent="updateModelValue(tab.value)"
86
+ >
87
+ </a>
88
+ </nav>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </template>