@xy-planning-network/trees 0.4.0-rc-6 → 0.4.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.
- package/README.md +236 -53
- package/dist/trees.es.js +910 -274
- package/dist/trees.umd.js +6 -6
- package/package.json +9 -6
- package/src/lib-components/forms/BaseInput.vue +83 -0
- package/src/lib-components/forms/Checkbox.vue +46 -0
- package/src/lib-components/forms/DateRangePicker.vue +65 -0
- package/src/lib-components/forms/InputHelp.vue +24 -0
- package/src/lib-components/forms/InputLabel.vue +23 -0
- package/src/lib-components/forms/MultiCheckboxes.vue +55 -0
- package/src/lib-components/forms/Radio.vue +58 -0
- package/src/lib-components/forms/Select.vue +65 -0
- package/src/lib-components/forms/TextArea.vue +50 -0
- package/src/lib-components/forms/Toggle.vue +25 -0
- package/src/lib-components/forms/YesOrNoRadio.vue +70 -0
- package/src/lib-components/layout/DateFilter.vue +54 -0
- package/src/lib-components/layout/SidebarLayout.vue +239 -0
- package/src/lib-components/layout/StackedLayout.vue +172 -0
- package/src/lib-components/lists/Cards.vue +33 -0
- package/src/lib-components/lists/DetailList.vue +114 -0
- package/src/lib-components/lists/DownloadCell.vue +12 -0
- package/src/lib-components/lists/StaticTable.vue +83 -0
- package/src/lib-components/lists/Table.vue +291 -0
- package/src/lib-components/navigation/ActionsDropdown.vue +78 -0
- package/src/lib-components/navigation/Paginator.vue +115 -0
- package/src/lib-components/navigation/Steps.vue +83 -0
- package/src/lib-components/navigation/Tabs.vue +92 -0
- package/src/lib-components/overlays/ContentModal.vue +95 -0
- package/src/lib-components/overlays/Flash.vue +131 -0
- package/src/lib-components/overlays/Modal.vue +133 -0
- package/src/lib-components/overlays/Popover/Popover.vue +109 -0
- package/src/lib-components/overlays/Popover/PopoverContent.vue +8 -0
- package/src/lib-components/overlays/Slideover.vue +87 -0
- package/src/lib-components/overlays/Spinner.vue +149 -0
- package/src/lib-components/overlays/Tooltip.vue +31 -0
- package/{dist → types}/api/base.d.ts +0 -0
- package/types/components.d.ts +12 -0
- package/types/composables/date.d.ts +4 -0
- package/types/composables/nav.d.ts +13 -0
- package/types/composables/overlay.d.ts +4 -0
- package/types/composables/table.d.ts +32 -0
- package/types/composables/user.d.ts +6 -0
- package/{dist → types}/entry.d.ts +0 -0
- package/types/global.d.ts +13 -0
- package/{dist → types}/helpers/Uniques.d.ts +0 -0
- package/{dist → types}/lib-components/forms/BaseInput.vue.d.ts +0 -0
- package/{dist → types}/lib-components/forms/Checkbox.vue.d.ts +0 -0
- package/{dist → types}/lib-components/forms/DateRangePicker.vue.d.ts +0 -0
- package/{dist → types}/lib-components/forms/InputHelp.vue.d.ts +0 -0
- package/{dist → types}/lib-components/forms/InputLabel.vue.d.ts +0 -0
- package/{dist → types}/lib-components/forms/MultiCheckboxes.vue.d.ts +0 -0
- package/{dist → types}/lib-components/forms/Radio.vue.d.ts +0 -0
- package/{dist → types}/lib-components/forms/Select.vue.d.ts +2 -2
- package/{dist → types}/lib-components/forms/TextArea.vue.d.ts +0 -0
- package/{dist → types}/lib-components/forms/Toggle.vue.d.ts +0 -0
- package/{dist → types}/lib-components/forms/YesOrNoRadio.vue.d.ts +0 -0
- package/{dist → types}/lib-components/index.d.ts +9 -9
- package/{dist → types}/lib-components/layout/DateFilter.vue.d.ts +1 -4
- package/{dist → types}/lib-components/layout/SidebarLayout.vue.d.ts +1 -1
- package/{dist → types}/lib-components/layout/StackedLayout.vue.d.ts +2 -2
- package/{dist → types}/lib-components/lists/Cards.vue.d.ts +0 -0
- package/{dist → types}/lib-components/lists/DetailList.vue.d.ts +0 -0
- package/{dist → types}/lib-components/lists/DownloadCell.vue.d.ts +0 -0
- package/{dist → types}/lib-components/lists/StaticTable.vue.d.ts +1 -1
- package/{dist → types}/lib-components/lists/Table.vue.d.ts +1 -1
- package/{dist → types}/lib-components/navigation/ActionsDropdown.vue.d.ts +2 -2
- package/{dist → types}/lib-components/navigation/Paginator.vue.d.ts +1 -6
- package/{dist → types}/lib-components/navigation/Steps.vue.d.ts +0 -0
- package/{dist → types}/lib-components/navigation/Tabs.vue.d.ts +0 -0
- package/{dist → types}/lib-components/overlays/ContentModal.vue.d.ts +0 -0
- package/{dist/lib-components/overlays/Spinner.vue.d.ts → types/lib-components/overlays/Flash.vue.d.ts} +0 -0
- package/{dist → types}/lib-components/overlays/Modal.vue.d.ts +0 -0
- package/types/lib-components/overlays/Popover/Popover.vue.d.ts +15 -0
- package/types/lib-components/overlays/Popover/PopoverContent.vue.d.ts +2 -0
- package/{dist → types}/lib-components/overlays/Slideover.vue.d.ts +0 -0
- package/{dist/lib-components/overlays/Flash.vue.d.ts → types/lib-components/overlays/Spinner.vue.d.ts} +0 -4
- package/types/lib-components/overlays/Tooltip.vue.d.ts +16 -0
- package/dist/types/components.d.ts +0 -6
- package/dist/types/global.d.ts +0 -10
- package/dist/types/nav.d.ts +0 -8
- package/dist/types/table.d.ts +0 -36
- package/dist/types/users.d.ts +0 -10
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
Dialog,
|
|
4
|
+
DialogOverlay,
|
|
5
|
+
DialogTitle,
|
|
6
|
+
TransitionChild,
|
|
7
|
+
TransitionRoot,
|
|
8
|
+
} from "@headlessui/vue"
|
|
9
|
+
|
|
10
|
+
withDefaults(
|
|
11
|
+
defineProps<{
|
|
12
|
+
modelValue: boolean
|
|
13
|
+
title?: string
|
|
14
|
+
}>(),
|
|
15
|
+
{
|
|
16
|
+
title: "",
|
|
17
|
+
}
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
const emit = defineEmits<{
|
|
21
|
+
(e: "update:modelValue", val: boolean): void
|
|
22
|
+
}>()
|
|
23
|
+
|
|
24
|
+
const updateModelValue = (value: boolean) => {
|
|
25
|
+
emit("update:modelValue", value)
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
<template>
|
|
29
|
+
<TransitionRoot as="template" :show="modelValue">
|
|
30
|
+
<Dialog
|
|
31
|
+
as="div"
|
|
32
|
+
static
|
|
33
|
+
class="fixed z-10 inset-0 overflow-y-auto"
|
|
34
|
+
@close="updateModelValue(false)"
|
|
35
|
+
:open="modelValue"
|
|
36
|
+
>
|
|
37
|
+
<div
|
|
38
|
+
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
|
|
39
|
+
>
|
|
40
|
+
<TransitionChild
|
|
41
|
+
as="template"
|
|
42
|
+
enter="ease-out duration-300"
|
|
43
|
+
enter-from="opacity-0"
|
|
44
|
+
enter-to="opacity-100"
|
|
45
|
+
leave="ease-in duration-200"
|
|
46
|
+
leave-from="opacity-100"
|
|
47
|
+
leave-to="opacity-0"
|
|
48
|
+
>
|
|
49
|
+
<DialogOverlay
|
|
50
|
+
class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
|
51
|
+
/>
|
|
52
|
+
</TransitionChild>
|
|
53
|
+
|
|
54
|
+
<!-- This element is to trick the browser into centering the modal contents. -->
|
|
55
|
+
<span
|
|
56
|
+
class="hidden sm:inline-block sm:align-middle sm:h-screen"
|
|
57
|
+
aria-hidden="true"
|
|
58
|
+
>​</span
|
|
59
|
+
>
|
|
60
|
+
<TransitionChild
|
|
61
|
+
as="template"
|
|
62
|
+
enter="ease-out duration-300"
|
|
63
|
+
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
64
|
+
enter-to="opacity-100 translate-y-0 sm:scale-100"
|
|
65
|
+
leave="ease-in duration-200"
|
|
66
|
+
leave-from="opacity-100 translate-y-0 sm:scale-100"
|
|
67
|
+
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
68
|
+
>
|
|
69
|
+
<div
|
|
70
|
+
class="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6"
|
|
71
|
+
>
|
|
72
|
+
<div>
|
|
73
|
+
<slot name="icon"></slot>
|
|
74
|
+
<div class="mt-3 text-center sm:mt-5">
|
|
75
|
+
<DialogTitle as="h3" v-text="title"></DialogTitle>
|
|
76
|
+
<div class="mt-2">
|
|
77
|
+
<slot></slot>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="mt-5 sm:mt-6">
|
|
82
|
+
<button
|
|
83
|
+
type="button"
|
|
84
|
+
class="inline-flex justify-center w-full xy-btn"
|
|
85
|
+
@click="updateModelValue(false)"
|
|
86
|
+
>
|
|
87
|
+
Go back
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</TransitionChild>
|
|
92
|
+
</div>
|
|
93
|
+
</Dialog>
|
|
94
|
+
</TransitionRoot>
|
|
95
|
+
</template>
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Flash } from "@/composables/overlay"
|
|
3
|
+
import { onMounted, ref } from "vue"
|
|
4
|
+
|
|
5
|
+
// TODO: spk this might benefit from the composition api to avoid race conditions where a flash is requested before the component is mounted.
|
|
6
|
+
|
|
7
|
+
const flashes = ref<Flash[]>([])
|
|
8
|
+
const flashTypeBorderClass = {
|
|
9
|
+
warning: "border-orange-500",
|
|
10
|
+
error: "border-red-500",
|
|
11
|
+
info: "border-blue-500",
|
|
12
|
+
success: "border-green-500",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const getFlashClass = (flash: Flash): string => {
|
|
16
|
+
const availableTypes = Object.keys(flashTypeBorderClass)
|
|
17
|
+
// default the flash type to "info" if no flash type or an unsupported flash.type is found
|
|
18
|
+
const type =
|
|
19
|
+
flash?.type && availableTypes.includes(flash.type)
|
|
20
|
+
? (flash.type as "warning" | "error" | "info" | "success")
|
|
21
|
+
: "info"
|
|
22
|
+
return flashTypeBorderClass[type]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const remove = (flash: Flash): void => {
|
|
26
|
+
let index = 0
|
|
27
|
+
for (const f of flashes.value) {
|
|
28
|
+
if (flash.message === f.message) {
|
|
29
|
+
flashes.value.splice(index, 1)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
index++
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const renderFlash = (flash: Flash): void => {
|
|
36
|
+
flashes.value.push(flash)
|
|
37
|
+
// Super simple flash implementation. This could get "smarter" by adding an
|
|
38
|
+
// id to the flash object, and then searching for the specific flash in the
|
|
39
|
+
// array and splicing, instead of simply doing a pop().
|
|
40
|
+
setTimeout(
|
|
41
|
+
(flashes: Flash[]) => {
|
|
42
|
+
flashes.pop()
|
|
43
|
+
},
|
|
44
|
+
10000,
|
|
45
|
+
flashes.value
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const renderGenericError = (email: string): void => {
|
|
50
|
+
renderFlash({
|
|
51
|
+
type: "error",
|
|
52
|
+
message:
|
|
53
|
+
"Whoops! Something went wrong, please reach out to " +
|
|
54
|
+
`<a class="underline text-xy-blue" href="mailto:${email}">${email}</a>` +
|
|
55
|
+
" if the issue persists.",
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
onMounted(() => {
|
|
60
|
+
window.VueBus.on("Flash-show-message", (flash) => {
|
|
61
|
+
renderFlash(flash)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
window.VueBus.on("Flash-show-generic-error", (email) => {
|
|
65
|
+
renderGenericError(email)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
if (window.Flashes) {
|
|
69
|
+
for (const flash of window.Flashes) {
|
|
70
|
+
if (typeof flash.type === "undefined") {
|
|
71
|
+
const values: string[] = flash.message.split(": ")
|
|
72
|
+
renderFlash({ type: values[0], message: values[1] })
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
renderFlash({ type: flash.type, message: flash.message })
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
</script>
|
|
80
|
+
<template>
|
|
81
|
+
<div
|
|
82
|
+
class="fixed inset-0 flex flex-col items-end justify-end px-4 py-6 pointer-events-none sm:p-6 z-40"
|
|
83
|
+
>
|
|
84
|
+
<transition-group
|
|
85
|
+
tag="div"
|
|
86
|
+
class="max-w-sm w-full"
|
|
87
|
+
enter-active-class="ease-out duration-300"
|
|
88
|
+
enter-from-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
|
|
89
|
+
enter-to-class="translate-y-0 opacity-100 sm:translate-x-0"
|
|
90
|
+
leave-active-class="ease-in duration-100"
|
|
91
|
+
leave-from-class="opacity-100"
|
|
92
|
+
leave-to-class="opacity-0"
|
|
93
|
+
>
|
|
94
|
+
<div
|
|
95
|
+
v-for="(flash, idx) in flashes"
|
|
96
|
+
:key="flash.message"
|
|
97
|
+
class="bg-white shadow-lg rounded-lg pointer-events-auto border-t-4 transform"
|
|
98
|
+
:class="[{ 'mt-2': idx > 0 }, getFlashClass(flash)]"
|
|
99
|
+
>
|
|
100
|
+
<div
|
|
101
|
+
class="rounded-lg ring-1 ring-black ring-opacity-5 overflow-hidden"
|
|
102
|
+
>
|
|
103
|
+
<div class="p-4">
|
|
104
|
+
<div class="flex items-center">
|
|
105
|
+
<div class="w-0 flex-1 flex justify-between">
|
|
106
|
+
<p
|
|
107
|
+
class="w-0 flex-1 text-sm leading-5 font-medium text-gray-900"
|
|
108
|
+
v-html="flash.message"
|
|
109
|
+
></p>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="ml-4 flex-shrink-0 flex">
|
|
112
|
+
<button
|
|
113
|
+
@click="remove(flash)"
|
|
114
|
+
class="inline-flex text-gray-400 focus:outline-none focus:text-gray-500 transition ease-in-out duration-150"
|
|
115
|
+
>
|
|
116
|
+
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
117
|
+
<path
|
|
118
|
+
fill-rule="evenodd"
|
|
119
|
+
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
|
120
|
+
clip-rule="evenodd"
|
|
121
|
+
/>
|
|
122
|
+
</svg>
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</transition-group>
|
|
130
|
+
</div>
|
|
131
|
+
</template>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
Dialog,
|
|
4
|
+
DialogOverlay,
|
|
5
|
+
DialogTitle,
|
|
6
|
+
TransitionChild,
|
|
7
|
+
TransitionRoot,
|
|
8
|
+
} from "@headlessui/vue"
|
|
9
|
+
import { XIcon } from "@heroicons/vue/outline"
|
|
10
|
+
|
|
11
|
+
withDefaults(
|
|
12
|
+
defineProps<{
|
|
13
|
+
destructive?: boolean
|
|
14
|
+
disabled?: boolean
|
|
15
|
+
modelValue: boolean
|
|
16
|
+
submitText?: string
|
|
17
|
+
title?: string
|
|
18
|
+
}>(),
|
|
19
|
+
{
|
|
20
|
+
destructive: false,
|
|
21
|
+
disabled: false,
|
|
22
|
+
submitText: "",
|
|
23
|
+
title: "",
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const emit = defineEmits<{
|
|
28
|
+
(e: "submit"): void
|
|
29
|
+
(e: "update:modelValue", val: boolean): void
|
|
30
|
+
}>()
|
|
31
|
+
|
|
32
|
+
const submit = () => {
|
|
33
|
+
emit("submit")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const updateModelValue = (value: boolean) => {
|
|
37
|
+
emit("update:modelValue", value)
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
<template>
|
|
41
|
+
<TransitionRoot as="template" :show="modelValue">
|
|
42
|
+
<Dialog
|
|
43
|
+
as="div"
|
|
44
|
+
static
|
|
45
|
+
class="fixed z-10 inset-0 overflow-y-auto"
|
|
46
|
+
@close="updateModelValue(false)"
|
|
47
|
+
:open="modelValue"
|
|
48
|
+
>
|
|
49
|
+
<div
|
|
50
|
+
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
|
|
51
|
+
>
|
|
52
|
+
<TransitionChild
|
|
53
|
+
as="template"
|
|
54
|
+
enter="ease-out duration-300"
|
|
55
|
+
enter-from="opacity-0"
|
|
56
|
+
enter-to="opacity-100"
|
|
57
|
+
leave="ease-in duration-200"
|
|
58
|
+
leave-from="opacity-100"
|
|
59
|
+
leave-to="opacity-0"
|
|
60
|
+
>
|
|
61
|
+
<DialogOverlay
|
|
62
|
+
class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
|
63
|
+
/>
|
|
64
|
+
</TransitionChild>
|
|
65
|
+
|
|
66
|
+
<!-- This element is to trick the browser into centering the modal contents. -->
|
|
67
|
+
<span
|
|
68
|
+
class="hidden sm:inline-block sm:align-middle sm:h-screen"
|
|
69
|
+
aria-hidden="true"
|
|
70
|
+
>​</span
|
|
71
|
+
>
|
|
72
|
+
<TransitionChild
|
|
73
|
+
as="template"
|
|
74
|
+
enter="ease-out duration-300"
|
|
75
|
+
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
76
|
+
enter-to="opacity-100 translate-y-0 sm:scale-100"
|
|
77
|
+
leave="ease-in duration-200"
|
|
78
|
+
leave-from="opacity-100 translate-y-0 sm:scale-100"
|
|
79
|
+
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
80
|
+
>
|
|
81
|
+
<div
|
|
82
|
+
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl w-full"
|
|
83
|
+
>
|
|
84
|
+
<div class="block absolute top-0 right-0 pt-4 pr-4">
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
class="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
88
|
+
@click="updateModelValue(false)"
|
|
89
|
+
>
|
|
90
|
+
<span class="sr-only">Close</span>
|
|
91
|
+
<XIcon class="h-6 w-6" aria-hidden="true" />
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
95
|
+
<div class="mt-3 sm:mt-0 sm:text-left">
|
|
96
|
+
<DialogTitle
|
|
97
|
+
as="h3"
|
|
98
|
+
class="text-center text-lg leading-6 font-medium text-gray-900"
|
|
99
|
+
v-text="title"
|
|
100
|
+
></DialogTitle>
|
|
101
|
+
<div class="mt-2">
|
|
102
|
+
<slot></slot>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
<div
|
|
107
|
+
class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"
|
|
108
|
+
v-if="submitText"
|
|
109
|
+
>
|
|
110
|
+
<button
|
|
111
|
+
type="button"
|
|
112
|
+
class="xy-btn w-full sm:ml-3 sm:w-auto sm:text-sm"
|
|
113
|
+
@click="submit()"
|
|
114
|
+
v-text="submitText"
|
|
115
|
+
:class="[destructive ? 'xy-btn-red' : 'xy-btn']"
|
|
116
|
+
:disabled="disabled"
|
|
117
|
+
></button>
|
|
118
|
+
<button
|
|
119
|
+
type="button"
|
|
120
|
+
class="xy-btn-white mt-3 w-full sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
|
|
121
|
+
@click="updateModelValue(false)"
|
|
122
|
+
ref="cancelButtonRef"
|
|
123
|
+
>
|
|
124
|
+
Cancel
|
|
125
|
+
</button>
|
|
126
|
+
</div>
|
|
127
|
+
<slot name="buttons"></slot>
|
|
128
|
+
</div>
|
|
129
|
+
</TransitionChild>
|
|
130
|
+
</div>
|
|
131
|
+
</Dialog>
|
|
132
|
+
</TransitionRoot>
|
|
133
|
+
</template>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export type PopoverPosition =
|
|
3
|
+
| "top-left"
|
|
4
|
+
| "top-center"
|
|
5
|
+
| "top-right"
|
|
6
|
+
| "bottom-left"
|
|
7
|
+
| "bottom-center"
|
|
8
|
+
| "bottom-right"
|
|
9
|
+
| "left"
|
|
10
|
+
| "right"
|
|
11
|
+
</script>
|
|
12
|
+
<script lang="ts" setup>
|
|
13
|
+
import {
|
|
14
|
+
Popover as HeadlessPopover,
|
|
15
|
+
PopoverButton as HeadlessPopoverButton,
|
|
16
|
+
PopoverPanel as HeadlessPopoverPanel,
|
|
17
|
+
} from "@headlessui/vue"
|
|
18
|
+
import { computed } from "vue"
|
|
19
|
+
|
|
20
|
+
const props = withDefaults(
|
|
21
|
+
defineProps<{
|
|
22
|
+
position?: PopoverPosition
|
|
23
|
+
}>(),
|
|
24
|
+
{
|
|
25
|
+
position: "top-center",
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const positionClasses = computed(() => {
|
|
30
|
+
// NOTE: by always pushing the screen width wrapper classes to the left we can avoid overflow scrolling
|
|
31
|
+
|
|
32
|
+
let wrapperClasses = ""
|
|
33
|
+
let contentClasses = ""
|
|
34
|
+
|
|
35
|
+
switch (props.position) {
|
|
36
|
+
case "top-left":
|
|
37
|
+
wrapperClasses =
|
|
38
|
+
"top-0 left-0 -translate-y-full -translate-x-full justify-end"
|
|
39
|
+
break
|
|
40
|
+
case "top-center":
|
|
41
|
+
wrapperClasses =
|
|
42
|
+
"top-0 -translate-y-full -translate-x-full left-1/2 justify-end"
|
|
43
|
+
contentClasses = "translate-x-1/2"
|
|
44
|
+
break
|
|
45
|
+
case "top-right":
|
|
46
|
+
wrapperClasses = "top-0 -translate-y-full right-0 justify-end"
|
|
47
|
+
contentClasses = "translate-x-full"
|
|
48
|
+
break
|
|
49
|
+
case "bottom-left":
|
|
50
|
+
wrapperClasses = "top-full left-0 -translate-x-full justify-end"
|
|
51
|
+
break
|
|
52
|
+
case "bottom-center":
|
|
53
|
+
wrapperClasses = "top-full -translate-x-full left-1/2 justify-end"
|
|
54
|
+
contentClasses = "translate-x-1/2"
|
|
55
|
+
break
|
|
56
|
+
case "bottom-right":
|
|
57
|
+
wrapperClasses = "top-full right-0 justify-end"
|
|
58
|
+
contentClasses = "translate-x-full"
|
|
59
|
+
break
|
|
60
|
+
case "left":
|
|
61
|
+
wrapperClasses =
|
|
62
|
+
"top-1/2 left-0 -translate-y-1/2 -translate-x-full justify-end"
|
|
63
|
+
break
|
|
64
|
+
case "right":
|
|
65
|
+
wrapperClasses = "top-1/2 -translate-y-1/2 right-0 justify-end"
|
|
66
|
+
contentClasses = "translate-x-full"
|
|
67
|
+
break
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
wrapper: wrapperClasses,
|
|
72
|
+
content: contentClasses,
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// TODO: maybe auto positioning - dynamic based on button location and closed overflow hidden container?
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<template>
|
|
80
|
+
<div class="flex">
|
|
81
|
+
<HeadlessPopover v-slot="{ open, close }" class="relative leading-none">
|
|
82
|
+
<HeadlessPopoverButton>
|
|
83
|
+
<slot name="button" :open="open" :close="close"></slot>
|
|
84
|
+
</HeadlessPopoverButton>
|
|
85
|
+
|
|
86
|
+
<transition
|
|
87
|
+
enter-active-class="transition-opacity transition-faster ease-out-quad"
|
|
88
|
+
leave-active-class="transition-opacity transition-faster ease-in-quad"
|
|
89
|
+
enter-from-class="opacity-0"
|
|
90
|
+
enter-to-class="opacity-100"
|
|
91
|
+
leave-from-class="opacity-100"
|
|
92
|
+
leave-to-class="opacity-0"
|
|
93
|
+
>
|
|
94
|
+
<!--NOTE: use prop "static" for dev work to keep the tooptip visible-->
|
|
95
|
+
<HeadlessPopoverPanel>
|
|
96
|
+
<!--positioning wrappers-->
|
|
97
|
+
<div
|
|
98
|
+
class="absolute z-10 transform w-screen flex"
|
|
99
|
+
:class="positionClasses.wrapper"
|
|
100
|
+
>
|
|
101
|
+
<div :class="positionClasses.content">
|
|
102
|
+
<slot :open="open" :close="close"></slot>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</HeadlessPopoverPanel>
|
|
106
|
+
</transition>
|
|
107
|
+
</HeadlessPopover>
|
|
108
|
+
</div>
|
|
109
|
+
</template>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
Dialog,
|
|
4
|
+
DialogOverlay,
|
|
5
|
+
DialogTitle,
|
|
6
|
+
TransitionChild,
|
|
7
|
+
TransitionRoot,
|
|
8
|
+
} from "@headlessui/vue"
|
|
9
|
+
import { XIcon } from "@heroicons/vue/outline"
|
|
10
|
+
import { ref } from "vue"
|
|
11
|
+
|
|
12
|
+
const props = defineProps<{
|
|
13
|
+
header: string
|
|
14
|
+
description: string
|
|
15
|
+
modelValue: boolean
|
|
16
|
+
}>()
|
|
17
|
+
|
|
18
|
+
const open = ref(props.modelValue)
|
|
19
|
+
|
|
20
|
+
const emit = defineEmits<{
|
|
21
|
+
(e: "close", val: boolean): void
|
|
22
|
+
(e: "update:modelValue", val: boolean): void
|
|
23
|
+
}>()
|
|
24
|
+
|
|
25
|
+
const close = () => {
|
|
26
|
+
open.value = false
|
|
27
|
+
emit("close", open.value)
|
|
28
|
+
emit("update:modelValue", open.value)
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
<template>
|
|
32
|
+
<TransitionRoot as="template" :show="modelValue">
|
|
33
|
+
<Dialog
|
|
34
|
+
as="div"
|
|
35
|
+
static
|
|
36
|
+
class="fixed inset-0 z-40 overflow-hidden bg-black bg-opacity-50"
|
|
37
|
+
@close="close()"
|
|
38
|
+
:open="modelValue"
|
|
39
|
+
>
|
|
40
|
+
<div class="absolute inset-0 overflow-hidden">
|
|
41
|
+
<DialogOverlay class="absolute inset-0" />
|
|
42
|
+
|
|
43
|
+
<div class="fixed inset-y-0 right-0 pl-10 max-w-full flex">
|
|
44
|
+
<TransitionChild
|
|
45
|
+
as="template"
|
|
46
|
+
enter="transform transition ease-in-out duration-500 sm:duration-700"
|
|
47
|
+
enter-from="translate-x-full"
|
|
48
|
+
enter-to="translate-x-0"
|
|
49
|
+
leave="transform transition ease-in-out duration-500 sm:duration-700"
|
|
50
|
+
leave-from="translate-x-0"
|
|
51
|
+
leave-to="translate-x-full"
|
|
52
|
+
>
|
|
53
|
+
<div class="w-screen max-w-md">
|
|
54
|
+
<div
|
|
55
|
+
class="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
|
|
56
|
+
>
|
|
57
|
+
<div class="py-6 px-4 bg-blue-700 sm:px-6">
|
|
58
|
+
<div class="flex items-center justify-between">
|
|
59
|
+
<DialogTitle as="h3" class="text-white" v-text="header">
|
|
60
|
+
</DialogTitle>
|
|
61
|
+
<div class="ml-3 h-7 flex items-center">
|
|
62
|
+
<button
|
|
63
|
+
class="bg-blue-700 rounded-md text-blue-200 hover:text-white focus:outline-none focus:ring-2 focus:ring-white"
|
|
64
|
+
@click="close()"
|
|
65
|
+
>
|
|
66
|
+
<span class="sr-only">Close panel</span>
|
|
67
|
+
<XIcon class="h-6 w-6" aria-hidden="true" />
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="mt-1">
|
|
72
|
+
<p class="text-blue-300" v-text="description"></p>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
<div class="relative flex-1 py-6 px-4 sm:px-6">
|
|
76
|
+
<slot></slot>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<slot name="footer"></slot>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</TransitionChild>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</Dialog>
|
|
86
|
+
</TransitionRoot>
|
|
87
|
+
</template>
|