@teammodlr/ui-kit 3.0.0-beta.9 → 3.0.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/lib/components/cardcontextmenu/CardContextMenu.vue.d.ts +4 -2
- package/lib/components/checkbox/Checkbox.vue.d.ts +4 -4
- package/lib/components/clearbutton/ClearButton.vue.d.ts +9 -0
- package/lib/components/clearbutton/style/ClearButton.css.d.ts +2 -0
- package/lib/components/clearbutton/style/index.d.ts +1 -0
- package/lib/components/colorpicker/ColorPicker.vue.d.ts +6 -4
- package/lib/components/colorpicker/style/ColorPicker.css.d.ts +1 -0
- package/lib/components/dialog/Dialog.vue.d.ts +14 -6
- package/lib/components/dialog/style/Dialog.css.d.ts +2 -0
- package/lib/components/drawer/Drawer.vue.d.ts +146 -0
- package/lib/components/drawer/style/Drawer.css.d.ts +33 -0
- package/lib/components/drawer/style/index.d.ts +1 -0
- package/lib/components/dropdown/Dropdown.vue.d.ts +4 -2
- package/lib/components/form/Form.vue.d.ts +4 -2
- package/lib/components/hovercard/HoverCard.vue.d.ts +124 -0
- package/lib/components/hovercard/style/HoverCard.css.d.ts +3 -0
- package/lib/components/hovercard/style/index.d.ts +1 -0
- package/lib/components/index.d.ts +3 -0
- package/lib/components/input/Input.vue.d.ts +20 -5
- package/lib/components/input/style/Input.css.d.ts +4 -0
- package/lib/components/modal/Modal.vue.d.ts +11 -4
- package/lib/components/pagination/Pagination.vue.d.ts +6 -3
- package/lib/components/paneldialog/PanelDialog.vue.d.ts +175 -0
- package/lib/components/paneldialog/style/PanelDialog.css.d.ts +8 -0
- package/lib/components/paneldialog/style/index.d.ts +1 -0
- package/lib/components/popover/Popover.vue.d.ts +28 -2
- package/lib/components/radio/Radio.vue.d.ts +5 -5
- package/lib/components/radiobutton/RadioButton.vue.d.ts +4 -4
- package/lib/components/radiobuttongroup/RadioButtonGroup.vue.d.ts +4 -2
- package/lib/components/select/Select.vue.d.ts +94 -91
- package/lib/components/select/style/Select.css.d.ts +9 -1
- package/lib/components/slider/Slider.vue.d.ts +6 -3
- package/lib/components/switch/Switch.vue.d.ts +4 -4
- package/lib/components/tab/Tab.vue.d.ts +10 -2
- package/lib/components/tab/TabGroup.vue.d.ts +6 -3
- package/lib/components/table/Table.vue.d.ts +4 -2
- package/lib/components/textarea/Textarea.vue.d.ts +16 -5
- package/lib/components/textarea/style/Textarea.css.d.ts +2 -0
- package/lib/components/tree/Tree.vue.d.ts +6 -3
- package/lib/composables/useInput.d.ts +9 -2
- package/lib/index.cjs.js +3 -3
- package/lib/index.es.js +8526 -6393
- package/lib/style.css +1 -1
- package/lib/tsconfig.app.tsbuildinfo +1 -0
- package/lib/tsconfig.vitest.tsbuildinfo +1 -0
- package/package.json +7 -4
- package/src/components/cardcontextmenu/CardContextMenu.vue +3 -1
- package/src/components/cardcontextmenu/style/Dropdown.css.ts +7 -0
- package/src/components/clearbutton/ClearButton.vue +41 -0
- package/src/components/clearbutton/style/ClearButton.css.ts +37 -0
- package/src/components/clearbutton/style/index.ts +1 -0
- package/src/components/colorpicker/ColorPicker.vue +12 -12
- package/src/components/colorpicker/examples/basic.vue +8 -17
- package/src/components/colorpicker/style/ColorPicker.css.ts +6 -1
- package/src/components/dialog/Dialog.vue +12 -6
- package/src/components/dialog/style/Dialog.css.ts +30 -0
- package/src/components/drawer/Drawer.vue +280 -0
- package/src/components/drawer/examples/basic.vue +15 -0
- package/src/components/drawer/examples/custom-title.vue +21 -0
- package/src/components/drawer/examples/index.stories.ts +24 -0
- package/src/components/drawer/examples/index.ts +7 -0
- package/src/components/drawer/examples/no-mask.vue +15 -0
- package/src/components/drawer/examples/no-outside-close.vue +15 -0
- package/src/components/drawer/examples/placements.vue +28 -0
- package/src/components/drawer/examples/with-footer.vue +21 -0
- package/src/components/drawer/examples/with-trigger.vue +13 -0
- package/src/components/drawer/style/Drawer.css.ts +208 -0
- package/src/components/drawer/style/index.ts +1 -0
- package/src/components/dropdown/Dropdown.vue +4 -2
- package/src/components/form/Form.vue +3 -3
- package/src/components/hovercard/HoverCard.vue +165 -0
- package/src/components/hovercard/style/HoverCard.css.ts +175 -0
- package/src/components/hovercard/style/index.ts +1 -0
- package/src/components/index.ts +3 -0
- package/src/components/input/Input.vue +52 -4
- package/src/components/input/examples/clearable.vue +18 -0
- package/src/components/input/examples/index.stories.ts +3 -1
- package/src/components/input/examples/index.ts +1 -0
- package/src/components/input/style/Input.css.ts +19 -0
- package/src/components/messageprovider/MessageProvider.vue +33 -7
- package/src/components/modal/Modal.vue +28 -11
- package/src/components/pagination/Pagination.vue +4 -1
- package/src/components/paneldialog/PanelDialog.vue +509 -0
- package/src/components/paneldialog/examples/basic.vue +36 -0
- package/src/components/paneldialog/examples/index.stories.ts +22 -0
- package/src/components/paneldialog/examples/index.ts +2 -0
- package/src/components/paneldialog/examples/modal.vue +31 -0
- package/src/components/paneldialog/style/PanelDialog.css.ts +90 -0
- package/src/components/paneldialog/style/index.ts +1 -0
- package/src/components/popover/Popover.vue +9 -2
- package/src/components/radiobuttongroup/RadioButtonGroup.vue +3 -1
- package/src/components/select/Select.vue +211 -111
- package/src/components/select/examples/clearable.vue +18 -0
- package/src/components/select/examples/fill.vue +52 -0
- package/src/components/select/examples/index.stories.ts +4 -1
- package/src/components/select/examples/index.ts +3 -0
- package/src/components/select/examples/remote.vue +54 -0
- package/src/components/select/style/Select.css.ts +140 -24
- package/src/components/slider/Slider.vue +4 -1
- package/src/components/tab/Tab.vue +4 -2
- package/src/components/tab/TabGroup.vue +4 -4
- package/src/components/tab/style/Tab.css.ts +6 -0
- package/src/components/table/Table.vue +3 -3
- package/src/components/textarea/Textarea.vue +40 -4
- package/src/components/textarea/examples/clearable.vue +16 -0
- package/src/components/textarea/examples/index.stories.ts +2 -1
- package/src/components/textarea/examples/index.ts +1 -0
- package/src/components/textarea/style/Textarea.css.ts +10 -0
- package/src/components/tree/Tree.vue +4 -1
- package/src/composables/useInput.ts +10 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teammodlr/ui-kit",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.cjs.js",
|
|
6
6
|
"module": "lib/index.es.js",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"exports": {
|
|
14
14
|
".": {
|
|
15
|
+
"types": "./lib/index.d.ts",
|
|
15
16
|
"import": "./lib/index.es.js",
|
|
16
17
|
"require": "./lib/index.cjs.js"
|
|
17
18
|
},
|
|
@@ -30,9 +31,10 @@
|
|
|
30
31
|
"build:vite": "vite build",
|
|
31
32
|
"build:storybook": "storybook build",
|
|
32
33
|
"gen-types": "vue-tsc --declaration --emitDeclarationOnly -p tsconfig.vitest.json --composite false",
|
|
34
|
+
"test:types": "npm run gen-types && vue-tsc --noEmit -p type-tests/tsconfig.json",
|
|
33
35
|
"release:prepare": "npm run build",
|
|
34
36
|
"release": "bumpp --execute=\"npm run release:prepare\" --all",
|
|
35
|
-
"release:publish": "npm run release && npm publish",
|
|
37
|
+
"release:publish": "npm run release && npm publish --tag latest",
|
|
36
38
|
"build:watch": "concurrently \"vite build --watch\" \"vue-tsc --declaration --emitDeclarationOnly -p tsconfig.vitest.json --composite false --watch\""
|
|
37
39
|
},
|
|
38
40
|
"keywords": [],
|
|
@@ -63,9 +65,9 @@
|
|
|
63
65
|
"ts-node": "^10.9.2",
|
|
64
66
|
"typescript": "^5.5.3",
|
|
65
67
|
"vite": "^5.3.3",
|
|
68
|
+
"vue": "^3.4.31",
|
|
66
69
|
"vue-tsc": "^2.0.26",
|
|
67
|
-
"vueuc": "^0.4.58"
|
|
68
|
-
"vue": "^3.4.31"
|
|
70
|
+
"vueuc": "^0.4.58"
|
|
69
71
|
},
|
|
70
72
|
"dependencies": {
|
|
71
73
|
"@floating-ui/vue": "^1.1.1",
|
|
@@ -77,6 +79,7 @@
|
|
|
77
79
|
"maska": "^2.1.11",
|
|
78
80
|
"reka-ui": "^2.9.3",
|
|
79
81
|
"treemate": "^0.3.11",
|
|
82
|
+
"virtua": "^0.49.1",
|
|
80
83
|
"vooks": "^0.2.12",
|
|
81
84
|
"vue-sonner": "^2.0.9"
|
|
82
85
|
},
|
|
@@ -124,7 +124,9 @@ export default defineComponent({
|
|
|
124
124
|
type: Number
|
|
125
125
|
}
|
|
126
126
|
},
|
|
127
|
-
emits:
|
|
127
|
+
emits: {
|
|
128
|
+
'update:open': (_value: boolean) => true
|
|
129
|
+
},
|
|
128
130
|
setup(props, { emit }) {
|
|
129
131
|
const parentState = inject(CARDCONTEXTMENU_STATE, null);
|
|
130
132
|
const { nextZIndex } = useZIndex();
|
|
@@ -189,6 +189,7 @@ export const menuItem = style({
|
|
|
189
189
|
cursor: 'pointer',
|
|
190
190
|
transition: 'background-color 0.14s ease',
|
|
191
191
|
textDecoration: 'none',
|
|
192
|
+
outline: 'none',
|
|
192
193
|
':hover': {
|
|
193
194
|
vars: {
|
|
194
195
|
[vars.backgroundColor]: themeVars.color.gray[100],
|
|
@@ -196,6 +197,12 @@ export const menuItem = style({
|
|
|
196
197
|
},
|
|
197
198
|
backgroundColor: vars.backgroundColor,
|
|
198
199
|
color: vars.color
|
|
200
|
+
},
|
|
201
|
+
':focus': {
|
|
202
|
+
outline: 'none'
|
|
203
|
+
},
|
|
204
|
+
':focus-visible': {
|
|
205
|
+
outline: 'none'
|
|
199
206
|
}
|
|
200
207
|
});
|
|
201
208
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
tabindex="-1"
|
|
5
|
+
aria-label="Clear"
|
|
6
|
+
:class="style.clearButton"
|
|
7
|
+
@mousedown.prevent
|
|
8
|
+
@click.stop.prevent="$emit('clear', $event)"
|
|
9
|
+
@keydown.stop
|
|
10
|
+
>
|
|
11
|
+
<svg
|
|
12
|
+
:class="style.icon"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
viewBox="0 0 24 24"
|
|
15
|
+
fill="none"
|
|
16
|
+
stroke="currentColor"
|
|
17
|
+
stroke-width="2.25"
|
|
18
|
+
stroke-linecap="round"
|
|
19
|
+
stroke-linejoin="round"
|
|
20
|
+
aria-hidden="true"
|
|
21
|
+
>
|
|
22
|
+
<path d="M18 6 6 18" />
|
|
23
|
+
<path d="m6 6 12 12" />
|
|
24
|
+
</svg>
|
|
25
|
+
</button>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script lang="ts">
|
|
29
|
+
import { defineComponent } from 'vue';
|
|
30
|
+
import * as style from './style';
|
|
31
|
+
|
|
32
|
+
export default defineComponent({
|
|
33
|
+
name: 'MClearButton',
|
|
34
|
+
emits: {
|
|
35
|
+
clear: (_e: MouseEvent) => true
|
|
36
|
+
},
|
|
37
|
+
setup() {
|
|
38
|
+
return { style };
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
</script>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { style } from '@vanilla-extract/css';
|
|
2
|
+
import { themeVars } from '../../../styles/theme.css';
|
|
3
|
+
|
|
4
|
+
export const clearButton = style({
|
|
5
|
+
display: 'inline-flex',
|
|
6
|
+
alignItems: 'center',
|
|
7
|
+
justifyContent: 'center',
|
|
8
|
+
flexShrink: 0,
|
|
9
|
+
width: themeVars.space[6],
|
|
10
|
+
height: themeVars.space[6],
|
|
11
|
+
padding: 0,
|
|
12
|
+
border: 'none',
|
|
13
|
+
borderRadius: '9999px',
|
|
14
|
+
background: 'transparent',
|
|
15
|
+
color: themeVars.color.gray[400],
|
|
16
|
+
cursor: 'pointer',
|
|
17
|
+
outline: 'none',
|
|
18
|
+
transition: `color .15s ${themeVars.transition.default},
|
|
19
|
+
background-color .15s ${themeVars.transition.default}`,
|
|
20
|
+
':hover': {
|
|
21
|
+
color: themeVars.color.gray[700],
|
|
22
|
+
backgroundColor: themeVars.color.gray[100]
|
|
23
|
+
},
|
|
24
|
+
':focus-visible': {
|
|
25
|
+
color: themeVars.color.gray[700],
|
|
26
|
+
boxShadow: `0 0 0 2px ${themeVars.color.blue[200]}`
|
|
27
|
+
},
|
|
28
|
+
':active': {
|
|
29
|
+
backgroundColor: themeVars.color.gray[200]
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const icon = style({
|
|
34
|
+
width: themeVars.space[4],
|
|
35
|
+
height: themeVars.space[4],
|
|
36
|
+
pointerEvents: 'none'
|
|
37
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ClearButton.css';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div ref="wrapper" class="m-color-picker">
|
|
3
|
-
<PopoverRoot v-model:open="isOpen"
|
|
3
|
+
<PopoverRoot v-model:open="isOpen">
|
|
4
4
|
<PopoverTrigger as-child>
|
|
5
|
-
<div ref="referenceRef">
|
|
5
|
+
<div ref="referenceRef" tabindex="0" @mousedown.prevent @focus="isOpen = true" :class="style.trigger">
|
|
6
6
|
<slot :r="finalColor.r" :g="finalColor.g" :b="finalColor.b" :a="finalColor.a" :hex="hexInput"
|
|
7
7
|
:transparent="style.transparent">
|
|
8
8
|
<div :class="style.reference">
|
|
@@ -147,7 +147,7 @@ export default defineComponent({
|
|
|
147
147
|
},
|
|
148
148
|
transfer: {
|
|
149
149
|
type: Boolean,
|
|
150
|
-
default:
|
|
150
|
+
default: true
|
|
151
151
|
},
|
|
152
152
|
placement: {
|
|
153
153
|
type: String as PropType<Placement>,
|
|
@@ -162,7 +162,10 @@ export default defineComponent({
|
|
|
162
162
|
default: "#FFFFFF"
|
|
163
163
|
}
|
|
164
164
|
},
|
|
165
|
-
emits:
|
|
165
|
+
emits: {
|
|
166
|
+
'update:modelValue': (_value: string) => true,
|
|
167
|
+
onColorChange: (_value: string) => true
|
|
168
|
+
},
|
|
166
169
|
components: {
|
|
167
170
|
MSlider,
|
|
168
171
|
MInput,
|
|
@@ -441,15 +444,13 @@ export default defineComponent({
|
|
|
441
444
|
updateFromHex();
|
|
442
445
|
};
|
|
443
446
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
if (wasOpen && !value) {
|
|
447
|
+
watch(isOpen, (open, wasOpen) => {
|
|
448
|
+
console.log('[ColorPicker] isOpen watch:', { wasOpen, open, hexInput: hexInput.value });
|
|
449
|
+
if (wasOpen && !open) {
|
|
450
|
+
console.log('[ColorPicker] Emitting onColorChange:', hexInput.value);
|
|
450
451
|
emit('onColorChange', hexInput.value);
|
|
451
452
|
}
|
|
452
|
-
};
|
|
453
|
+
});
|
|
453
454
|
|
|
454
455
|
|
|
455
456
|
return {
|
|
@@ -485,7 +486,6 @@ export default defineComponent({
|
|
|
485
486
|
transfer,
|
|
486
487
|
canPickColor,
|
|
487
488
|
openEyeDropper,
|
|
488
|
-
handleOpenChange,
|
|
489
489
|
onSliderChangeComplete,
|
|
490
490
|
hexInputUser,
|
|
491
491
|
toggleInherit,
|
|
@@ -12,22 +12,13 @@
|
|
|
12
12
|
</div>
|
|
13
13
|
</template>
|
|
14
14
|
|
|
15
|
-
<script>
|
|
16
|
-
|
|
17
|
-
data() {
|
|
18
|
-
return {
|
|
19
|
-
colorValue: 'rgba(0,0,0,50%)' // Initial color value
|
|
20
|
-
};
|
|
21
|
-
},
|
|
22
|
-
setup() {
|
|
23
|
-
const updateColor = (color) => {
|
|
24
|
-
console.log(color);
|
|
25
|
-
debugger;
|
|
26
|
-
}
|
|
15
|
+
<script setup>
|
|
16
|
+
import { ref } from 'vue';
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
const colorValue = ref('rgba(0,0,0,50%)');
|
|
19
|
+
|
|
20
|
+
function updateColor(color) {
|
|
21
|
+
console.log(color);
|
|
22
|
+
colorValue.value = color;
|
|
23
|
+
}
|
|
33
24
|
</script>
|
|
@@ -72,6 +72,10 @@ export const colours = style({
|
|
|
72
72
|
cursor: 'crosshair'
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
+
export const trigger = style({
|
|
76
|
+
outline: 'none'
|
|
77
|
+
});
|
|
78
|
+
|
|
75
79
|
export const reference = style({
|
|
76
80
|
backgroundImage:
|
|
77
81
|
'linear-gradient(45deg, #bebebe 25%, transparent 25%), linear-gradient(-45deg, #bebebe 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #bebebe 75%), linear-gradient(-45deg, transparent 75%, #bebebe 75%)',
|
|
@@ -85,7 +89,8 @@ export const reference = style({
|
|
|
85
89
|
borderRadius: themeVars.rounded.default,
|
|
86
90
|
position: 'relative',
|
|
87
91
|
cursor: 'pointer',
|
|
88
|
-
userSelect: 'none'
|
|
92
|
+
userSelect: 'none',
|
|
93
|
+
outline: 'none'
|
|
89
94
|
});
|
|
90
95
|
|
|
91
96
|
export const transparent = style({
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
v-if="isOpen"
|
|
14
14
|
ref="dialog"
|
|
15
15
|
force-mount
|
|
16
|
+
:class="style.dialogContent"
|
|
16
17
|
:style="computedStyle"
|
|
17
18
|
:disable-outside-pointer-events="modal"
|
|
18
19
|
@open-auto-focus="handleOpenAutoFocus"
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
@focus-outside="handleFocusOutside"
|
|
22
23
|
@interact-outside.prevent
|
|
23
24
|
>
|
|
24
|
-
<FocusScope :trapped="focusTrap" :loop="focusTrap">
|
|
25
|
+
<FocusScope :trapped="focusTrap" :loop="focusTrap" :class="style.focusScope">
|
|
25
26
|
<slot v-if="$slots.container" name="container" :close="close"></slot>
|
|
26
27
|
<template v-else>
|
|
27
28
|
<div :class="style.content">
|
|
@@ -126,7 +127,15 @@ export default defineComponent({
|
|
|
126
127
|
},
|
|
127
128
|
},
|
|
128
129
|
// Eventually remove 'open', i lazy cant be assed for now
|
|
129
|
-
emits:
|
|
130
|
+
emits: {
|
|
131
|
+
onOpen: () => true,
|
|
132
|
+
onClose: () => true,
|
|
133
|
+
'update:open': (_value: boolean) => true,
|
|
134
|
+
open: () => true,
|
|
135
|
+
close: () => true,
|
|
136
|
+
// Fired once the leave animation has finished and the dialog is out of the DOM
|
|
137
|
+
'after-leave': () => true
|
|
138
|
+
},
|
|
130
139
|
setup(props, { emit, expose }) {
|
|
131
140
|
const wrapper = ref<HTMLElement | null>(null);
|
|
132
141
|
const dialog = shallowRef<HTMLElement | ComponentPublicInstance | null>(null);
|
|
@@ -230,6 +239,7 @@ export default defineComponent({
|
|
|
230
239
|
|
|
231
240
|
function handleAfterLeave() {
|
|
232
241
|
emit('close');
|
|
242
|
+
emit('after-leave');
|
|
233
243
|
}
|
|
234
244
|
|
|
235
245
|
function onCloseEmit() {
|
|
@@ -246,10 +256,6 @@ export default defineComponent({
|
|
|
246
256
|
}
|
|
247
257
|
|
|
248
258
|
function handleFocusOutside(event: Event) {
|
|
249
|
-
if (!props.focusTrap) {
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
259
|
event.preventDefault();
|
|
254
260
|
}
|
|
255
261
|
|
|
@@ -72,6 +72,36 @@ export const content = style({
|
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
+
// DialogContent chrome ([role="dialog"]). Covers both the default content
|
|
76
|
+
// above and custom `#container` panels, which bypass `content`.
|
|
77
|
+
// Scoped to the chrome element itself so interactive descendants
|
|
78
|
+
// (inputs, buttons) keep their own focus rings.
|
|
79
|
+
export const dialogContent = style({
|
|
80
|
+
outline: 'none',
|
|
81
|
+
selectors: {
|
|
82
|
+
'&:focus': {
|
|
83
|
+
outline: 'none'
|
|
84
|
+
},
|
|
85
|
+
'&:focus-visible': {
|
|
86
|
+
outline: 'none'
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Inner FocusScope wrapper ([tabindex="-1"]) rendered inside DialogContent.
|
|
92
|
+
// Suppresses the detached black outline seen on drag mousedown / Tab wrap.
|
|
93
|
+
export const focusScope = style({
|
|
94
|
+
outline: 'none',
|
|
95
|
+
selectors: {
|
|
96
|
+
'&:focus': {
|
|
97
|
+
outline: 'none'
|
|
98
|
+
},
|
|
99
|
+
'&:focus-visible': {
|
|
100
|
+
outline: 'none'
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
75
105
|
export const header = style({
|
|
76
106
|
color: themeVars.color.font.default,
|
|
77
107
|
backgroundColor: '#FFF',
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<DialogRoot :open="isOpen" modal @update:open="handleOpenChange">
|
|
3
|
+
<DialogTrigger v-if="hasTrigger" as-child>
|
|
4
|
+
<slot name="trigger" />
|
|
5
|
+
</DialogTrigger>
|
|
6
|
+
<DialogPortal to="body">
|
|
7
|
+
<Transition
|
|
8
|
+
appear
|
|
9
|
+
type="animation"
|
|
10
|
+
:enter-active-class="fade.enterActive"
|
|
11
|
+
:leave-active-class="fade.leaveActive"
|
|
12
|
+
:enter-from-class="fade.enterFrom"
|
|
13
|
+
:leave-to-class="fade.leaveTo"
|
|
14
|
+
>
|
|
15
|
+
<DialogOverlay
|
|
16
|
+
v-if="isOpen && showMask"
|
|
17
|
+
force-mount
|
|
18
|
+
:class="style.mask"
|
|
19
|
+
:style="{ zIndex }"
|
|
20
|
+
/>
|
|
21
|
+
</Transition>
|
|
22
|
+
<Transition
|
|
23
|
+
appear
|
|
24
|
+
type="animation"
|
|
25
|
+
:enter-active-class="currentTransition.enterActive"
|
|
26
|
+
:leave-active-class="currentTransition.leaveActive"
|
|
27
|
+
:enter-from-class="currentTransition.enterFrom"
|
|
28
|
+
:leave-to-class="currentTransition.leaveTo"
|
|
29
|
+
>
|
|
30
|
+
<DialogContent
|
|
31
|
+
v-if="isOpen"
|
|
32
|
+
force-mount
|
|
33
|
+
:class="[style.placement[placement]]"
|
|
34
|
+
:style="contentStyle"
|
|
35
|
+
:disable-outside-pointer-events="true"
|
|
36
|
+
@open-auto-focus="handleOpenAutoFocus"
|
|
37
|
+
@escape-key-down="onEscapeKeyDown"
|
|
38
|
+
@pointer-down-outside="onPointerDownOutside"
|
|
39
|
+
@interact-outside="onInteractOutside"
|
|
40
|
+
>
|
|
41
|
+
<FocusScope :trapped="focusTrap" :loop="focusTrap">
|
|
42
|
+
<div v-if="hasTitle || showClose" :class="style.header">
|
|
43
|
+
<DialogTitle as-child>
|
|
44
|
+
<div :class="style.title">
|
|
45
|
+
<slot name="title">{{ title }}</slot>
|
|
46
|
+
</div>
|
|
47
|
+
</DialogTitle>
|
|
48
|
+
<DialogClose v-if="showClose" as-child>
|
|
49
|
+
<button :class="style.close" aria-label="Close" />
|
|
50
|
+
</DialogClose>
|
|
51
|
+
</div>
|
|
52
|
+
<div :class="style.body" ref="bodyRef">
|
|
53
|
+
<slot />
|
|
54
|
+
</div>
|
|
55
|
+
<div v-if="hasFooter" :class="style.footer">
|
|
56
|
+
<slot name="footer" />
|
|
57
|
+
</div>
|
|
58
|
+
</FocusScope>
|
|
59
|
+
</DialogContent>
|
|
60
|
+
</Transition>
|
|
61
|
+
</DialogPortal>
|
|
62
|
+
</DialogRoot>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<script lang="ts">
|
|
66
|
+
import type { CSSProperties, PropType } from 'vue';
|
|
67
|
+
import {
|
|
68
|
+
DialogClose,
|
|
69
|
+
DialogContent,
|
|
70
|
+
DialogOverlay,
|
|
71
|
+
DialogPortal,
|
|
72
|
+
DialogRoot,
|
|
73
|
+
DialogTitle,
|
|
74
|
+
DialogTrigger,
|
|
75
|
+
FocusScope
|
|
76
|
+
} from 'reka-ui';
|
|
77
|
+
import { computed, defineComponent, provide, ref, watch } from 'vue';
|
|
78
|
+
import * as style from './style';
|
|
79
|
+
import { fade } from '../../styles/transitions.css';
|
|
80
|
+
import { useZIndex, ZINDEX } from '../../composables/useZIndex';
|
|
81
|
+
|
|
82
|
+
type Placement = 'left' | 'right' | 'top' | 'bottom';
|
|
83
|
+
|
|
84
|
+
export default defineComponent({
|
|
85
|
+
name: 'MDrawer',
|
|
86
|
+
components: {
|
|
87
|
+
DialogClose,
|
|
88
|
+
DialogContent,
|
|
89
|
+
DialogOverlay,
|
|
90
|
+
DialogPortal,
|
|
91
|
+
DialogRoot,
|
|
92
|
+
DialogTitle,
|
|
93
|
+
DialogTrigger,
|
|
94
|
+
FocusScope
|
|
95
|
+
},
|
|
96
|
+
props: {
|
|
97
|
+
open: {
|
|
98
|
+
type: Boolean,
|
|
99
|
+
default: undefined
|
|
100
|
+
},
|
|
101
|
+
placement: {
|
|
102
|
+
type: String as PropType<Placement>,
|
|
103
|
+
default: 'right'
|
|
104
|
+
},
|
|
105
|
+
width: {
|
|
106
|
+
type: [String, Number],
|
|
107
|
+
default: '378px'
|
|
108
|
+
},
|
|
109
|
+
height: {
|
|
110
|
+
type: [String, Number],
|
|
111
|
+
default: '378px'
|
|
112
|
+
},
|
|
113
|
+
title: {
|
|
114
|
+
type: String,
|
|
115
|
+
default: ''
|
|
116
|
+
},
|
|
117
|
+
showClose: {
|
|
118
|
+
type: Boolean,
|
|
119
|
+
default: true
|
|
120
|
+
},
|
|
121
|
+
showMask: {
|
|
122
|
+
type: Boolean,
|
|
123
|
+
default: true
|
|
124
|
+
},
|
|
125
|
+
outsideClose: {
|
|
126
|
+
type: Boolean,
|
|
127
|
+
default: true
|
|
128
|
+
},
|
|
129
|
+
focusTrap: {
|
|
130
|
+
type: Boolean,
|
|
131
|
+
default: true
|
|
132
|
+
},
|
|
133
|
+
zIndex: {
|
|
134
|
+
type: Number
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
emits: {
|
|
138
|
+
open: () => true,
|
|
139
|
+
close: (_e?: MouseEvent | KeyboardEvent) => true,
|
|
140
|
+
'update:open': (_value: boolean) => true
|
|
141
|
+
},
|
|
142
|
+
setup(props, { emit, slots }) {
|
|
143
|
+
const { nextZIndex } = useZIndex();
|
|
144
|
+
const zIndex = ref(props.zIndex ?? nextZIndex());
|
|
145
|
+
provide(ZINDEX, zIndex);
|
|
146
|
+
|
|
147
|
+
const bodyRef = ref<HTMLElement | null>(null);
|
|
148
|
+
const hasFooter = computed(() => !!slots.footer);
|
|
149
|
+
const hasTitle = computed(() => !!slots.title || !!props.title);
|
|
150
|
+
const hasTrigger = computed(() => !!slots.trigger);
|
|
151
|
+
|
|
152
|
+
const internalOpen = ref(false);
|
|
153
|
+
|
|
154
|
+
const isOpen = computed({
|
|
155
|
+
get: () => {
|
|
156
|
+
if (props.open !== undefined) return props.open;
|
|
157
|
+
return internalOpen.value;
|
|
158
|
+
},
|
|
159
|
+
set: (value: boolean) => {
|
|
160
|
+
if (props.open !== undefined) {
|
|
161
|
+
emit('update:open', value);
|
|
162
|
+
} else {
|
|
163
|
+
internalOpen.value = value;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const isHorizontal = computed(() =>
|
|
169
|
+
props.placement === 'left' || props.placement === 'right'
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const contentStyle = computed<CSSProperties>(() => {
|
|
173
|
+
const s: CSSProperties = { zIndex: zIndex.value };
|
|
174
|
+
if (isHorizontal.value) {
|
|
175
|
+
const w = props.width;
|
|
176
|
+
s.width = typeof w === 'number' ? `${w}px` : w;
|
|
177
|
+
} else {
|
|
178
|
+
const h = props.height;
|
|
179
|
+
s.height = typeof h === 'number' ? `${h}px` : h;
|
|
180
|
+
}
|
|
181
|
+
return s;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const currentTransition = computed(() => style.slideTransition[props.placement]);
|
|
185
|
+
|
|
186
|
+
const focusableSelector = [
|
|
187
|
+
'[autofocus]',
|
|
188
|
+
'input:not([type="hidden"])',
|
|
189
|
+
'textarea',
|
|
190
|
+
'select',
|
|
191
|
+
'[contenteditable="true"]',
|
|
192
|
+
'button',
|
|
193
|
+
'a[href]',
|
|
194
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
195
|
+
].join(', ');
|
|
196
|
+
|
|
197
|
+
function getFirstFocusable() {
|
|
198
|
+
if (!bodyRef.value) return null;
|
|
199
|
+
return Array.from(
|
|
200
|
+
bodyRef.value.querySelectorAll<HTMLElement>(focusableSelector)
|
|
201
|
+
).find(el =>
|
|
202
|
+
!el.matches(':disabled') &&
|
|
203
|
+
el.getAttribute('aria-disabled') !== 'true' &&
|
|
204
|
+
el.getAttribute('aria-hidden') !== 'true' &&
|
|
205
|
+
el.getClientRects().length > 0
|
|
206
|
+
) ?? null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const handleOpenAutoFocus = (e: Event) => {
|
|
210
|
+
const first = getFirstFocusable();
|
|
211
|
+
if (first) {
|
|
212
|
+
e.preventDefault();
|
|
213
|
+
first.focus();
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const close = (e: MouseEvent | KeyboardEvent | undefined) => {
|
|
218
|
+
emit('close', e);
|
|
219
|
+
isOpen.value = false;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const onEscapeKeyDown = (e: KeyboardEvent) => {
|
|
223
|
+
e.stopPropagation();
|
|
224
|
+
close(e);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const onPointerDownOutside = (e: Event) => {
|
|
228
|
+
if (!props.outsideClose) e.preventDefault();
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const onInteractOutside = (e: Event) => {
|
|
232
|
+
if (!props.outsideClose) {
|
|
233
|
+
e.preventDefault();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
close(e as MouseEvent | KeyboardEvent);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const handleOpenChange = (value: boolean) => {
|
|
240
|
+
if (!value && isOpen.value) {
|
|
241
|
+
close(undefined);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
isOpen.value = value;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
watch(
|
|
248
|
+
() => isOpen.value,
|
|
249
|
+
(next, prev) => {
|
|
250
|
+
if (next && !prev) {
|
|
251
|
+
zIndex.value = props.zIndex ?? nextZIndex();
|
|
252
|
+
emit('open');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const open = () => { isOpen.value = true; };
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
style,
|
|
261
|
+
fade,
|
|
262
|
+
isOpen,
|
|
263
|
+
zIndex,
|
|
264
|
+
hasFooter,
|
|
265
|
+
hasTitle,
|
|
266
|
+
hasTrigger,
|
|
267
|
+
bodyRef,
|
|
268
|
+
contentStyle,
|
|
269
|
+
currentTransition,
|
|
270
|
+
open,
|
|
271
|
+
close,
|
|
272
|
+
handleOpenAutoFocus,
|
|
273
|
+
handleOpenChange,
|
|
274
|
+
onEscapeKeyDown,
|
|
275
|
+
onPointerDownOutside,
|
|
276
|
+
onInteractOutside
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
</script>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<MDrawer ref="drawer" title="Basic Drawer">
|
|
3
|
+
<p style="margin: 0">This is the drawer body. It slides in from the right by default.</p>
|
|
4
|
+
</MDrawer>
|
|
5
|
+
|
|
6
|
+
<MButton @click="drawer?.open()">Open Drawer</MButton>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script lang="ts" setup>
|
|
10
|
+
import { ref } from 'vue';
|
|
11
|
+
import MDrawer from '../Drawer.vue';
|
|
12
|
+
import MButton from '../../button/Button.vue';
|
|
13
|
+
|
|
14
|
+
const drawer = ref<InstanceType<typeof MDrawer> | null>(null);
|
|
15
|
+
</script>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<MDrawer ref="drawer">
|
|
3
|
+
<template #title>
|
|
4
|
+
<span style="display: flex; align-items: center; gap: 6px">
|
|
5
|
+
<span style="font-size: 1.1em">⚙️</span>
|
|
6
|
+
Settings
|
|
7
|
+
</span>
|
|
8
|
+
</template>
|
|
9
|
+
<p style="margin: 0">Custom title slot with an icon.</p>
|
|
10
|
+
</MDrawer>
|
|
11
|
+
|
|
12
|
+
<MButton @click="drawer?.open()">Open Drawer</MButton>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script lang="ts" setup>
|
|
16
|
+
import { ref } from 'vue';
|
|
17
|
+
import MDrawer from '../Drawer.vue';
|
|
18
|
+
import MButton from '../../button/Button.vue';
|
|
19
|
+
|
|
20
|
+
const drawer = ref<InstanceType<typeof MDrawer> | null>(null);
|
|
21
|
+
</script>
|