Skip to content

Commit 34a4a16

Browse files
committed
Merge branch 'develop' of github.com:coderan-io/ui into feature/navigation
2 parents b65ba59 + 96877a9 commit 34a4a16

File tree

15 files changed

+41922
-18674
lines changed

15 files changed

+41922
-18674
lines changed

.github/workflows/ci.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# This is a basic workflow to help you get started with Actions
2+
3+
name: CI
4+
5+
on: [push, pull_request]
6+
jobs:
7+
test-coverage:
8+
name: Test on Node.js Latest
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Checkout code
12+
uses: actions/checkout@v2
13+
- name: Use Node.js latest
14+
uses: actions/setup-node@v2.1.3
15+
with:
16+
node-version: "15"
17+
- name: Install dependencies
18+
run: npm install
19+
- name: Generate coverage report
20+
run: npm run test-coverage
21+
- name: Upload coverage report
22+
uses: codecov/codecov-action@v1.0.15
23+
with:
24+
token: ${{ secrets.CODECOV }}
25+
test-node-12:
26+
name: Test on Node.js v12
27+
runs-on: ubuntu-latest
28+
steps:
29+
- name: Checkout codd
30+
uses: actions/checkout@v2
31+
- name: Use Node.js v12
32+
uses: actions/setup-node@v2.1.3
33+
with:
34+
node-version: "12"

jest.config.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
{
2-
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$",
32
"setupFilesAfterEnv": ["./setupTests.ts"],
43
"moduleNameMapper": {
54
"@/(.*)": "<rootDir>/src/$1"

package-lock.json

Lines changed: 41291 additions & 18667 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@
4040
"webpack-dev-server": "^3.11.0"
4141
},
4242
"dependencies": {
43+
"@popperjs/core": "^2.6.0",
4344
"clsx": "^1.1.1",
45+
"framer-motion": "^3.1.1",
4446
"react": "^16.14.0"
4547
}
4648
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import * as React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { useRef, useState } from 'react';
4+
import Overlay from '@/components/Overlay/index';
5+
import { Placement, PositioningStrategy } from '@popperjs/core';
6+
import { Trigger, triggerPropTypes } from '@/components/Overlay/Trigger';
7+
import { AnimatePresence } from 'framer-motion';
8+
9+
interface OverlayTriggerProps {
10+
arrow?: boolean;
11+
children: React.ReactElement;
12+
overlay: React.ReactNode;
13+
placement?: Placement;
14+
positionStrategy?: PositioningStrategy;
15+
className?: string;
16+
trigger?: Trigger | string;
17+
motion?: string;
18+
}
19+
20+
const OverlayTrigger = ({
21+
arrow,
22+
children: triggerElement,
23+
className,
24+
overlay,
25+
placement,
26+
positionStrategy,
27+
trigger = 'hover',
28+
motion
29+
}: OverlayTriggerProps): React.ReactElement => {
30+
const [shown, setShown] = useState<boolean>(false);
31+
const triggerRef = useRef<HTMLElement>();
32+
33+
const attachEvents = (child: React.ReactElement, trigger: string) => {
34+
switch (trigger) {
35+
case Trigger.CLICK:
36+
return {
37+
onClick: (event: React.MouseEvent) => {
38+
if (child.props.onClick) {
39+
child.props.onClick(event);
40+
}
41+
42+
setShown(!shown);
43+
}
44+
}
45+
case Trigger.HOVER:
46+
default:
47+
return {
48+
onMouseEnter: (event: React.MouseEvent): void => {
49+
if (child.props.onMouseEnter) {
50+
child.props.onMouseEnter(event);
51+
}
52+
53+
setShown(true)
54+
},
55+
onMouseLeave: (event: React.MouseEvent) => {
56+
if (child.props.onMouseLeave) {
57+
child.props.onMouseLeave(event);
58+
}
59+
60+
setShown(false)
61+
}
62+
}
63+
}
64+
}
65+
66+
const createChildren = () => shown && (
67+
<Overlay
68+
motion={motion}
69+
arrow={arrow}
70+
triggerRef={triggerRef}
71+
placement={placement}
72+
positionStrategy={positionStrategy}
73+
className={className}
74+
>
75+
{overlay}
76+
</Overlay>
77+
)
78+
79+
return (
80+
<>
81+
{React.cloneElement(triggerElement, {
82+
ref: triggerRef,
83+
...attachEvents(triggerElement, trigger)
84+
})}
85+
{motion
86+
? React.createElement(AnimatePresence, {}, createChildren())
87+
: createChildren()}
88+
</>
89+
)
90+
}
91+
92+
OverlayTrigger.displayName = 'OverlayTrigger';
93+
OverlayTrigger.propTypes = {
94+
children: PropTypes.node.isRequired,
95+
className: PropTypes.string,
96+
overlay: PropTypes.element.isRequired,
97+
placement: PropTypes.string,
98+
trigger: triggerPropTypes
99+
}
100+
101+
export default OverlayTrigger;

src/components/Overlay/Trigger.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Proptypes from 'prop-types';
2+
3+
export enum Trigger {
4+
CLICK = 'click',
5+
HOVER = 'hover'
6+
}
7+
8+
export const triggerPropTypes = Proptypes.oneOf(['click', 'hover'])
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { mount } from 'enzyme';
2+
import React from 'react';
3+
import OverlayTrigger from '@/components/Overlay/OverlayTrigger';
4+
import { motion } from 'framer-motion';
5+
6+
describe('Overlay test', () => {
7+
it('should render overlay when hovered', async () => {
8+
const tooltip = mount(
9+
<div>
10+
<OverlayTrigger
11+
overlay="test"
12+
>
13+
<button>test</button>
14+
</OverlayTrigger>
15+
</div>
16+
);
17+
18+
tooltip.find('button').simulate('mouseenter');
19+
20+
expect(tooltip.find('.content').text()).toBe('test');
21+
22+
tooltip.find('button').simulate('mouseleave');
23+
24+
expect(tooltip.find('.content').length).toBe(0);
25+
});
26+
27+
it('should call props along hover triggers', async () => {
28+
const mockFnEnter = jest.fn();
29+
const mockFnLeave = jest.fn();
30+
31+
const tooltip = mount(
32+
<div>
33+
<OverlayTrigger
34+
overlay="test"
35+
>
36+
<button onMouseEnter={mockFnEnter} onMouseLeave={mockFnLeave}>test</button>
37+
</OverlayTrigger>
38+
</div>
39+
);
40+
41+
tooltip.find('button').simulate('mouseenter');
42+
tooltip.find('button').simulate('mouseleave');
43+
44+
expect(mockFnEnter).toHaveBeenCalled();
45+
expect(mockFnLeave).toHaveBeenCalled();
46+
});
47+
48+
it('should render overlay when clicked', async () => {
49+
const mockFn = jest.fn();
50+
51+
const tooltip = mount(
52+
<div>
53+
<OverlayTrigger
54+
trigger="click"
55+
overlay="test"
56+
>
57+
<button onClick={mockFn}>test</button>
58+
</OverlayTrigger>
59+
</div>
60+
);
61+
62+
tooltip.find('button').simulate('click');
63+
64+
expect(tooltip.find('.content').text()).toBe('test');
65+
expect(mockFn).toHaveBeenCalled();
66+
});
67+
68+
it('should render overlay with arrow', async () => {
69+
const tooltip = mount(
70+
<div>
71+
<OverlayTrigger
72+
trigger="click"
73+
overlay="test"
74+
arrow
75+
>
76+
<button>test</button>
77+
</OverlayTrigger>
78+
</div>
79+
);
80+
81+
tooltip.find('button').simulate('click');
82+
83+
expect(tooltip.find('.arrow')).toBeDefined();
84+
});
85+
86+
it('should render overlay without arrow', async () => {
87+
const tooltip = mount(
88+
<div>
89+
<OverlayTrigger
90+
trigger="click"
91+
overlay="test"
92+
arrow={false}
93+
>
94+
<button>test</button>
95+
</OverlayTrigger>
96+
</div>
97+
);
98+
99+
tooltip.find('button').simulate('click');
100+
101+
expect(tooltip.find('.arrow').length).toBe(0);
102+
});
103+
104+
it('should should use motion when defined', async () => {
105+
const tooltip = mount(
106+
<div>
107+
<OverlayTrigger
108+
trigger="click"
109+
overlay="test"
110+
motion="fade"
111+
>
112+
<button>test</button>
113+
</OverlayTrigger>
114+
</div>
115+
);
116+
117+
tooltip.find('button').simulate('click');
118+
119+
expect(tooltip.find(motion.div)).toBeDefined();
120+
});
121+
122+
it('should should ignore undefined motion', async () => {
123+
const tooltip = mount(
124+
<div>
125+
<OverlayTrigger
126+
trigger="click"
127+
overlay="test"
128+
motion="bounce"
129+
>
130+
<button>test</button>
131+
</OverlayTrigger>
132+
</div>
133+
);
134+
135+
tooltip.find('button').simulate('click');
136+
137+
expect(tooltip.find(motion.div).length).toBe(0);
138+
});
139+
});

0 commit comments

Comments
 (0)