tide-design-system 2.1.7 → 2.1.8
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/.storybook/main.ts +1 -0
- package/dist/css/realm/aero.css +3 -3
- package/dist/css/realm/boatmart.css +1 -1
- package/dist/css/realm/cycle.css +1 -1
- package/dist/css/realm/equipment.css +1 -1
- package/dist/css/realm/pwc.css +1 -1
- package/dist/css/reset.css +7 -0
- package/dist/style.css +1 -1
- package/dist/tide-design-system.cjs +2 -2
- package/dist/tide-design-system.esm.d.ts +40 -22
- package/dist/tide-design-system.esm.js +787 -785
- package/dist/utilities/event.ts +4 -0
- package/dist/utilities/storybook.ts +4 -0
- package/dist/utilities/viewport.ts +44 -0
- package/index.ts +2 -4
- package/package.json +4 -1
- package/src/assets/css/realm/aero.css +3 -3
- package/src/assets/css/realm/boatmart.css +1 -1
- package/src/assets/css/realm/cycle.css +1 -1
- package/src/assets/css/realm/equipment.css +1 -1
- package/src/assets/css/realm/pwc.css +1 -1
- package/src/assets/css/reset.css +7 -0
- package/src/components/TideCard.vue +3 -7
- package/src/components/TideModal.vue +164 -132
- package/src/components/TidePopover.vue +167 -0
- package/src/stories/TideAccordionItem.stories.ts +1 -0
- package/src/stories/TideButtonSegmented.stories.ts +1 -0
- package/src/stories/TideCard.stories.ts +1 -11
- package/src/stories/TideCarousel.stories.ts +1 -0
- package/src/stories/TideModal.stories.ts +68 -6
- package/src/stories/TidePagination.stories.ts +1 -0
- package/src/stories/TidePopover.stories.ts +98 -0
- package/src/stories/TideSwitch.stories.ts +1 -0
- package/src/types/Card.ts +0 -7
- package/src/utilities/event.ts +4 -0
- package/src/utilities/storybook.ts +4 -0
- package/src/utilities/viewport.ts +44 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { autoPlacement, autoUpdate, offset as offsetMiddleware, shift, useFloating } from '@floating-ui/vue';
|
|
3
|
+
import { computed, onBeforeMount, onMounted, onUnmounted, ref, watch, watchEffect } from 'vue';
|
|
4
|
+
|
|
5
|
+
import { CSS } from '@/types/Styles';
|
|
6
|
+
import { isClickOutside } from '@/utilities/event';
|
|
7
|
+
import { TOP_LAYER_ID, initFauxTopLayer } from '@/utilities/viewport';
|
|
8
|
+
|
|
9
|
+
import type { Ref } from 'vue';
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
anchorId: string;
|
|
13
|
+
offset?: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
17
|
+
offset: 16,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const anchor: Ref<HTMLElement | null> = ref(null);
|
|
21
|
+
const floating: Ref<HTMLElement | null> = ref(null);
|
|
22
|
+
const root: Ref<HTMLElement | null> = ref(null);
|
|
23
|
+
const isHovered = ref(false);
|
|
24
|
+
const isToggledOpen = ref(false);
|
|
25
|
+
const middleware = ref([autoPlacement(), offsetMiddleware({ mainAxis: props.offset }), shift({ padding: 16 })]);
|
|
26
|
+
|
|
27
|
+
const isShowPopover = computed(() => isHovered.value || isToggledOpen.value);
|
|
28
|
+
|
|
29
|
+
const { floatingStyles } = useFloating(anchor, floating, {
|
|
30
|
+
middleware,
|
|
31
|
+
strategy: 'fixed',
|
|
32
|
+
whileElementsMounted: autoUpdate,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const handlePermanentOpenBodyClick = (e: MouseEvent) => {
|
|
36
|
+
if (!anchor.value || !floating.value) return;
|
|
37
|
+
if (isClickOutside(e, [anchor.value, floating.value])) {
|
|
38
|
+
e.stopImmediatePropagation();
|
|
39
|
+
isToggledOpen.value = false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handleAnchorElementMouseOver = () => {
|
|
44
|
+
isHovered.value = true;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const handleAnchorElementMouseLeave = () => {
|
|
48
|
+
isHovered.value = false;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleAnchorElementClick = () => {
|
|
52
|
+
isToggledOpen.value = true;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const addListenersToAnchorElement = () => {
|
|
56
|
+
if (!anchor.value) return null;
|
|
57
|
+
anchor.value.addEventListener('mouseover', handleAnchorElementMouseOver);
|
|
58
|
+
anchor.value.addEventListener('mouseleave', handleAnchorElementMouseLeave);
|
|
59
|
+
anchor.value.addEventListener('click', handleAnchorElementClick);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const removeListenersFromAnchorElement = () => {
|
|
63
|
+
if (!anchor.value) return null;
|
|
64
|
+
anchor.value.removeEventListener('mouseover', handleAnchorElementMouseOver);
|
|
65
|
+
anchor.value.removeEventListener('mouseleave', handleAnchorElementMouseLeave);
|
|
66
|
+
anchor.value.removeEventListener('click', handleAnchorElementClick);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const handlePermanentOpenBodyKeydown = (e: KeyboardEvent) => {
|
|
70
|
+
if (e.key === 'Escape') {
|
|
71
|
+
isToggledOpen.value = false;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const addOpenListenersToRoot = () => {
|
|
76
|
+
if (!root.value) return;
|
|
77
|
+
root.value.addEventListener('click', handlePermanentOpenBodyClick, true);
|
|
78
|
+
root.value.addEventListener('keydown', handlePermanentOpenBodyKeydown);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const removeOpenListenersFromRoot = () => {
|
|
82
|
+
if (!root.value) return;
|
|
83
|
+
root.value.removeEventListener('click', handlePermanentOpenBodyClick, true);
|
|
84
|
+
root.value.removeEventListener('keydown', handlePermanentOpenBodyKeydown);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const updateAnchorElement = () => {
|
|
88
|
+
anchor.value = document.getElementById(props.anchorId);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
watch(
|
|
92
|
+
() => isToggledOpen.value,
|
|
93
|
+
(newValue) => {
|
|
94
|
+
if (newValue) {
|
|
95
|
+
addOpenListenersToRoot();
|
|
96
|
+
} else {
|
|
97
|
+
removeOpenListenersFromRoot();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
watch(
|
|
103
|
+
() => props.anchorId,
|
|
104
|
+
() => {
|
|
105
|
+
removeListenersFromAnchorElement();
|
|
106
|
+
updateAnchorElement();
|
|
107
|
+
addListenersToAnchorElement();
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
watchEffect(() => {
|
|
112
|
+
middleware.value = [autoPlacement(), offsetMiddleware({ mainAxis: props.offset }), shift({ padding: 16 })];
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
onBeforeMount(() => {
|
|
116
|
+
initFauxTopLayer();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
onMounted(() => {
|
|
120
|
+
updateAnchorElement();
|
|
121
|
+
root.value = document.documentElement;
|
|
122
|
+
addListenersToAnchorElement();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
onUnmounted(() => {
|
|
126
|
+
removeListenersFromAnchorElement();
|
|
127
|
+
});
|
|
128
|
+
</script>
|
|
129
|
+
|
|
130
|
+
<template>
|
|
131
|
+
<Teleport :to="`#${TOP_LAYER_ID}`">
|
|
132
|
+
<Transition>
|
|
133
|
+
<div
|
|
134
|
+
:class="[
|
|
135
|
+
'tide-popover',
|
|
136
|
+
CSS.BG.SURFACE.DEFAULT,
|
|
137
|
+
CSS.BORDER.COLOR.LOW,
|
|
138
|
+
CSS.BORDER.FULL.ONE,
|
|
139
|
+
CSS.BORDER.RADIUS.HALF,
|
|
140
|
+
CSS.FONT.ROLE.BODY_2,
|
|
141
|
+
CSS.PADDING.FULL.ONE,
|
|
142
|
+
CSS.SHADOW.BOTTOM,
|
|
143
|
+
]"
|
|
144
|
+
ref="floating"
|
|
145
|
+
:style="{ ...floatingStyles, maxWidth: `calc(100% - ${props.offset * 2}px)` }"
|
|
146
|
+
v-show="isShowPopover"
|
|
147
|
+
>
|
|
148
|
+
<slot />
|
|
149
|
+
</div>
|
|
150
|
+
</Transition>
|
|
151
|
+
</Teleport>
|
|
152
|
+
</template>
|
|
153
|
+
|
|
154
|
+
<style scoped>
|
|
155
|
+
.v-enter-from,
|
|
156
|
+
.v-leave-to {
|
|
157
|
+
opacity: 0;
|
|
158
|
+
}
|
|
159
|
+
.v-enter-active,
|
|
160
|
+
.v-leave-active {
|
|
161
|
+
transition: opacity var(--tide-animate);
|
|
162
|
+
}
|
|
163
|
+
.v-enter-to,
|
|
164
|
+
.v-leave-from {
|
|
165
|
+
opacity: 1;
|
|
166
|
+
}
|
|
167
|
+
</style>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { action } from '@storybook/addon-actions';
|
|
2
2
|
|
|
3
3
|
import TideCard from '@/components/TideCard.vue';
|
|
4
|
-
import {
|
|
4
|
+
import { TYPE_CARD as STANDARD_TYPE_CARD } from '@/types/Card';
|
|
5
5
|
import { ICON } from '@/types/Icon';
|
|
6
6
|
import {
|
|
7
7
|
argTypeBooleanUnrequired,
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
} from '@/utilities/storybook';
|
|
14
14
|
|
|
15
15
|
const TYPE_CARD = prependNoneAsUndefined(STANDARD_TYPE_CARD);
|
|
16
|
-
const POSITION_CARD_ICON = prependNoneAsUndefined(STANDARD_POSITION_CARD_ICON);
|
|
17
16
|
const CARD_ICON = prependNoneAsUndefined(ICON);
|
|
18
17
|
|
|
19
18
|
const render = (args: any) => ({
|
|
@@ -67,14 +66,6 @@ export default {
|
|
|
67
66
|
type: { summary: 'Icon' },
|
|
68
67
|
},
|
|
69
68
|
},
|
|
70
|
-
iconPosition: {
|
|
71
|
-
...formatArgType({ POSITION_CARD_ICON }),
|
|
72
|
-
description: 'Position of the icon relative to the content.',
|
|
73
|
-
table: {
|
|
74
|
-
defaultValue: { summary: 'LEFT' },
|
|
75
|
-
type: { summary: 'CardIconPosition' },
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
69
|
selected: {
|
|
79
70
|
...argTypeBooleanUnrequired,
|
|
80
71
|
description: 'Determines whether the Card is selected (for selectable cards).',
|
|
@@ -93,7 +84,6 @@ export default {
|
|
|
93
84
|
description: '',
|
|
94
85
|
heading: 'Demo',
|
|
95
86
|
icon: CARD_ICON.None,
|
|
96
|
-
iconPosition: POSITION_CARD_ICON.None,
|
|
97
87
|
selected: undefined,
|
|
98
88
|
type: TYPE_CARD.None,
|
|
99
89
|
},
|
|
@@ -2,17 +2,33 @@ import { action } from '@storybook/addon-actions';
|
|
|
2
2
|
|
|
3
3
|
import TideButton from '@/components/TideButton.vue';
|
|
4
4
|
import TideModal from '@/components/TideModal.vue';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
argTypeBooleanUnrequired,
|
|
7
|
+
disabledArgType,
|
|
8
|
+
doSomething,
|
|
9
|
+
doSomethingElse,
|
|
10
|
+
lineBreak,
|
|
11
|
+
tab,
|
|
12
|
+
} from '@/utilities/storybook';
|
|
6
13
|
|
|
7
14
|
import type { StoryContext } from '@storybook/vue3';
|
|
8
15
|
|
|
9
16
|
const formatSnippet = (code: string, context: StoryContext) => {
|
|
10
17
|
const { args } = context;
|
|
11
18
|
|
|
19
|
+
const argsWithValues: string[] = [`:is-open="${args.isOpen}"`];
|
|
12
20
|
const slotContentIndentationFixed = (args.default as string).replace(/(<\/[^>]+>)$/, `${tab}$1`);
|
|
13
21
|
|
|
22
|
+
if (args.isBackButton !== undefined) argsWithValues.push(`:is-back-button="${args.isBackButton}"`);
|
|
23
|
+
if (args.isDismissible !== undefined) argsWithValues.push(`:is-dismissible="${args.isDismissible}"`);
|
|
24
|
+
if (args.title !== undefined) argsWithValues.push(`title="${args.title}"`);
|
|
25
|
+
if (args.width !== '') argsWithValues.push(`width="${args.width}"`);
|
|
26
|
+
|
|
27
|
+
if (args.handleBack !== undefined) argsWithValues.push(`@back="${args.handleBack}"`);
|
|
28
|
+
if (args.handleClose !== undefined) argsWithValues.push(`@close="${args.handleClose}"`);
|
|
29
|
+
|
|
14
30
|
return (
|
|
15
|
-
`<TideModal
|
|
31
|
+
`<TideModal ${argsWithValues.join(' ')}>${lineBreak}` +
|
|
16
32
|
`${tab}${slotContentIndentationFixed}${lineBreak}${lineBreak}` +
|
|
17
33
|
`${tab}<template #footer>${lineBreak}` +
|
|
18
34
|
`${tab}${tab}${args.footer}${lineBreak}` +
|
|
@@ -35,6 +51,20 @@ const render = (args: any, { updateArgs }: any) => ({
|
|
|
35
51
|
components: { TideButton, TideModal },
|
|
36
52
|
methods: {
|
|
37
53
|
doSomething,
|
|
54
|
+
doSomethingElse,
|
|
55
|
+
handleBack: () => {
|
|
56
|
+
action('Back button pressed')({});
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const callback = eval(args.handleBack);
|
|
60
|
+
|
|
61
|
+
if (callback) {
|
|
62
|
+
callback();
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
alert('Please specify a valid handler in the "back" control.');
|
|
66
|
+
}
|
|
67
|
+
},
|
|
38
68
|
handleClose: () => {
|
|
39
69
|
action('Modal closed')({});
|
|
40
70
|
updateArgs({ ...args, isOpen: false });
|
|
@@ -49,11 +79,14 @@ const render = (args: any, { updateArgs }: any) => ({
|
|
|
49
79
|
alert('Please specify a valid handler in the "close" control.');
|
|
50
80
|
}
|
|
51
81
|
},
|
|
82
|
+
handleOpenModalClick: () => {
|
|
83
|
+
updateArgs({ ...args, isOpen: true });
|
|
84
|
+
},
|
|
52
85
|
},
|
|
53
86
|
setup: () => ({ args }),
|
|
54
87
|
template: `
|
|
55
|
-
<
|
|
56
|
-
<TideModal v-bind="args" @close="handleClose">
|
|
88
|
+
<TideButton label="Open Modal" @click="handleOpenModalClick" />
|
|
89
|
+
<TideModal v-bind="args" @close="handleClose" @back="handleBack">
|
|
57
90
|
${args.default}
|
|
58
91
|
<template #footer>${args.footer}</template>
|
|
59
92
|
</TideModal>`,
|
|
@@ -61,6 +94,7 @@ const render = (args: any, { updateArgs }: any) => ({
|
|
|
61
94
|
|
|
62
95
|
export default {
|
|
63
96
|
argTypes: {
|
|
97
|
+
back: disabledArgType,
|
|
64
98
|
close: disabledArgType,
|
|
65
99
|
default: {
|
|
66
100
|
control: 'text',
|
|
@@ -78,15 +112,40 @@ export default {
|
|
|
78
112
|
type: { summary: 'HTML' },
|
|
79
113
|
},
|
|
80
114
|
},
|
|
115
|
+
handleBack: {
|
|
116
|
+
control: 'text',
|
|
117
|
+
description: "JS function to execute when modal's back button is pressed",
|
|
118
|
+
name: 'back',
|
|
119
|
+
table: {
|
|
120
|
+
category: 'Events',
|
|
121
|
+
defaultValue: { summary: 'None' },
|
|
122
|
+
type: { summary: '() => void' },
|
|
123
|
+
},
|
|
124
|
+
},
|
|
81
125
|
handleClose: {
|
|
82
126
|
control: 'text',
|
|
83
127
|
description: 'JS function to execute when modal is closed',
|
|
84
128
|
name: 'close',
|
|
85
129
|
table: {
|
|
130
|
+
category: 'Events',
|
|
86
131
|
defaultValue: { summary: 'None' },
|
|
87
132
|
type: { summary: '() => void' },
|
|
88
133
|
},
|
|
89
134
|
},
|
|
135
|
+
isBackButton: {
|
|
136
|
+
...argTypeBooleanUnrequired,
|
|
137
|
+
description: 'Determines whether the back button is displayed',
|
|
138
|
+
table: {
|
|
139
|
+
defaultValue: { summary: 'False' },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
isDismissible: {
|
|
143
|
+
...argTypeBooleanUnrequired,
|
|
144
|
+
description: 'Determines whether the close button is displayed',
|
|
145
|
+
table: {
|
|
146
|
+
defaultValue: { summary: 'False' },
|
|
147
|
+
},
|
|
148
|
+
},
|
|
90
149
|
isOpen: {
|
|
91
150
|
description: 'Determines whether the Modal is displayed',
|
|
92
151
|
table: {
|
|
@@ -109,10 +168,13 @@ export default {
|
|
|
109
168
|
args: {
|
|
110
169
|
default: `<div>${lineBreak}${tab}Default Slot Demo${lineBreak}</div>`,
|
|
111
170
|
footer: '<TideButton label="Footer Slot Demo" />',
|
|
112
|
-
|
|
171
|
+
handleBack: 'doSomething',
|
|
172
|
+
handleClose: 'doSomethingElse',
|
|
173
|
+
isBackButton: undefined,
|
|
174
|
+
isDismissible: undefined,
|
|
113
175
|
isOpen: false,
|
|
114
176
|
title: 'Modal Demo',
|
|
115
|
-
width: '
|
|
177
|
+
width: '',
|
|
116
178
|
},
|
|
117
179
|
component: TideModal,
|
|
118
180
|
parameters,
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import TideIcon from '@/components/TideIcon.vue';
|
|
2
|
+
import TidePopover from '@/components/TidePopover.vue';
|
|
3
|
+
import { ICON } from '@/types/Icon';
|
|
4
|
+
import { lineBreak, tab } from '@/utilities/storybook';
|
|
5
|
+
|
|
6
|
+
import type { StoryContext } from '@storybook/vue3';
|
|
7
|
+
|
|
8
|
+
const formatSnippet = (code: string, context: StoryContext) => {
|
|
9
|
+
const { args } = context;
|
|
10
|
+
|
|
11
|
+
const argsWithValues: string[] = [];
|
|
12
|
+
|
|
13
|
+
if (args.anchorId !== '') argsWithValues.push(`anchor-id="${args.anchorId}"`);
|
|
14
|
+
if (args.offset !== '') argsWithValues.push(`:offset="${args.offset}"`);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
`<TideIcon :icon="ICON.INFO" id="anchorDemo" />${lineBreak}${lineBreak}` +
|
|
18
|
+
`<TidePopover ${argsWithValues.join(' ')}>${lineBreak}` +
|
|
19
|
+
`${tab}${args.default}${lineBreak}` +
|
|
20
|
+
`</TidePopover>`
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const parameters = {
|
|
25
|
+
docs: {
|
|
26
|
+
source: {
|
|
27
|
+
format: 'vue',
|
|
28
|
+
language: 'html',
|
|
29
|
+
transform: formatSnippet,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const render = (args: any) => ({
|
|
35
|
+
components: { TideIcon, TidePopover },
|
|
36
|
+
setup: () => {
|
|
37
|
+
if (args.offset === '') {
|
|
38
|
+
delete args.offset;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { ICON, args };
|
|
42
|
+
},
|
|
43
|
+
template: `
|
|
44
|
+
<TideIcon :icon="ICON.INFO" id="anchorDemo" />
|
|
45
|
+
|
|
46
|
+
<TidePopover v-bind="args">
|
|
47
|
+
${args.default}
|
|
48
|
+
</TidePopover>
|
|
49
|
+
`,
|
|
50
|
+
updated: () => {
|
|
51
|
+
if (args.offset === '') {
|
|
52
|
+
delete args.offset;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { ICON, args };
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export default {
|
|
60
|
+
argTypes: {
|
|
61
|
+
anchorId: {
|
|
62
|
+
control: 'text',
|
|
63
|
+
description: 'Determines the HTML node over which the Popover will appear',
|
|
64
|
+
table: {
|
|
65
|
+
defaultValue: { summary: 'None' },
|
|
66
|
+
type: { summary: 'string' },
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
default: {
|
|
70
|
+
control: 'text',
|
|
71
|
+
description: 'Popover content',
|
|
72
|
+
table: {
|
|
73
|
+
defaultValue: { summary: 'None' },
|
|
74
|
+
type: { summary: 'HTML' },
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
offset: {
|
|
78
|
+
control: 'text',
|
|
79
|
+
description: 'Determines spacing between Popover and anchor node',
|
|
80
|
+
table: {
|
|
81
|
+
defaultValue: { summary: '16' },
|
|
82
|
+
type: { summary: 'number' },
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
args: {
|
|
87
|
+
anchorId: 'anchorDemo',
|
|
88
|
+
default: '<span>Demo</span>',
|
|
89
|
+
offset: '',
|
|
90
|
+
},
|
|
91
|
+
component: TidePopover,
|
|
92
|
+
parameters,
|
|
93
|
+
render,
|
|
94
|
+
tags: ['autodocs'],
|
|
95
|
+
title: 'Basic Components/TidePopover',
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const Demo = {};
|
package/src/types/Card.ts
CHANGED
|
@@ -5,10 +5,3 @@ export const TYPE_CARD = {
|
|
|
5
5
|
} as const;
|
|
6
6
|
|
|
7
7
|
export type CardType = (typeof TYPE_CARD)[keyof typeof TYPE_CARD];
|
|
8
|
-
|
|
9
|
-
export const POSITION_CARD_ICON = {
|
|
10
|
-
LEFT: 'left',
|
|
11
|
-
TOP: 'top',
|
|
12
|
-
} as const;
|
|
13
|
-
|
|
14
|
-
export type CardIconPosition = (typeof POSITION_CARD_ICON)[keyof typeof POSITION_CARD_ICON];
|
|
@@ -95,6 +95,10 @@ export const doSomething = () => {
|
|
|
95
95
|
alert('Did something.');
|
|
96
96
|
};
|
|
97
97
|
|
|
98
|
+
export const doSomethingElse = () => {
|
|
99
|
+
alert('Did something else.');
|
|
100
|
+
};
|
|
101
|
+
|
|
98
102
|
// Flatten a nested constant into a simple constant.
|
|
99
103
|
export const flatten = (input: Nested): KeyString => {
|
|
100
104
|
const output: KeyString = {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { nextTick } from 'vue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Directly modifies the `<body>` element to apply or remove scroll lock.
|
|
5
|
+
* When `false` is provided, it only unlocks scroll if there are no open
|
|
6
|
+
* HTML dialog elements.
|
|
7
|
+
*/
|
|
8
|
+
export const setScrollLock = async (isLocked: boolean) => {
|
|
9
|
+
const BODY_LOCK_CLASS = 'body-scroll-lock';
|
|
10
|
+
const body = document.body;
|
|
11
|
+
|
|
12
|
+
if (isLocked) {
|
|
13
|
+
if (!body.dataset.scrollLockY) {
|
|
14
|
+
const scrollY = window.scrollY;
|
|
15
|
+
body.dataset.scrollLockY = scrollY.toString();
|
|
16
|
+
body.style.setProperty('--saved-scroll-y', `${scrollY}px`);
|
|
17
|
+
body.classList.add(BODY_LOCK_CLASS);
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
await nextTick();
|
|
21
|
+
if (!document.querySelector('dialog[open]')) {
|
|
22
|
+
const savedScrollY = parseInt(body.dataset.scrollLockY || '0');
|
|
23
|
+
body.classList.remove(BODY_LOCK_CLASS);
|
|
24
|
+
body.style.removeProperty('--saved-scroll-y');
|
|
25
|
+
window.scrollTo({
|
|
26
|
+
behavior: 'auto',
|
|
27
|
+
top: savedScrollY,
|
|
28
|
+
});
|
|
29
|
+
delete body.dataset.scrollLockY;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const TOP_LAYER_ID = 'tideTopLayer';
|
|
35
|
+
|
|
36
|
+
export const initFauxTopLayer = () => {
|
|
37
|
+
let topLayer = document.getElementById(TOP_LAYER_ID);
|
|
38
|
+
if (!topLayer) {
|
|
39
|
+
topLayer = document.createElement('div');
|
|
40
|
+
topLayer.id = TOP_LAYER_ID;
|
|
41
|
+
document.body.appendChild(topLayer);
|
|
42
|
+
}
|
|
43
|
+
topLayer.style.isolation = 'isolate';
|
|
44
|
+
};
|