-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(tooltip, popover): add popover component, add arrowPointAtCenter… (
#33) affects: @gio-design/components, @gio-design/tokens, website * style: fix popover style issue cause by ghost blank node. and update doc example * docs: create group for functional components Co-authored-by: lihang <lihang@growingio.com>
- Loading branch information
Showing
36 changed files
with
729 additions
and
35 deletions.
There are no files selected for viewing
74 changes: 74 additions & 0 deletions
74
packages/components/src/components/popover/__tests__/Popover.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import React from 'react'; | ||
import Popover from '../index'; | ||
import '@gio-design/components/es/components/Tabs/style/index.css'; | ||
import { act } from 'react-dom/test-utils'; | ||
import { mount, render } from 'enzyme'; | ||
|
||
async function waitForComponentToPaint(wrapper, amount = 500) { | ||
await act(async () => new Promise((resolve) => setTimeout(resolve, amount)).then(() => wrapper.update())); | ||
} | ||
|
||
describe('Testing Popover', () => { | ||
const getPopover = () => ( | ||
<Popover contentArea='content' footerArea='footer'> | ||
<span>Test</span> | ||
</Popover> | ||
); | ||
|
||
beforeEach(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.useRealTimers(); | ||
}); | ||
|
||
it('should be stable', () => { | ||
const wrapper = render(getPopover()); | ||
expect(wrapper).toMatchSnapshot(); | ||
}); | ||
|
||
it('should be mount, setProps, unmount with no error', () => { | ||
expect(() => { | ||
const wrapper = mount(getPopover()); | ||
wrapper.setProps({ contentArea: 'content update' }); | ||
wrapper.setProps({ visible: 'true' }); | ||
wrapper.unmount(); | ||
}).not.toThrow(); | ||
}); | ||
|
||
test('prop contentArea', () => { | ||
const wrapper = mount(getPopover()); | ||
wrapper.setProps({ contentArea: 'new text', footerArea: null }); | ||
wrapper.setProps({ trigger: 'click' }); | ||
wrapper.find('span').at(0).simulate('click'); | ||
expect(wrapper.find('.gio-popover-inner').exists('.gio-popover-inner-content')).toBe(true); | ||
expect(wrapper.find('.gio-popover-inner-content').text()).toBe('new text'); | ||
expect(wrapper.find('.gio-popover-inner').exists('.gio-popover-inner-footer')).toBe(false); | ||
}); | ||
|
||
test('prop footerArea', () => { | ||
const wrapper = mount(getPopover()); | ||
wrapper.setProps({ trigger: 'click' }); | ||
wrapper.find('span').at(0).simulate('click'); | ||
wrapper.setProps({ contentArea: 0, footerArea: 'only footer' }); | ||
expect(wrapper.find('.gio-popover-inner').exists('.gio-popover-inner-footer')).toBe(true); | ||
expect(wrapper.find('.gio-popover-inner-footer').text()).toBe('only footer'); | ||
expect(wrapper.find('.gio-popover-inner').exists('.gio-popover-inner-content')).toBe(false); | ||
}); | ||
|
||
it('should be render rightly', () => { | ||
const wrapper = mount(getPopover()); | ||
wrapper.setProps({ trigger: 'click' }); | ||
wrapper.setProps({ placement: 'topLeft' }); | ||
wrapper.setProps({ overlayClassName: 'overlayClassName' }); | ||
wrapper.find('span').at(0).simulate('click'); | ||
expect(wrapper.exists('.gio-popover-inner')).toBe(true); | ||
expect(wrapper.find('.gio-popover-inner').exists('.gio-popover-inner-content')).toBe(true); | ||
expect(wrapper.find('.gio-popover-inner').exists('.gio-popover-inner-footer')).toBe(true); | ||
expect(wrapper.exists('.overlayClassName')).toBe(true); | ||
waitForComponentToPaint(wrapper).then(() => { | ||
expect(wrapper.exists('.gio-popover-placement-topLeft')).toBe(true); | ||
}); | ||
}); | ||
}); |
48 changes: 48 additions & 0 deletions
48
packages/components/src/components/popover/__tests__/__snapshots__/Popover.test.js.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`Testing Popover should be stable 1`] = ` | ||
initialize { | ||
"0": Object { | ||
"attribs": Object {}, | ||
"children": Array [ | ||
Object { | ||
"data": "Test", | ||
"next": null, | ||
"parent": [Circular], | ||
"prev": null, | ||
"type": "text", | ||
}, | ||
], | ||
"name": "span", | ||
"namespace": "http://www.w3.org/1999/xhtml", | ||
"next": null, | ||
"parent": null, | ||
"prev": null, | ||
"root": Object { | ||
"attribs": Object {}, | ||
"children": Array [ | ||
[Circular], | ||
], | ||
"name": "root", | ||
"namespace": "http://www.w3.org/1999/xhtml", | ||
"next": null, | ||
"parent": null, | ||
"prev": null, | ||
"type": "root", | ||
"x-attribsNamespace": Object {}, | ||
"x-attribsPrefix": Object {}, | ||
}, | ||
"type": "tag", | ||
"x-attribsNamespace": Object {}, | ||
"x-attribsPrefix": Object {}, | ||
}, | ||
"_root": [Circular], | ||
"length": 1, | ||
"options": Object { | ||
"decodeEntities": true, | ||
"normalizeWhitespace": false, | ||
"withDomLvl1": true, | ||
"xml": false, | ||
}, | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import React, { useContext } from 'react'; | ||
import Tooltip from '../tooltip'; | ||
import { PopoverProps } from './interface'; | ||
import { ConfigContext } from '../config-provider'; | ||
|
||
const Popover: React.FC<PopoverProps> = (props: PopoverProps) => { | ||
const { children, contentArea, footerArea, prefixCls: customizePrefixCls, ...rest } = props; | ||
const { getPrefixCls } = useContext(ConfigContext); | ||
const prefixCls = getPrefixCls('popover', customizePrefixCls); | ||
|
||
const popoverOverlay = () => ( | ||
<> | ||
{contentArea && <div className={`${prefixCls}-inner-content`}>{contentArea}</div>} | ||
{footerArea && <div className={`${prefixCls}-inner-footer`}>{footerArea}</div>} | ||
</> | ||
); | ||
return ( | ||
<Tooltip prefixCls={prefixCls} overlay={popoverOverlay()} {...rest}> | ||
{children} | ||
</Tooltip> | ||
); | ||
}; | ||
|
||
export default Popover; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { TooltipProps } from '../tooltip/interface'; | ||
|
||
export type ReactRender = () => React.ReactNode; | ||
export interface PopoverProps extends Omit<TooltipProps, 'title' | 'tooltipLink'> { | ||
contentArea: React.ReactNode | ReactRender; | ||
footerArea?: React.ReactNode | ReactRender; | ||
} |
64 changes: 64 additions & 0 deletions
64
packages/components/src/components/popover/style/index.less
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
@import '../../../stylesheet/theme.less'; | ||
@import '../../../stylesheet/mixin/trigger.less'; | ||
@import '~@gio-design/tokens/dist/variables.less'; | ||
|
||
@popover-prefix-cls: ~'@{component-prefix}-popover'; | ||
@popover-arrow-width: 12px; | ||
@distance: 6px; | ||
@popover-offset: 20px; | ||
@popover-duration: 10ms; | ||
|
||
.@{popover-prefix-cls}{ | ||
margin: auto; | ||
position: absolute; | ||
z-index: 100; | ||
|
||
&-hidden { | ||
display: none; | ||
} | ||
|
||
&-content { | ||
position: relative; | ||
} | ||
|
||
&-inner { | ||
box-shadow: @shadow-popover; | ||
border-radius: 4px; | ||
background-color: @color-background-popover; | ||
display: block; | ||
overflow: hidden; | ||
&-content{ | ||
margin: 20px; | ||
position: relative; | ||
overflow: hidden; | ||
} | ||
&-footer { | ||
margin: -4px 20px 16px; | ||
position: relative; | ||
overflow: hidden; | ||
} | ||
} | ||
|
||
&-arrow { | ||
position: absolute; | ||
display: block; | ||
pointer-events: none; | ||
height: 12px; | ||
width: 12px; | ||
&-content { | ||
position: absolute; | ||
margin: auto; | ||
top: 0; | ||
left: 0; | ||
right: 0; | ||
bottom: 0; | ||
width: 0px; | ||
height: 0px; | ||
border: @popover-arrow-width/2 solid; | ||
pointer-events: auto; | ||
} | ||
} | ||
.trigger-placement(@popover-prefix-cls, @color-background-popover, @popover-arrow-width, @distance, @popover-offset); | ||
} | ||
|
||
.trigger-transition(@popover-prefix-cls, @popover-duration); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
packages/components/src/components/tooltip/placements.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { placements } from 'rc-tooltip/lib/placements'; | ||
import { BuildInPlacements } from 'rc-trigger'; | ||
|
||
const autoAdjustOverflowEnabled = { | ||
adjustX: 1, | ||
adjustY: 1, | ||
}; | ||
|
||
const autoAdjustOverflowDisabled = { | ||
adjustX: 0, | ||
adjustY: 0, | ||
}; | ||
|
||
const targetOffset = [0, 0]; | ||
|
||
export interface AdjustOverflow { | ||
adjustX?: 0 | 1; | ||
adjustY?: 0 | 1; | ||
} | ||
|
||
export interface PlacementsConfig { | ||
arrowWidth?: number; | ||
horizontalArrowShift?: number; | ||
verticalArrowShift?: number; | ||
arrowPointAtCenter?: boolean; | ||
autoAdjustOverflow?: boolean | AdjustOverflow; | ||
} | ||
|
||
export function getOverflowOptions(autoAdjustOverflow?: boolean | AdjustOverflow) { | ||
if (typeof autoAdjustOverflow === 'boolean') { | ||
return autoAdjustOverflow ? autoAdjustOverflowEnabled : autoAdjustOverflowDisabled; | ||
} | ||
return { | ||
...autoAdjustOverflowDisabled, | ||
...autoAdjustOverflow, | ||
}; | ||
} | ||
|
||
export default function getPlacements(config: PlacementsConfig) { | ||
const { | ||
arrowWidth = 6, | ||
horizontalArrowShift = 20, | ||
verticalArrowShift = 20, | ||
autoAdjustOverflow = true, | ||
arrowPointAtCenter, | ||
} = config; | ||
const placementMap: BuildInPlacements = { | ||
left: { | ||
points: ['cr', 'cl'], | ||
offset: [-4, 0], | ||
}, | ||
right: { | ||
points: ['cl', 'cr'], | ||
offset: [4, 0], | ||
}, | ||
top: { | ||
points: ['bc', 'tc'], | ||
offset: [0, -4], | ||
}, | ||
bottom: { | ||
points: ['tc', 'bc'], | ||
offset: [0, 4], | ||
}, | ||
topLeft: { | ||
points: ['bl', 'tc'], | ||
offset: [-(horizontalArrowShift + arrowWidth), -4], | ||
}, | ||
leftTop: { | ||
points: ['tr', 'cl'], | ||
offset: [-4, -(verticalArrowShift + arrowWidth)], | ||
}, | ||
topRight: { | ||
points: ['br', 'tc'], | ||
offset: [horizontalArrowShift + arrowWidth, -4], | ||
}, | ||
rightTop: { | ||
points: ['tl', 'cr'], | ||
offset: [4, -(verticalArrowShift + arrowWidth)], | ||
}, | ||
bottomRight: { | ||
points: ['tr', 'bc'], | ||
offset: [horizontalArrowShift + arrowWidth, 4], | ||
}, | ||
rightBottom: { | ||
points: ['bl', 'cr'], | ||
offset: [4, verticalArrowShift + arrowWidth], | ||
}, | ||
bottomLeft: { | ||
points: ['tl', 'bc'], | ||
offset: [-(horizontalArrowShift + arrowWidth), 4], | ||
}, | ||
leftBottom: { | ||
points: ['br', 'cl'], | ||
offset: [-4, verticalArrowShift + arrowWidth], | ||
}, | ||
}; | ||
Object.keys(placementMap).forEach((key) => { | ||
placementMap[key] = arrowPointAtCenter | ||
? { | ||
...placementMap[key], | ||
overflow: getOverflowOptions(autoAdjustOverflow), | ||
targetOffset, | ||
} | ||
: { | ||
...placements[key], | ||
overflow: getOverflowOptions(autoAdjustOverflow), | ||
}; | ||
|
||
placementMap[key].ignoreShake = true; | ||
}); | ||
return placementMap; | ||
} |
Oops, something went wrong.