design-system-next 1.2.18 → 1.2.21
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/dist/design-system-next.js +3015 -2591
- package/dist/design-system-next.js.gz +0 -0
- package/dist/main.css +1 -1
- package/dist/main.css.gz +0 -0
- package/package.json +1 -1
- package/src/assets/styles/tailwind.css +34 -2
- package/src/components/checkbox/checkbox.ts +31 -0
- package/src/components/checkbox/checkbox.vue +43 -0
- package/src/components/checkbox/use-checkbox.ts +86 -0
- package/src/components/dropdown/dropdown.ts +55 -0
- package/src/components/dropdown/dropdown.vue +61 -0
- package/src/components/dropdown/use-dropdown.ts +122 -0
- package/src/components/input/input.vue +6 -5
- package/src/components/sidenav/sidenav.vue +10 -5
- package/src/components/sidepanel/sidepanel.ts +87 -0
- package/src/components/sidepanel/sidepanel.vue +60 -0
- package/src/components/sidepanel/use-sidepanel.ts +81 -0
- package/src/components/tooltip/tooltip.ts +6 -6
- package/src/components/tooltip/tooltip.vue +13 -13
- package/src/components/tooltip/use-tooltip.ts +6 -30
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { ref, onMounted, watch, computed } from 'vue';
|
|
2
|
+
import type { SetupContext } from 'vue';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
|
|
5
|
+
import type { DropdownPropTypes, DropdownEmitTypes } from './dropdown';
|
|
6
|
+
|
|
7
|
+
interface SelectedItem {
|
|
8
|
+
text: string;
|
|
9
|
+
value: string | number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const useDropdown = (props: DropdownPropTypes, emit: SetupContext<DropdownEmitTypes>['emit']) => {
|
|
13
|
+
const dropdownItemBaseClasses = computed(() => {
|
|
14
|
+
return classNames(
|
|
15
|
+
'flex cursor-pointer items-center justify-between gap-1.5 rounded-lg p-2',
|
|
16
|
+
'transition duration-150 ease-in-out',
|
|
17
|
+
'hover:background-color-hover',
|
|
18
|
+
'active:background-color-single-active active:scale-95',
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const menuOpened = ref(false);
|
|
23
|
+
|
|
24
|
+
watch(menuOpened, () => {
|
|
25
|
+
handlePopperState();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const isSingleSelect = computed(() => props.dropdownType === 'single-select');
|
|
29
|
+
const isMultiSelect = computed(() => props.dropdownType === 'multi-select');
|
|
30
|
+
|
|
31
|
+
const selectedItems = ref<SelectedItem[]>([]);
|
|
32
|
+
|
|
33
|
+
const handleSelectedItem = (item: SelectedItem) => {
|
|
34
|
+
if (isSingleSelect.value) {
|
|
35
|
+
menuOpened.value = false;
|
|
36
|
+
|
|
37
|
+
selectedItems.value = [item];
|
|
38
|
+
|
|
39
|
+
emit('get-selected-item', selectedItems.value[0]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (isMultiSelect.value) {
|
|
43
|
+
const index = selectedItems.value.findIndex((selectedItem: SelectedItem) => selectedItem.value === item.value);
|
|
44
|
+
|
|
45
|
+
if (index === -1) {
|
|
46
|
+
checkboxModels.value[item.text] = true;
|
|
47
|
+
selectedItems.value.push(item);
|
|
48
|
+
} else {
|
|
49
|
+
checkboxModels.value[item.text] = false;
|
|
50
|
+
selectedItems.value.splice(index, 1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
emit('get-selected-item', selectedItems.value);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const isItemSelected = (item: SelectedItem) => {
|
|
58
|
+
if (Array.isArray(selectedItems.value)) {
|
|
59
|
+
return selectedItems.value.some((selectedItem) => selectedItem.text === item.text);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (selectedItems.value[0] as SelectedItem).text === item.text;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const checkboxModels = ref<Record<string, boolean>>({});
|
|
66
|
+
|
|
67
|
+
const setCheckboxModels = () => {
|
|
68
|
+
if (props.menu && props.menu.length > 0) {
|
|
69
|
+
props.menu.forEach((item) => {
|
|
70
|
+
checkboxModels.value = Object.assign({}, checkboxModels.value, {
|
|
71
|
+
[item.text]: false,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const setPreSelectedItems = () => {
|
|
78
|
+
if (props.menu && props.menu.length > 0 && props.preSelectedItems && props.preSelectedItems.length > 0) {
|
|
79
|
+
(props.preSelectedItems as string[]).forEach((preSelectedItem: string) => {
|
|
80
|
+
const item = props.menu?.find((menuItem) => menuItem.text === preSelectedItem);
|
|
81
|
+
|
|
82
|
+
if (item) {
|
|
83
|
+
if (isMultiSelect.value) {
|
|
84
|
+
checkboxModels.value[item.text] = true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
selectedItems.value.push(item);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (isSingleSelect.value) {
|
|
92
|
+
if (selectedItems.value.length > 0) {
|
|
93
|
+
emit('get-selected-item', selectedItems.value[0]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (isMultiSelect.value) {
|
|
98
|
+
emit('get-selected-item', selectedItems.value);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const handlePopperState = () => {
|
|
104
|
+
emit('get-popper-state', menuOpened.value);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
onMounted(() => {
|
|
108
|
+
menuOpened.value = props.menuOpened;
|
|
109
|
+
|
|
110
|
+
setCheckboxModels();
|
|
111
|
+
setPreSelectedItems();
|
|
112
|
+
handlePopperState();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
dropdownItemBaseClasses,
|
|
117
|
+
menuOpened,
|
|
118
|
+
handleSelectedItem,
|
|
119
|
+
isItemSelected,
|
|
120
|
+
checkboxModels,
|
|
121
|
+
};
|
|
122
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :class="wrapperClasses">
|
|
3
|
-
<label v-if="label" :for="id" :class="labelClasses">
|
|
4
|
-
{{ label }}
|
|
3
|
+
<label v-if="props.label" :for="id" :class="labelClasses">
|
|
4
|
+
{{ props.label }}
|
|
5
5
|
</label>
|
|
6
6
|
<div class="relative">
|
|
7
7
|
<div v-if="$slots.prefix" :class="prefixSlotClasses">
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
</div>
|
|
10
10
|
<input
|
|
11
11
|
:class="[inputClasses, { 'number-input': type === 'number' }]"
|
|
12
|
-
:placeholder="placeholder"
|
|
13
|
-
:disabled="disabled"
|
|
14
|
-
:readonly="readonly"
|
|
12
|
+
:placeholder="props.placeholder"
|
|
13
|
+
:disabled="props.disabled"
|
|
14
|
+
:readonly="props.readonly"
|
|
15
|
+
:value="props.modelValue"
|
|
15
16
|
@input="onInput"
|
|
16
17
|
/>
|
|
17
18
|
<div v-if="$slots.trailing" :class="trailingSlotClasses">
|
|
@@ -277,7 +277,7 @@
|
|
|
277
277
|
<template v-else>
|
|
278
278
|
<Tooltip
|
|
279
279
|
v-if="!parentLink.hidden"
|
|
280
|
-
aria-id="
|
|
280
|
+
aria-id="sidenav-tooltip-wrapper"
|
|
281
281
|
placement="right"
|
|
282
282
|
distance="18"
|
|
283
283
|
:triggers="['hover']"
|
|
@@ -460,7 +460,7 @@
|
|
|
460
460
|
<template v-else>
|
|
461
461
|
<Tooltip
|
|
462
462
|
v-if="!parentLink.hidden"
|
|
463
|
-
aria-id="
|
|
463
|
+
aria-id="sidenav-tooltip-wrapper"
|
|
464
464
|
placement="right"
|
|
465
465
|
distance="18"
|
|
466
466
|
:triggers="['hover']"
|
|
@@ -504,7 +504,7 @@
|
|
|
504
504
|
@click="emit('notifications', 'notifications-triggered')"
|
|
505
505
|
>
|
|
506
506
|
<Icon icon="ph:bell" class="h-[1.25em] w-[1.25em]" />
|
|
507
|
-
<badge
|
|
507
|
+
<spr-badge
|
|
508
508
|
class="absolute -top-0.5 right-2.5"
|
|
509
509
|
:text="String(props.notificationCount)"
|
|
510
510
|
variant="danger"
|
|
@@ -523,7 +523,12 @@
|
|
|
523
523
|
@click="emit('requests', 'requests-triggered')"
|
|
524
524
|
>
|
|
525
525
|
<Icon icon="ph:check-square" class="h-[1.25em] w-[1.25em]" />
|
|
526
|
-
<badge
|
|
526
|
+
<spr-badge
|
|
527
|
+
class="absolute -top-0.5 right-2.5"
|
|
528
|
+
:text="String(props.requestCount)"
|
|
529
|
+
variant="danger"
|
|
530
|
+
size="small"
|
|
531
|
+
/>
|
|
527
532
|
</div>
|
|
528
533
|
</div>
|
|
529
534
|
|
|
@@ -625,7 +630,7 @@ import 'floating-vue/dist/style.css';
|
|
|
625
630
|
import { sidenavPropTypes, sidenavEmitTypes } from './sidenav';
|
|
626
631
|
import { useSidenav } from './use-sidenav';
|
|
627
632
|
|
|
628
|
-
import
|
|
633
|
+
import SprBadge from '../badge/badge.vue';
|
|
629
634
|
|
|
630
635
|
const props = defineProps(sidenavPropTypes);
|
|
631
636
|
const emit = defineEmits(sidenavEmitTypes);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { PropType, ExtractPropTypes } from 'vue';
|
|
2
|
+
|
|
3
|
+
const SIDEPANEL_SIZE = ['sm', 'md', 'lg', 'xl'] as const;
|
|
4
|
+
const SIDEPANEL_POSITION = ['right'] as const;
|
|
5
|
+
|
|
6
|
+
export const sidepanelPropTypes = {
|
|
7
|
+
/**
|
|
8
|
+
* @description Controls whether the side panel is open.
|
|
9
|
+
* Set to `true` to display the side panel or `false` to hide it.
|
|
10
|
+
*/
|
|
11
|
+
isOpen: {
|
|
12
|
+
type: Boolean,
|
|
13
|
+
default: false,
|
|
14
|
+
},
|
|
15
|
+
/**
|
|
16
|
+
* @description The title displayed in the side panel's header.
|
|
17
|
+
* If not provided, defaults to 'Sidepanel Header'.
|
|
18
|
+
*/
|
|
19
|
+
headerTitle: {
|
|
20
|
+
type: String,
|
|
21
|
+
default: 'Sidepanel Header'
|
|
22
|
+
},
|
|
23
|
+
/**
|
|
24
|
+
* @description Specifies the size of the side panel.
|
|
25
|
+
* Acceptable values are: `'sm'`, `'md'`, `'lg'`, `'xl'`.
|
|
26
|
+
* Defaults to `'sm'`.
|
|
27
|
+
*/
|
|
28
|
+
size: {
|
|
29
|
+
type: String as PropType<(typeof SIDEPANEL_SIZE)[number]>,
|
|
30
|
+
validator: (value: (typeof SIDEPANEL_SIZE)[number]) => SIDEPANEL_SIZE.includes(value),
|
|
31
|
+
default: 'sm',
|
|
32
|
+
},
|
|
33
|
+
/**
|
|
34
|
+
* @description Sets the height of the side panel.
|
|
35
|
+
* Accepts a string (e.g., `'500px'`, `'70vh'`) or a number (interpreted as pixels).
|
|
36
|
+
* Defaults to `'calc(100vh - 32px)'`.
|
|
37
|
+
*/
|
|
38
|
+
height: {
|
|
39
|
+
type: [String, Number],
|
|
40
|
+
default: 'calc(100vh - 32px)',
|
|
41
|
+
},
|
|
42
|
+
/**
|
|
43
|
+
* @description Controls the visibility of the side panel header.
|
|
44
|
+
* Set to `true` to hide the header, or `false` to display it.
|
|
45
|
+
* Defaults to `false`.
|
|
46
|
+
*/
|
|
47
|
+
hideHeader: {
|
|
48
|
+
type: Boolean,
|
|
49
|
+
default: false
|
|
50
|
+
},
|
|
51
|
+
/**
|
|
52
|
+
* @description Specifies the position of the side panel.
|
|
53
|
+
* Currently, only `'right'` is supported.
|
|
54
|
+
* Defaults to `'right'`.
|
|
55
|
+
*/
|
|
56
|
+
position: {
|
|
57
|
+
type: String as PropType<(typeof SIDEPANEL_POSITION)[number]>,
|
|
58
|
+
validator: (value: (typeof SIDEPANEL_POSITION)[number]) => SIDEPANEL_POSITION.includes(value),
|
|
59
|
+
default: 'right',
|
|
60
|
+
},
|
|
61
|
+
/**
|
|
62
|
+
* @description Determines whether a backdrop is displayed behind the side panel.
|
|
63
|
+
* Set to `true` to enable the backdrop, or `false` to disable it.
|
|
64
|
+
* Defaults to `true`.
|
|
65
|
+
*/
|
|
66
|
+
hasBackdrop: {
|
|
67
|
+
type: Boolean,
|
|
68
|
+
default: true
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* @description Controls whether clicking outside the side panel should close it.
|
|
72
|
+
* Set to `true` to enable closing on outside click, or `false` to disable it.
|
|
73
|
+
* Defaults to `false`.
|
|
74
|
+
*/
|
|
75
|
+
closeOutside: {
|
|
76
|
+
type: Boolean,
|
|
77
|
+
default: false,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const sidepanelEmitTypes = {
|
|
82
|
+
close: Function,
|
|
83
|
+
onClose: Function,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type SidepanelPropTypes = ExtractPropTypes<typeof sidepanelPropTypes>;
|
|
87
|
+
export type SidepanelEmitTypes = typeof sidepanelEmitTypes;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-if="isOpen && hasBackdrop"
|
|
4
|
+
class="w-screen h-screen bg-mushroom-700/60 fixed top-0 left-0 z-[30]"
|
|
5
|
+
@click="handleBackdropClick"
|
|
6
|
+
></div>
|
|
7
|
+
<Transition
|
|
8
|
+
:name="`sidepanel`"
|
|
9
|
+
enter-active-class="transition-transform duration-[0.3s] ease-[ease-out]"
|
|
10
|
+
leave-active-class="transition-transform duration-[0.3s] ease-[ease-out]"
|
|
11
|
+
:enter-from-class="sidepanelStartEndState"
|
|
12
|
+
:leave-to-class="sidepanelStartEndState"
|
|
13
|
+
:enter-to-class="sidepanelMidState"
|
|
14
|
+
:leave-from-class="sidepanelMidState"
|
|
15
|
+
appear
|
|
16
|
+
>
|
|
17
|
+
<div
|
|
18
|
+
v-if="isOpen"
|
|
19
|
+
ref="sidepanelRef"
|
|
20
|
+
:class="[
|
|
21
|
+
sidepanelSizesClasses,
|
|
22
|
+
'h-[calc(100vh_-_32px)] bg-white-50 rounded-border-radius-xl fixed right-4 z-[30] min-h-[200px] flex flex-col top-1/2 translate-y-[-50%] drop-shadow'
|
|
23
|
+
]"
|
|
24
|
+
:style="{ height: typeof height === 'number' ? `${height}px` : height }"
|
|
25
|
+
>
|
|
26
|
+
<template v-if="!hideHeader">
|
|
27
|
+
<div v-if="!$slots.header" class="flex justify-between tw-min-h-12 p-4 border-mushroom-200 border-b border-solid border-0 subheading-xs text-color-strong">
|
|
28
|
+
{{ headerTitle }}
|
|
29
|
+
<Icon
|
|
30
|
+
class="cursor-pointer"
|
|
31
|
+
icon="ph:x"
|
|
32
|
+
@click="handleClose"
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
<div v-else>
|
|
36
|
+
<slot name="header"></slot>
|
|
37
|
+
</div>
|
|
38
|
+
</template>
|
|
39
|
+
<div :class="['overflow-y-auto p-4', {'mb-[52px]': $slots.footer}]">
|
|
40
|
+
<slot>
|
|
41
|
+
Sidepanel Content
|
|
42
|
+
</slot>
|
|
43
|
+
</div>
|
|
44
|
+
<div v-if="$slots.footer" class="absolute bottom-0 left-0 w-full border-0 border-t border-mushroom-200 bg-white-50 border-solid py-3 rounded-b-border-radius-xl">
|
|
45
|
+
<slot name="footer"></slot>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</Transition>
|
|
49
|
+
|
|
50
|
+
</template>
|
|
51
|
+
<script lang="ts" setup>
|
|
52
|
+
import { useSidepanel } from './use-sidepanel';
|
|
53
|
+
import { sidepanelPropTypes, sidepanelEmitTypes } from './sidepanel';
|
|
54
|
+
import { Icon } from '@iconify/vue';
|
|
55
|
+
|
|
56
|
+
const props = defineProps(sidepanelPropTypes);
|
|
57
|
+
const emit = defineEmits(sidepanelEmitTypes);
|
|
58
|
+
|
|
59
|
+
const { sidepanelRef, sidepanelSizesClasses, sidepanelMidState, sidepanelStartEndState, handleClose, handleBackdropClick } = useSidepanel(props, emit);
|
|
60
|
+
</script>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ref, computed, toRefs, watch, onMounted, onUnmounted } from 'vue';
|
|
2
|
+
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import type { SetupContext } from 'vue';
|
|
5
|
+
import type { SidepanelPropTypes, SidepanelEmitTypes } from './sidepanel';
|
|
6
|
+
|
|
7
|
+
export const useSidepanel = (props: SidepanelPropTypes, emit: SetupContext<SidepanelEmitTypes>['emit']) => {
|
|
8
|
+
const sidepanelRef = ref<HTMLDivElement | null>(null);
|
|
9
|
+
const { size, position } = toRefs(props);
|
|
10
|
+
|
|
11
|
+
const sidepanelSizesClasses = computed(() => {
|
|
12
|
+
return classNames({
|
|
13
|
+
'w-[360px]': size.value === 'sm',
|
|
14
|
+
'w-[420px]': size.value === 'md',
|
|
15
|
+
'w-[480px]': size.value === 'lg',
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const sidepanelStartEndState = computed(() => {
|
|
20
|
+
return classNames({
|
|
21
|
+
'translate-x-full -translate-y-2/4': position.value === 'right'
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const sidepanelMidState = computed(() => {
|
|
26
|
+
return classNames({
|
|
27
|
+
'translate-x-0 -translate-y-2/4': position.value === 'right'
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const handleClose = () => {
|
|
32
|
+
emit('close');
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const handleBackdropClick = () => {
|
|
36
|
+
if (props.closeOutside) {
|
|
37
|
+
emit('close')
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let ignoreClick = false;
|
|
42
|
+
|
|
43
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
44
|
+
if (ignoreClick) return;
|
|
45
|
+
if (sidepanelRef.value && !sidepanelRef.value.contains(event.target as Node) && props.closeOutside) {
|
|
46
|
+
emit('close')
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
watch(
|
|
51
|
+
() => props.isOpen,
|
|
52
|
+
(value) => {
|
|
53
|
+
if (value) {
|
|
54
|
+
ignoreClick = true;
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
ignoreClick = false;
|
|
57
|
+
});
|
|
58
|
+
} else {
|
|
59
|
+
emit('onClose');
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
onMounted(() => {
|
|
65
|
+
document.addEventListener('click', handleClickOutside);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
onUnmounted(() => {
|
|
69
|
+
document.removeEventListener('click', handleClickOutside);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
sidepanelRef,
|
|
74
|
+
sidepanelSizesClasses,
|
|
75
|
+
sidepanelMidState,
|
|
76
|
+
sidepanelStartEndState,
|
|
77
|
+
handleClose,
|
|
78
|
+
handleBackdropClick,
|
|
79
|
+
handleClickOutside
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { PropType, ExtractPropTypes } from 'vue';
|
|
|
2
2
|
|
|
3
3
|
export const definePropType = <T>(val: unknown): PropType<T> => val as PropType<T>;
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const PLACEMENTS = [
|
|
6
6
|
'top',
|
|
7
7
|
'top-start',
|
|
8
8
|
'top-end',
|
|
@@ -14,7 +14,7 @@ const TOOLTIP_POSITION = [
|
|
|
14
14
|
'left-end',
|
|
15
15
|
'right',
|
|
16
16
|
'right-start',
|
|
17
|
-
'right-end'
|
|
17
|
+
'right-end',
|
|
18
18
|
] as const;
|
|
19
19
|
|
|
20
20
|
export const tooltipPropTypes = {
|
|
@@ -26,12 +26,12 @@ export const tooltipPropTypes = {
|
|
|
26
26
|
default: 'Sample tooltip',
|
|
27
27
|
},
|
|
28
28
|
/**
|
|
29
|
-
* @description Tooltip
|
|
29
|
+
* @description Tooltip Placement
|
|
30
30
|
*/
|
|
31
|
-
|
|
31
|
+
placement: {
|
|
32
32
|
type: String,
|
|
33
|
-
validator: (value: (typeof
|
|
34
|
-
default: 'top
|
|
33
|
+
validator: (value: (typeof PLACEMENTS)[number]) => PLACEMENTS.includes(value),
|
|
34
|
+
default: 'top',
|
|
35
35
|
},
|
|
36
36
|
};
|
|
37
37
|
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<Tooltip class="w-fit" aria-id="tooltip-wrapper" :placement="placement">
|
|
3
|
+
<template #popper>
|
|
4
|
+
<p>{{ text }}</p>
|
|
5
|
+
|
|
6
|
+
<slot name="popper-content" />
|
|
7
|
+
</template>
|
|
8
|
+
|
|
3
9
|
<slot />
|
|
4
|
-
|
|
5
|
-
:class="[
|
|
6
|
-
tooltipClasses,
|
|
7
|
-
'background-color-inverted absolute z-50 w-max min-w-10 max-w-72 text-wrap rounded-md px-size-spacing-4xs py-size-spacing-3xs',
|
|
8
|
-
]"
|
|
9
|
-
>
|
|
10
|
-
<div class="text-color-inverted-strong body-xs-regular">
|
|
11
|
-
{{ text }}
|
|
12
|
-
</div>
|
|
13
|
-
</div>
|
|
14
|
-
</div>
|
|
10
|
+
</Tooltip>
|
|
15
11
|
</template>
|
|
16
12
|
|
|
17
13
|
<script lang="ts" setup>
|
|
14
|
+
import { Tooltip } from 'floating-vue';
|
|
15
|
+
import 'floating-vue/dist/style.css';
|
|
16
|
+
|
|
18
17
|
import { tooltipPropTypes } from './tooltip';
|
|
19
18
|
import { useTooltip } from './use-tooltip';
|
|
20
19
|
|
|
21
20
|
const props = defineProps(tooltipPropTypes);
|
|
22
|
-
|
|
21
|
+
|
|
22
|
+
const { placement } = useTooltip(props);
|
|
23
23
|
</script>
|
|
@@ -1,37 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useElementHover } from '@vueuse/core';
|
|
3
|
-
import { TooltipPropTypes } from './tooltip';
|
|
4
|
-
|
|
5
|
-
import classNames from 'classnames';
|
|
1
|
+
import { ref } from 'vue';
|
|
6
2
|
|
|
7
|
-
|
|
8
|
-
const { position, text } = toRefs(props);
|
|
3
|
+
import { Placement } from 'floating-vue';
|
|
9
4
|
|
|
10
|
-
|
|
11
|
-
const isHovered = useElementHover(componentRef);
|
|
5
|
+
import { TooltipPropTypes } from './tooltip';
|
|
12
6
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return classNames({
|
|
16
|
-
hidden: !isHovered.value,
|
|
17
|
-
'bottom-[calc(100%+6px)] left-[50%] translate-x-[-50%]': position.value === 'top',
|
|
18
|
-
'bottom-[calc(100%+6px)] left-0': position.value === 'top-start',
|
|
19
|
-
'bottom-[calc(100%+6px)] right-0': position.value === 'top-end',
|
|
20
|
-
'top-[calc(100%+6px)] left-[50%] translate-x-[-50%]': position.value === 'bottom',
|
|
21
|
-
'top-[calc(100%+6px)] left-0': position.value === 'bottom-start',
|
|
22
|
-
'top-[calc(100%+6px)] right-0': position.value === 'bottom-end',
|
|
23
|
-
'left-[calc(100%+6px)] top-[50%] translate-y-[-50%]': position.value === 'right',
|
|
24
|
-
'left-[calc(100%+6px)] top-0': position.value === 'right-start',
|
|
25
|
-
'left-[calc(100%+6px)] bottom-0': position.value === 'right-end',
|
|
26
|
-
'right-[calc(100%+6px)] top-[50%] translate-y-[-50%]': position.value === 'left',
|
|
27
|
-
'right-[calc(100%+6px)] top-0': position.value === 'left-start',
|
|
28
|
-
'right-[calc(100%+6px)] bottom-0': position.value === 'left-end',
|
|
29
|
-
});
|
|
30
|
-
});
|
|
7
|
+
export const useTooltip = (props: TooltipPropTypes) => {
|
|
8
|
+
const placement = ref(props.placement as Placement);
|
|
31
9
|
|
|
32
10
|
return {
|
|
33
|
-
|
|
34
|
-
tooltipClasses,
|
|
35
|
-
text,
|
|
11
|
+
placement,
|
|
36
12
|
};
|
|
37
13
|
};
|