Skip to content

Commit aa5a4c9

Browse files
Feature/lite 29476 create menu component (#48)
* LITE-29476 add menu component * LITE-29477 add story to menu component
1 parent b8b99c0 commit aa5a4c9

File tree

6 files changed

+170
-2
lines changed

6 files changed

+170
-2
lines changed
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
border-color = #e0e0e0;
1+
border-color = #e0e0e0;
2+
base-text-color = #212121;

components/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export { default as Textfield } from '~widgets/textfield/widget.vue';
1414
export { default as Table } from '~widgets/table/widget.vue';
1515
export { default as ComplexTable } from './widgets/complexTable/widget.vue';
1616
export { default as Button } from '~widgets/button/widget.vue';
17+
export { default as Menu } from '~widgets/menu/widget.vue';
1718

1819
export { default as store } from '~core/store';
1920
export { default as bus } from '~core/eventBus';
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Menu from '~widgets/menu/widget.vue';
2+
import Button from '~widgets/button/widget.vue';
3+
import registerWidget from '~core/registerWidget';
4+
5+
registerWidget('ui-menu', Menu);
6+
registerWidget('ui-button', Button);
7+
8+
export const Component = {
9+
render: (args) => ({
10+
setup() {
11+
return {args};
12+
},
13+
template: `<ui-menu>
14+
<ui-button
15+
slot="trigger"
16+
text="open menu"
17+
/>
18+
<div style="padding:8px 16px; width:300px; border:1px solid black;" slot="content">
19+
<p>item</p>
20+
</div>
21+
</ui-menu>`
22+
}),
23+
};
24+
25+
export default {
26+
title: 'Components/Menu',
27+
component: Menu,
28+
parameters: {
29+
layout: 'centered',
30+
},
31+
};
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { mount } from '@vue/test-utils'
2+
import Menu from './widget';
3+
4+
describe('Menu component', () => {
5+
describe('methods', () => {
6+
describe('#toggle', () => {
7+
it('toggles menu to true when clicking', () => {
8+
const wrapper = mount(Menu);
9+
wrapper.vm.showMenu = false;
10+
wrapper.vm.toggle(wrapper.vm.showMenu);
11+
12+
expect(wrapper.vm.showMenu).toBe(true);
13+
});
14+
15+
it('toggles menu back to false when clicking', () => {
16+
const wrapper = mount(Menu);
17+
wrapper.vm.showMenu = true;
18+
wrapper.vm.toggle(wrapper.vm.showMenu);
19+
20+
expect(wrapper.vm.showMenu).toBe(false);
21+
});
22+
});
23+
24+
describe('#handleClickOutside', () => {
25+
26+
it('hides menu content when clicked outside menu', () => {
27+
const event = { target: 'another value'};
28+
const wrapper = mount(Menu);
29+
wrapper.vm.menu = { contains: jest.fn().mockReturnValue(false) };
30+
wrapper.vm.showMenu = true;
31+
wrapper.vm.handleClickOutside(event);
32+
33+
expect(wrapper.vm.showMenu).toBe(false);
34+
});
35+
36+
it('does not hide menu content when clicked inside menu', () => {
37+
const event = { target: 'some value'};
38+
const wrapper = mount(Menu);
39+
wrapper.vm.menu = { contains: jest.fn().mockReturnValue(true) };
40+
wrapper.vm.showMenu = true;
41+
wrapper.vm.handleClickOutside(event);
42+
43+
expect(wrapper.vm.showMenu).toBe(true);
44+
});
45+
});
46+
});
47+
48+
describe('onMounted', () => {
49+
it('adds up event listener on component mount', () => {
50+
const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
51+
52+
mount(Menu);
53+
54+
expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function));
55+
56+
addEventListenerSpy.mockRestore();
57+
});
58+
});
59+
60+
describe('onUnmounted', () => {
61+
it('cleans up event listener on component unmount', async () => {
62+
const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener');
63+
64+
const wrapper = mount(Menu);
65+
await wrapper.unmount();
66+
67+
expect(removeEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function));
68+
69+
removeEventListenerSpy.mockRestore();
70+
});
71+
})
72+
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<template>
2+
<div
3+
ref="menu"
4+
class="menu"
5+
>
6+
<div
7+
class="menu-trigger"
8+
@click.stop="toggle"
9+
>
10+
<slot name="trigger" />
11+
</div>
12+
13+
<div class="menu-content-wrapper">
14+
<div
15+
v-if="showMenu"
16+
class="menu-content"
17+
@click.stop
18+
>
19+
<slot name="content" />
20+
</div>
21+
</div>
22+
</div>
23+
</template>
24+
25+
<script setup>
26+
import { onMounted, onUnmounted, ref } from 'vue'
27+
28+
const showMenu = ref(false)
29+
const menu = ref(null)
30+
31+
const toggle = () => {
32+
showMenu.value = !showMenu.value;
33+
}
34+
35+
const handleClickOutside = (event) => {
36+
if (menu.value && !menu.value.contains(event.target)) {
37+
showMenu.value = false;
38+
}
39+
}
40+
41+
onMounted(() => {
42+
document.addEventListener("click", handleClickOutside)
43+
})
44+
45+
onUnmounted(() => {
46+
document.removeEventListener("click", handleClickOutside)
47+
})
48+
</script>
49+
50+
<style lang="stylus" scoped>
51+
52+
.menu-content-wrapper {
53+
position: relative;
54+
}
55+
56+
.menu-content {
57+
position: absolute;
58+
top: 0;
59+
left: 0;
60+
}
61+
</style>

components/src/widgets/textfield/widget.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,16 @@ export default {
6161
</script>
6262

6363
<style lang="stylus">
64+
@import '../../assets/styles/common.styl';
65+
6466
.text-field {
6567
font-family: 'Roboto';
6668
font-size: 14px;
6769
line-height: 20px;
6870
font-weight: 400;
6971
display: flex;
7072
flex-flow: column nowrap;
71-
color: #212121;
73+
color: base-text-color;
7274

7375
label {
7476
font-weight 500;

0 commit comments

Comments
 (0)