Skip to content

Commit b667b29

Browse files
committed
updating eslint settings
refactor: moved reusable code for positioning to its own module adding basic theme support
1 parent f1680d3 commit b667b29

File tree

9 files changed

+325
-140
lines changed

9 files changed

+325
-140
lines changed

.eslintrc.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ module.exports = {
44
"es2021": true
55
},
66
"extends": [
7-
"eslint:recommended",
8-
"plugin:vue/essential",
9-
"plugin:@typescript-eslint/recommended"
7+
"plugin:@typescript-eslint/recommended",
8+
"plugin:vue/vue3-recommended",
109
],
1110
"parserOptions": {
1211
"ecmaVersion": 12,

package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,15 @@
2121
"dev": "vite",
2222
"build": "vite build",
2323
"rollup": "rimraf ./dist && rollup -c",
24+
"lint": "eslint src/**/*.vue",
2425
"lint:css": "stylelint src/**/*.vue"
2526
},
27+
"husky": {
28+
"hooks": {
29+
"pre-commit": "yarn lint:css",
30+
"pre-push": "yarn lint"
31+
}
32+
},
2633
"dependencies": {
2734
"nanoid": "^3.1.12",
2835
"vue": "^3.0.0",
@@ -39,6 +46,7 @@
3946
"@vue/compiler-sfc": "^3.0.0",
4047
"eslint": "^7.10.0",
4148
"eslint-plugin-vue": "^7.0.0-beta.4",
49+
"husky": "^4.3.0",
4250
"rollup": "^2.28.1",
4351
"rollup-plugin-buble": "^0.19.8",
4452
"rollup-plugin-commonjs": "^10.1.0",

src/assets/box.svg

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/components/Menu.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ $shadow: rgba(0, 0, 0, 0.2) 2px 2px 10px 2px;
6363
}
6464

6565
&.selected {
66-
background: $blue;
66+
background: var(--background);
6767
color: $white;
6868
}
6969

src/components/Menu.vue

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
<li
55
v-for="item of menuItems"
66
:key="item.id"
7-
class="menu-list-item"
8-
:class="{selected: item.selected, flip}"
9-
@mousedown="handleMenuItemClick($event, item.id, item.name, item.subMenu)"
7+
:class="[{ selected: item.selected, flip }, 'menu-list-item']"
8+
:style="getTheme"
9+
@mousedown="
10+
handleMenuItemClick($event, item.id, item.name, item.subMenu)
11+
"
1012
>
1113
<span class="name">{{ item.name }}</span>
1214
<span
@@ -31,7 +33,13 @@
3133
</template>
3234

3335
<script lang="ts">
34-
import { defineComponent, PropType, ref, resolveComponent } from "vue";
36+
import {
37+
defineComponent,
38+
PropType,
39+
ref,
40+
resolveComponent,
41+
computed,
42+
} from "vue";
3543
import { nanoid } from "nanoid";
3644
import ChevRightIcon from "./icons/ChevRightIcon.vue";
3745
@@ -62,11 +70,14 @@ export default defineComponent({
6270
default: false,
6371
},
6472
onSelection: {
65-
type: Function as PropType<
66-
(name: string, parent?: string) => void
67-
>,
73+
type: Function as PropType<(name: string, parent?: string) => void>,
6874
default: null,
6975
},
76+
theme: {
77+
type: Object as PropType<{ primary: string }>,
78+
required: false,
79+
default: { primary: "#0080ff" },
80+
},
7081
},
7182
setup(props) {
7283
const menuItems = ref<MenuItem[]>(
@@ -102,10 +113,15 @@ export default defineComponent({
102113
}
103114
};
104115
116+
const getTheme = computed(() => ({
117+
"--background": props.theme.primary,
118+
}));
119+
105120
return {
106121
menuItems,
107122
handleMenuItemClick,
108123
SubMenuComponent,
124+
getTheme,
109125
};
110126
},
111127
});

src/components/index.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ $cubic: cubic-bezier(0.25, 0.46, 0.45, 0.94);
1010

1111
.menu-head {
1212
align-items: center;
13-
background: #0080ff;
13+
background: var(--background);
1414
border-radius: 50%;
1515
cursor: pointer;
1616
display: flex;

src/components/index.vue

Lines changed: 47 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<template>
22
<div
3-
class="menu-head-wrapper"
4-
:class="{dragActive}"
3+
:class="[{ dragActive }, 'menu-head-wrapper']"
54
:draggable="!fixed"
65
:style="style || getInitStyle"
76
@dragstart="handleDragStart"
@@ -10,8 +9,8 @@
109
<div
1110
ref="menuHead"
1211
tabindex="0"
13-
class="menu-head"
14-
:class="{menuActive, dragActive}"
12+
:class="[{ menuActive, dragActive }, 'menu-head']"
13+
:style="getTheme"
1514
@mouseup="toggleMenu"
1615
@mousedown="handleMouseDown"
1716
@blur="handleBlur"
@@ -23,8 +22,7 @@
2322
</div>
2423
<div
2524
ref="menuContainer"
26-
class="menu-container"
27-
:class="{menuActive}"
25+
:class="[{ menuActive }, 'menu-container']"
2826
:style="menuStyle"
2927
@mousedown="handleMenuClick"
3028
>
@@ -39,6 +37,7 @@
3937
:data="menuData"
4038
:flip="flipMenu"
4139
:on-selection="handleMenuItemSelection"
40+
:theme="theme"
4241
/>
4342
</div>
4443
</div>
@@ -57,8 +56,7 @@ import {
5756
import Menu, { MenuItem } from "./Menu.vue";
5857
import XIcon from "./icons/XIcon.vue";
5958
import BoxIcon from "./icons/BoxIcon.vue";
60-
61-
const MENU_SPACE = 10;
59+
import utils from "../utils";
6260
6361
export default defineComponent({
6462
name: "FloatMenu",
@@ -107,6 +105,11 @@ export default defineComponent({
107105
type: Boolean,
108106
default: false,
109107
},
108+
theme: {
109+
type: Object as PropType<{ primary: string }>,
110+
required: false,
111+
default: { primary: "#0080ff" },
112+
},
110113
},
111114
setup(props, { slots }) {
112115
// position of the circular menu head
@@ -138,32 +141,8 @@ export default defineComponent({
138141
139142
// sets the initial style
140143
const getInitStyle = computed(() => {
141-
let left = 0,
142-
top = 0;
143-
switch (props.position) {
144-
case "top left":
145-
left = 20;
146-
top = 20;
147-
break;
148-
case "top right":
149-
left = window.innerWidth - props.dimension;
150-
top = 20;
151-
break;
152-
case "bottom left":
153-
left = 20;
154-
top = window.innerHeight - props.dimension;
155-
break;
156-
case "bottom right":
157-
left = window.innerWidth - props.dimension;
158-
top = window.innerHeight - props.dimension;
159-
}
160-
161-
return {
162-
left: `${left}px`,
163-
top: `${top}px`,
164-
width: `${props.dimension}px`,
165-
height: `${props.dimension}px`,
166-
};
144+
const position = utils.setupInitStyle(props.position, props.dimension);
145+
return position;
167146
});
168147
169148
// compute the style
@@ -187,97 +166,39 @@ export default defineComponent({
187166
const setupMenuOrientation = () => {
188167
const menuContDOM = menuContainer.value as HTMLElement;
189168
const menuHeadDOM = menuHead.value as HTMLElement;
190-
191-
const { top, bottom } = menuHeadDOM.getBoundingClientRect();
192169
const { dimension } = props;
193-
const left = Math.round((menuContDOM.clientWidth - dimension) / 2);
194170
const dir = unref(localMenuOrientation);
195-
const menuHeight = menuContDOM.clientHeight;
196-
197-
let newMenuStyle = null;
198-
199-
// flip to bottom if there is not enough space on top
200-
if (dir === "top" && menuHeight > top) {
201-
newMenuStyle = {
202-
top: `${dimension + MENU_SPACE}px`,
203-
left: `-${left}px`,
204-
};
205-
localMenuOrientation.value = "top";
206-
} else if (dir === "top") {
207-
newMenuStyle = {
208-
bottom: `${dimension + MENU_SPACE}px`,
209-
left: `-${left}px`,
210-
};
211-
// flip menu to top if there is no enough space at bottom
212-
} else if (dir === "bottom" && window.innerHeight - bottom < menuHeight) {
213-
newMenuStyle = {
214-
bottom: `${dimension + MENU_SPACE}px`,
215-
left: `-${left}px`,
216-
};
217-
localMenuOrientation.value = "bottom";
218-
} else if (dir === "bottom") {
219-
newMenuStyle = {
220-
top: `${dimension + MENU_SPACE}px`,
221-
left: `-${left}px`,
222-
};
223-
}
224-
225-
menuStyle.value = Object.assign({}, newMenuStyle, {
226-
"min-height": `${props.menuDimension}px`,
227-
width: `${props.menuDimension}px`,
228-
});
171+
const newStyle = utils.setupMenuOrientation(
172+
menuHeadDOM,
173+
menuContDOM,
174+
dimension,
175+
dir,
176+
props.menuDimension
177+
);
178+
179+
localMenuOrientation.value = newStyle.newOrientation;
180+
menuStyle.value = newStyle;
229181
};
230182
231183
// this function repositions the menu head whenever it goes out of screen edges.
232184
const adjustFloatMenuPosition = (element: HTMLElement) => {
233-
const { top, bottom, left, right } = element.getBoundingClientRect();
234-
const { innerWidth: screenWidth, innerHeight: screenHeight } = window;
235-
const positionValue = unref(position);
236-
const menuContWidth = ((menuContainer.value as unknown) as HTMLElement)
237-
.clientWidth;
238-
const menuContHalfWidth = Math.ceil(menuContWidth / 2);
239-
240-
if (!positionValue) {
241-
return;
242-
}
243-
244-
if (props.flipOnEdges) {
245-
flipMenu.value = false;
246-
}
185+
const positionRef = unref(position);
247186
248-
// resposition if the menuhead goes below the bottom of the viewport
249-
if (bottom > screenHeight) {
250-
position.value = {
251-
left: positionValue.left,
252-
top: positionValue.top - (bottom - screenHeight),
253-
};
254-
}
255-
256-
// resposition if the menuhead goes above the bottom of the viewport
257-
if (top < 0) {
258-
position.value = {
259-
left: positionValue.left,
260-
top: positionValue.top + Math.abs(top),
261-
};
262-
}
263-
264-
// resposition if the menuhead goes beyond the leftside of the viewport
265-
if (left < 0 || left < menuContHalfWidth) {
266-
position.value = {
267-
left: menuContHalfWidth,
268-
top: positionValue.top,
269-
};
187+
if (!positionRef) {
188+
return;
270189
}
271190
272-
// resposition if the menuhead goes beyond the rightside of the viewport
273-
if (right > screenWidth || screenWidth - right < menuContWidth) {
274-
position.value = {
275-
left: screenWidth - menuContWidth,
276-
top: positionValue.top,
277-
};
278-
279-
if (props.flipOnEdges) {
280-
flipMenu.value = true;
191+
if (menuContainer.value) {
192+
const newPosition = utils.setupMenuPosition(
193+
element,
194+
positionRef,
195+
props.flipOnEdges,
196+
menuContainer.value
197+
);
198+
flipMenu.value = newPosition.flip;
199+
200+
if (newPosition.position) {
201+
position.value = newPosition.position;
281202
}
282203
}
283204
};
@@ -334,34 +255,33 @@ export default defineComponent({
334255
});
335256
};
336257
337-
const handleMenuClose = () => {
338-
menuActive.value = false;
339-
};
258+
const handleMenuClose = () => (menuActive.value = false);
259+
const handleBlur = () => (menuActive.value = false);
340260
341261
// close the menu while dragging
342262
const handleDragStart = () => {
343263
menuActive.value = false;
344264
dragActive.value = true;
345265
};
346266
347-
const handleDragEnd = () => {
348-
dragActive.value = false;
349-
};
267+
// set drag active to false
268+
const handleDragEnd = () => (dragActive.value = false);
350269
270+
// handler for selection
351271
const handleMenuItemSelection = (name: string) => {
352272
menuActive.value = false;
353273
props.onSelected && props.onSelected(name);
354274
};
355275
356-
const handleBlur = () => {
357-
menuActive.value = false;
358-
};
359-
360276
const handleMenuClick = (event: MouseEvent) => {
361277
event.preventDefault();
362278
event.stopPropagation();
363279
};
364280
281+
const getTheme = computed(() => ({
282+
"--background": props.theme.primary,
283+
}));
284+
365285
return {
366286
flipMenu,
367287
getInitStyle,
@@ -380,6 +300,7 @@ export default defineComponent({
380300
style,
381301
toggleMenu,
382302
dragActive,
303+
getTheme,
383304
isSlotEmpty: slots && !slots.default,
384305
};
385306
},

0 commit comments

Comments
 (0)