@redsift/popovers 9.2.3-patch → 9.2.3
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/coverage/clover.xml +763 -0
- package/coverage/coverage-final.json +53 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/dialog/Dialog.tsx.html +271 -0
- package/coverage/lcov-report/dialog/context.ts.html +97 -0
- package/coverage/lcov-report/dialog/index.html +191 -0
- package/coverage/lcov-report/dialog/index.ts.html +100 -0
- package/coverage/lcov-report/dialog/types.ts.html +241 -0
- package/coverage/lcov-report/dialog/useDialog.tsx.html +346 -0
- package/coverage/lcov-report/dialog/useDialogContext.tsx.html +121 -0
- package/coverage/lcov-report/dialog-content/DialogContent.tsx.html +484 -0
- package/coverage/lcov-report/dialog-content/index.html +146 -0
- package/coverage/lcov-report/dialog-content/index.ts.html +91 -0
- package/coverage/lcov-report/dialog-content/intl/index.html +116 -0
- package/coverage/lcov-report/dialog-content/intl/index.ts.html +106 -0
- package/coverage/lcov-report/dialog-content/styles.ts.html +256 -0
- package/coverage/lcov-report/dialog-content-actions/DialogContentActions.tsx.html +205 -0
- package/coverage/lcov-report/dialog-content-actions/index.html +146 -0
- package/coverage/lcov-report/dialog-content-actions/index.ts.html +91 -0
- package/coverage/lcov-report/dialog-content-actions/styles.ts.html +139 -0
- package/coverage/lcov-report/dialog-content-body/DialogContentBody.tsx.html +232 -0
- package/coverage/lcov-report/dialog-content-body/index.html +146 -0
- package/coverage/lcov-report/dialog-content-body/index.ts.html +91 -0
- package/coverage/lcov-report/dialog-content-body/styles.ts.html +259 -0
- package/coverage/lcov-report/dialog-content-header/DialogContentHeader.tsx.html +280 -0
- package/coverage/lcov-report/dialog-content-header/index.html +146 -0
- package/coverage/lcov-report/dialog-content-header/index.ts.html +91 -0
- package/coverage/lcov-report/dialog-content-header/styles.ts.html +193 -0
- package/coverage/lcov-report/dialog-trigger/DialogTrigger.tsx.html +217 -0
- package/coverage/lcov-report/dialog-trigger/index.html +131 -0
- package/coverage/lcov-report/dialog-trigger/index.ts.html +91 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +341 -0
- package/coverage/lcov-report/popover/Popover.tsx.html +295 -0
- package/coverage/lcov-report/popover/context.ts.html +97 -0
- package/coverage/lcov-report/popover/index.html +191 -0
- package/coverage/lcov-report/popover/index.ts.html +100 -0
- package/coverage/lcov-report/popover/types.ts.html +283 -0
- package/coverage/lcov-report/popover/usePopover.tsx.html +415 -0
- package/coverage/lcov-report/popover/usePopoverContext.tsx.html +121 -0
- package/coverage/lcov-report/popover-content/PopoverContent.tsx.html +229 -0
- package/coverage/lcov-report/popover-content/index.html +146 -0
- package/coverage/lcov-report/popover-content/index.ts.html +94 -0
- package/coverage/lcov-report/popover-content/styles.ts.html +370 -0
- package/coverage/lcov-report/popover-trigger/PopoverTrigger.tsx.html +202 -0
- package/coverage/lcov-report/popover-trigger/index.html +131 -0
- package/coverage/lcov-report/popover-trigger/index.ts.html +91 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov-report/toast/Toast.tsx.html +373 -0
- package/coverage/lcov-report/toast/index.html +161 -0
- package/coverage/lcov-report/toast/index.ts.html +91 -0
- package/coverage/lcov-report/toast/intl/index.html +116 -0
- package/coverage/lcov-report/toast/intl/index.ts.html +106 -0
- package/coverage/lcov-report/toast/styles.ts.html +193 -0
- package/coverage/lcov-report/toast/types.ts.html +217 -0
- package/coverage/lcov-report/toast-container/ToastContainer.tsx.html +217 -0
- package/coverage/lcov-report/toast-container/index.html +161 -0
- package/coverage/lcov-report/toast-container/index.ts.html +94 -0
- package/coverage/lcov-report/toast-container/styles.ts.html +2284 -0
- package/coverage/lcov-report/toast-container/useToast.tsx.html +469 -0
- package/coverage/lcov-report/tooltip/Tooltip.tsx.html +250 -0
- package/coverage/lcov-report/tooltip/context.ts.html +97 -0
- package/coverage/lcov-report/tooltip/index.html +191 -0
- package/coverage/lcov-report/tooltip/index.ts.html +100 -0
- package/coverage/lcov-report/tooltip/types.ts.html +250 -0
- package/coverage/lcov-report/tooltip/useTooltip.tsx.html +358 -0
- package/coverage/lcov-report/tooltip/useTooltipContext.tsx.html +121 -0
- package/coverage/lcov-report/tooltip-content/TooltipContent.tsx.html +313 -0
- package/coverage/lcov-report/tooltip-content/index.html +146 -0
- package/coverage/lcov-report/tooltip-content/index.ts.html +91 -0
- package/coverage/lcov-report/tooltip-content/styles.ts.html +337 -0
- package/coverage/lcov-report/tooltip-trigger/TooltipTrigger.tsx.html +211 -0
- package/coverage/lcov-report/tooltip-trigger/index.html +131 -0
- package/coverage/lcov-report/tooltip-trigger/index.ts.html +91 -0
- package/coverage/lcov.info +1510 -0
- package/coverage/storybook/coverage-storybook.json +58724 -0
- package/dist/package.json +96 -0
- package/index.ts +1 -0
- package/jest.config.js +3 -0
- package/package.json +2 -3
- package/rollup.config.js +13 -0
- package/src/components/dialog/Dialog.stories.tsx +264 -0
- package/src/components/dialog/Dialog.test.tsx +116 -0
- package/src/components/dialog/Dialog.tsx +62 -0
- package/src/components/dialog/context.ts +4 -0
- package/src/components/dialog/index.ts +5 -0
- package/src/components/dialog/types.ts +52 -0
- package/src/components/dialog/useDialog.tsx +87 -0
- package/src/components/dialog/useDialogContext.tsx +12 -0
- package/src/components/dialog-content/DialogContent.stories.tsx +348 -0
- package/src/components/dialog-content/DialogContent.tsx +133 -0
- package/src/components/dialog-content/index.ts +2 -0
- package/src/components/dialog-content/intl/en-US.json +3 -0
- package/src/components/dialog-content/intl/fr-FR.json +3 -0
- package/src/components/dialog-content/intl/index.ts +7 -0
- package/src/components/dialog-content/styles.ts +57 -0
- package/src/components/dialog-content/types.ts +10 -0
- package/src/components/dialog-content-actions/DialogContentActions.test.tsx +68 -0
- package/src/components/dialog-content-actions/DialogContentActions.tsx +40 -0
- package/src/components/dialog-content-actions/index.ts +2 -0
- package/src/components/dialog-content-actions/styles.ts +18 -0
- package/src/components/dialog-content-actions/types.ts +11 -0
- package/src/components/dialog-content-body/DialogContentBody.test.tsx +63 -0
- package/src/components/dialog-content-body/DialogContentBody.tsx +49 -0
- package/src/components/dialog-content-body/index.ts +2 -0
- package/src/components/dialog-content-body/styles.ts +58 -0
- package/src/components/dialog-content-body/types.ts +14 -0
- package/src/components/dialog-content-header/DialogContentHeader.test.tsx +63 -0
- package/src/components/dialog-content-header/DialogContentHeader.tsx +65 -0
- package/src/components/dialog-content-header/index.ts +2 -0
- package/src/components/dialog-content-header/styles.ts +36 -0
- package/src/components/dialog-content-header/types.ts +21 -0
- package/src/components/dialog-trigger/DialogTrigger.tsx +44 -0
- package/src/components/dialog-trigger/index.ts +2 -0
- package/src/components/dialog-trigger/types.ts +9 -0
- package/src/components/popover/Popover.stories.tsx +129 -0
- package/src/components/popover/Popover.test.tsx +102 -0
- package/src/components/popover/Popover.tsx +70 -0
- package/src/components/popover/context.ts +4 -0
- package/src/components/popover/index.ts +5 -0
- package/src/components/popover/types.ts +66 -0
- package/src/components/popover/usePopover.tsx +110 -0
- package/src/components/popover/usePopoverContext.tsx +12 -0
- package/src/components/popover-content/PopoverContent.tsx +48 -0
- package/src/components/popover-content/index.ts +3 -0
- package/src/components/popover-content/styles.ts +95 -0
- package/src/components/popover-content/types.ts +11 -0
- package/src/components/popover-trigger/PopoverTrigger.tsx +39 -0
- package/src/components/popover-trigger/index.ts +2 -0
- package/src/components/popover-trigger/types.ts +9 -0
- package/src/components/toast/Toast.stories.tsx +68 -0
- package/src/components/toast/Toast.test.tsx +63 -0
- package/src/components/toast/Toast.tsx +96 -0
- package/src/components/toast/index.ts +2 -0
- package/src/components/toast/intl/en-US.json +3 -0
- package/src/components/toast/intl/fr-FR.json +3 -0
- package/src/components/toast/intl/index.ts +7 -0
- package/src/components/toast/styles.ts +36 -0
- package/src/components/toast/types.ts +44 -0
- package/src/components/toast-container/ToastContainer.stories.tsx +349 -0
- package/src/components/toast-container/ToastContainer.tsx +44 -0
- package/src/components/toast-container/index.ts +3 -0
- package/src/components/toast-container/styles.ts +733 -0
- package/src/components/toast-container/types.ts +110 -0
- package/src/components/toast-container/useToast.test.tsx +111 -0
- package/src/components/toast-container/useToast.tsx +128 -0
- package/src/components/tooltip/Tooltip.stories.tsx +196 -0
- package/src/components/tooltip/Tooltip.test.tsx +119 -0
- package/src/components/tooltip/Tooltip.tsx +55 -0
- package/src/components/tooltip/context.ts +4 -0
- package/src/components/tooltip/index.ts +5 -0
- package/src/components/tooltip/types.ts +55 -0
- package/src/components/tooltip/useTooltip.tsx +93 -0
- package/src/components/tooltip/useTooltipContext.tsx +12 -0
- package/src/components/tooltip-content/TooltipContent.tsx +76 -0
- package/src/components/tooltip-content/index.ts +2 -0
- package/src/components/tooltip-content/styles.ts +84 -0
- package/src/components/tooltip-content/types.ts +14 -0
- package/src/components/tooltip-trigger/TooltipTrigger.tsx +42 -0
- package/src/components/tooltip-trigger/index.ts +2 -0
- package/src/components/tooltip-trigger/types.ts +9 -0
- package/src/index.ts +16 -0
- package/tsconfig.json +3 -0
- /package/{CONTRIBUTING.md → dist/CONTRIBUTING.md} +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{index.js → dist/index.js} +0 -0
- /package/{index.js.map → dist/index.js.map} +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Flexbox, Button, Text } from '@redsift/design-system';
|
|
3
|
+
import { Popover, PopoverPlacement } from '.';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Popovers/Popover',
|
|
7
|
+
component: Popover,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const Uncontrolled = () => (
|
|
11
|
+
<Popover>
|
|
12
|
+
<Popover.Trigger>
|
|
13
|
+
<Button variant="secondary">Button</Button>
|
|
14
|
+
</Popover.Trigger>
|
|
15
|
+
<Popover.Content>
|
|
16
|
+
<Text margin="16px">
|
|
17
|
+
Cupcake ipsum dolor sit amet blueie jujubes topping sesame snaps. Liquorice marzipan jelly-o carrot cake icing
|
|
18
|
+
croissant carrot cake. Tart soufflé sweet roll halvah croissant wafer cotton candy. Candy halvah marzipan bear
|
|
19
|
+
claw donut.
|
|
20
|
+
</Text>
|
|
21
|
+
</Popover.Content>
|
|
22
|
+
</Popover>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const Controlled = () => {
|
|
26
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<Button variant="secondary" onClick={() => setIsOpen(!isOpen)}>
|
|
30
|
+
External trigger
|
|
31
|
+
</Button>
|
|
32
|
+
<Popover isOpen={isOpen} onOpen={setIsOpen}>
|
|
33
|
+
<Popover.Trigger>
|
|
34
|
+
<Button variant="secondary">Trigger</Button>
|
|
35
|
+
</Popover.Trigger>
|
|
36
|
+
<Popover.Content>
|
|
37
|
+
<Text margin="16px">
|
|
38
|
+
Cupcake ipsum dolor sit amet blueie jujubes topping sesame snaps. Liquorice marzipan jelly-o carrot cake
|
|
39
|
+
icing croissant carrot cake. Tart soufflé sweet roll halvah croissant wafer cotton candy. Candy halvah
|
|
40
|
+
marzipan bear claw donut.
|
|
41
|
+
</Text>
|
|
42
|
+
</Popover.Content>
|
|
43
|
+
</Popover>
|
|
44
|
+
</>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const AllPlacements = () => (
|
|
49
|
+
<Flexbox flexDirection="column" flexWrap="wrap" gap="32px" padding="64px" style={{ backgroundColor: '#EFEFEF' }}>
|
|
50
|
+
{['top', 'left', 'right', 'bottom'].map((side) => {
|
|
51
|
+
return (
|
|
52
|
+
<Flexbox key={side} flexDirection="row" flexWrap="wrap" justifyContent="space-evenly" alignItems="center">
|
|
53
|
+
{['start', '', 'end'].map((alignment) => {
|
|
54
|
+
return (
|
|
55
|
+
<div key={`${side}${alignment ? '-' : ''}${alignment}`} style={{ margin: '1em' }}>
|
|
56
|
+
<Popover isOpen placement={`${side}${alignment ? '-' : ''}${alignment}` as PopoverPlacement}>
|
|
57
|
+
<Popover.Trigger>
|
|
58
|
+
<Button variant="secondary">Button</Button>
|
|
59
|
+
</Popover.Trigger>
|
|
60
|
+
<Popover.Content>
|
|
61
|
+
<Text margin="16px">{`Popover [${side}${alignment ? '-' : ''}${alignment}]`}</Text>
|
|
62
|
+
</Popover.Content>
|
|
63
|
+
</Popover>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
})}
|
|
67
|
+
</Flexbox>
|
|
68
|
+
);
|
|
69
|
+
})}
|
|
70
|
+
</Flexbox>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
export const TriggerIsNotAButton = () => (
|
|
74
|
+
<Popover>
|
|
75
|
+
<Popover.Trigger>
|
|
76
|
+
This text has a popover but this will never be displayed because the trigger is not a button.
|
|
77
|
+
</Popover.Trigger>
|
|
78
|
+
<Popover.Content>
|
|
79
|
+
<Text margin="16px">
|
|
80
|
+
Cupcake ipsum dolor sit amet blueie jujubes topping sesame snaps. Liquorice marzipan jelly-o carrot cake icing
|
|
81
|
+
croissant carrot cake. Tart soufflé sweet roll halvah croissant wafer cotton candy. Candy halvah marzipan bear
|
|
82
|
+
claw donut.
|
|
83
|
+
</Text>
|
|
84
|
+
</Popover.Content>
|
|
85
|
+
</Popover>
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
export const EmptyPopover = () => (
|
|
89
|
+
<Flexbox
|
|
90
|
+
flexDirection="row"
|
|
91
|
+
flexWrap="wrap"
|
|
92
|
+
justifyContent="center"
|
|
93
|
+
alignItems="center"
|
|
94
|
+
padding="32px"
|
|
95
|
+
style={{ backgroundColor: '#EFEFEF' }}
|
|
96
|
+
>
|
|
97
|
+
<Popover>
|
|
98
|
+
<Popover.Trigger>
|
|
99
|
+
<Button variant="secondary">Button</Button>
|
|
100
|
+
</Popover.Trigger>
|
|
101
|
+
</Popover>
|
|
102
|
+
</Flexbox>
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
export const PopoverOnDisabledButton = () => (
|
|
106
|
+
<Flexbox
|
|
107
|
+
flexDirection="row"
|
|
108
|
+
flexWrap="wrap"
|
|
109
|
+
justifyContent="center"
|
|
110
|
+
alignItems="center"
|
|
111
|
+
padding="32px"
|
|
112
|
+
style={{ backgroundColor: '#EFEFEF' }}
|
|
113
|
+
>
|
|
114
|
+
<Popover>
|
|
115
|
+
<Popover.Trigger>
|
|
116
|
+
<Button variant="secondary" isDisabled>
|
|
117
|
+
Disabled
|
|
118
|
+
</Button>
|
|
119
|
+
</Popover.Trigger>
|
|
120
|
+
<Popover.Content>
|
|
121
|
+
<Text margin="16px">
|
|
122
|
+
Cupcake ipsum dolor sit amet blueie jujubes topping sesame snaps. Liquorice marzipan jelly-o carrot cake icing
|
|
123
|
+
croissant carrot cake. Tart soufflé sweet roll halvah croissant wafer cotton candy. Candy halvah marzipan bear
|
|
124
|
+
claw donut.
|
|
125
|
+
</Text>
|
|
126
|
+
</Popover.Content>
|
|
127
|
+
</Popover>
|
|
128
|
+
</Flexbox>
|
|
129
|
+
);
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
+
|
|
4
|
+
import { Button } from '@redsift/design-system';
|
|
5
|
+
import { PopoverContent } from '../popover-content';
|
|
6
|
+
import { PopoverTrigger } from '../popover-trigger';
|
|
7
|
+
import { Popover } from '.';
|
|
8
|
+
|
|
9
|
+
describe('Popover', () => {
|
|
10
|
+
const onOpenSpy = jest.fn();
|
|
11
|
+
const realError = console.error;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
console.error = jest.fn();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
onOpenSpy.mockClear();
|
|
19
|
+
console.error = realError;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it.each`
|
|
23
|
+
Name | Component | props
|
|
24
|
+
${'PopoverTrigger'} | ${PopoverTrigger} | ${{}}
|
|
25
|
+
${'PopoverContent'} | ${PopoverContent} | ${{}}
|
|
26
|
+
`(
|
|
27
|
+
'$Name should throw error when not wrapped inside `Popover`',
|
|
28
|
+
function ({ Component, props }) {
|
|
29
|
+
expect(() => render(<Component {...props} />)).toThrow(
|
|
30
|
+
'Popover components must be wrapped in <Popover />'
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
it.each`
|
|
36
|
+
Name | Component | props
|
|
37
|
+
${'Controlled Popover'} | ${Popover} | ${{ onOpen: onOpenSpy, isOpen: false }}
|
|
38
|
+
${'Uncontrolled Popover'} | ${Popover} | ${{ onOpen: onOpenSpy, defaultOpen: false }}
|
|
39
|
+
`(
|
|
40
|
+
'$Name can be closed by default and then open',
|
|
41
|
+
function ({ Name, Component, props }) {
|
|
42
|
+
const { getByText } = render(
|
|
43
|
+
<Component {...props}>
|
|
44
|
+
<PopoverTrigger>
|
|
45
|
+
<Button variant="secondary">Trigger</Button>
|
|
46
|
+
</PopoverTrigger>
|
|
47
|
+
<PopoverContent>Content</PopoverContent>
|
|
48
|
+
</Component>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(screen.queryByText('Content')).not.toBeInTheDocument();
|
|
52
|
+
expect(onOpenSpy).not.toHaveBeenCalled();
|
|
53
|
+
|
|
54
|
+
const triggerButton = getByText('Trigger');
|
|
55
|
+
fireEvent.click(triggerButton);
|
|
56
|
+
|
|
57
|
+
expect(onOpenSpy).toHaveBeenCalled();
|
|
58
|
+
if (Name.includes('Uncontrolled')) {
|
|
59
|
+
expect(getByText('Content')).toBeVisible();
|
|
60
|
+
|
|
61
|
+
fireEvent.click(triggerButton);
|
|
62
|
+
|
|
63
|
+
expect(screen.queryByText('Content')).not.toBeInTheDocument();
|
|
64
|
+
expect(onOpenSpy).toHaveBeenCalled();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
it.each`
|
|
70
|
+
Name | Component | props
|
|
71
|
+
${'Controlled Popover'} | ${Popover} | ${{ onOpen: onOpenSpy, isOpen: true }}
|
|
72
|
+
${'Uncontrolled Popover'} | ${Popover} | ${{ onOpen: onOpenSpy, defaultOpen: true }}
|
|
73
|
+
`(
|
|
74
|
+
'$Name can be open by default and then closed',
|
|
75
|
+
function ({ Name, Component, props }) {
|
|
76
|
+
const { getByText } = render(
|
|
77
|
+
<Component {...props}>
|
|
78
|
+
<PopoverTrigger>
|
|
79
|
+
<Button variant="secondary">Trigger</Button>
|
|
80
|
+
</PopoverTrigger>
|
|
81
|
+
<PopoverContent>Content</PopoverContent>
|
|
82
|
+
</Component>
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
expect(getByText('Content')).toBeVisible();
|
|
86
|
+
expect(onOpenSpy).not.toHaveBeenCalled();
|
|
87
|
+
|
|
88
|
+
const triggerButton = getByText('Trigger');
|
|
89
|
+
fireEvent.click(triggerButton);
|
|
90
|
+
|
|
91
|
+
expect(onOpenSpy).toHaveBeenCalled();
|
|
92
|
+
if (Name.includes('Uncontrolled')) {
|
|
93
|
+
expect(screen.queryByText('Content')).not.toBeInTheDocument();
|
|
94
|
+
|
|
95
|
+
fireEvent.click(triggerButton);
|
|
96
|
+
|
|
97
|
+
expect(getByText('Content')).toBeVisible();
|
|
98
|
+
expect(onOpenSpy).toHaveBeenCalled();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { partitionComponents, isComponent } from '@redsift/design-system';
|
|
3
|
+
import { PopoverContent } from '../popover-content';
|
|
4
|
+
import { PopoverTrigger } from '../popover-trigger';
|
|
5
|
+
|
|
6
|
+
import { PopoverContext } from './context';
|
|
7
|
+
import { PopoverPlacement, PopoverProps } from './types';
|
|
8
|
+
import { usePopover } from './usePopover';
|
|
9
|
+
|
|
10
|
+
const COMPONENT_NAME = 'Popover';
|
|
11
|
+
const CLASSNAME = 'redsift-popover';
|
|
12
|
+
const DEFAULT_PROPS: Partial<PopoverProps> = {
|
|
13
|
+
isModal: false,
|
|
14
|
+
placement: PopoverPlacement.bottom,
|
|
15
|
+
role: 'dialog',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The Popover component.
|
|
20
|
+
*/
|
|
21
|
+
export const BasePopover: React.FC<PopoverProps> & {
|
|
22
|
+
displayName?: string;
|
|
23
|
+
className?: string;
|
|
24
|
+
} = (props) => {
|
|
25
|
+
const {
|
|
26
|
+
children,
|
|
27
|
+
defaultOpen,
|
|
28
|
+
isModal,
|
|
29
|
+
isOpen,
|
|
30
|
+
maxWidth,
|
|
31
|
+
minWidth,
|
|
32
|
+
onOpen,
|
|
33
|
+
overrideDisplayName,
|
|
34
|
+
placement,
|
|
35
|
+
role,
|
|
36
|
+
width,
|
|
37
|
+
} = props;
|
|
38
|
+
|
|
39
|
+
const popover = usePopover({
|
|
40
|
+
defaultOpen,
|
|
41
|
+
isModal,
|
|
42
|
+
isOpen,
|
|
43
|
+
maxWidth,
|
|
44
|
+
minWidth,
|
|
45
|
+
onOpen,
|
|
46
|
+
placement,
|
|
47
|
+
role,
|
|
48
|
+
width,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const [[trigger], [content]] = partitionComponents(React.Children.toArray(children), [
|
|
52
|
+
isComponent(overrideDisplayName?.trigger ?? 'PopoverTrigger'),
|
|
53
|
+
isComponent(overrideDisplayName?.content ?? 'PopoverContent'),
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<PopoverContext.Provider value={popover}>
|
|
58
|
+
{trigger}
|
|
59
|
+
{content}
|
|
60
|
+
</PopoverContext.Provider>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
BasePopover.className = CLASSNAME;
|
|
64
|
+
BasePopover.defaultProps = DEFAULT_PROPS;
|
|
65
|
+
BasePopover.displayName = COMPONENT_NAME;
|
|
66
|
+
|
|
67
|
+
export const Popover = Object.assign(BasePopover, {
|
|
68
|
+
Trigger: PopoverTrigger,
|
|
69
|
+
Content: PopoverContent,
|
|
70
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { ValueOf } from '@redsift/design-system';
|
|
3
|
+
import { usePopover } from './usePopover';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Context props.
|
|
7
|
+
*/
|
|
8
|
+
export type PopoverState = ReturnType<typeof usePopover> | null;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Component variant.
|
|
12
|
+
*/
|
|
13
|
+
export const PopoverPlacement = {
|
|
14
|
+
top: 'top',
|
|
15
|
+
right: 'right',
|
|
16
|
+
bottom: 'bottom',
|
|
17
|
+
left: 'left',
|
|
18
|
+
'top-start': 'top-start',
|
|
19
|
+
'top-end': 'top-end',
|
|
20
|
+
'right-start': 'right-start',
|
|
21
|
+
'right-end': 'right-end',
|
|
22
|
+
'bottom-start': 'bottom-start',
|
|
23
|
+
'bottom-end': 'bottom-end',
|
|
24
|
+
'left-start': 'left-start',
|
|
25
|
+
'left-end': 'left-end',
|
|
26
|
+
} as const;
|
|
27
|
+
export type PopoverPlacement = ValueOf<typeof PopoverPlacement>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Component props.
|
|
31
|
+
*/
|
|
32
|
+
export interface PopoverProps {
|
|
33
|
+
/** Popover content width. Can be either based on the trigger, the available space or define with a number of pixels. */
|
|
34
|
+
width?: 'trigger-width' | 'available-width' | number;
|
|
35
|
+
/** Popover content min width. Can be either based on the trigger, the available space or define with a number of pixels. */
|
|
36
|
+
minWidth?: 'trigger-width' | 'available-width' | number;
|
|
37
|
+
/** Popover content max width. Can be either based on the trigger, the available space or define with a number of pixels. */
|
|
38
|
+
maxWidth?: 'trigger-width' | 'available-width' | number;
|
|
39
|
+
/** Children. Can only be PopoverTrigger and PopoverContent. */
|
|
40
|
+
children: ReactNode;
|
|
41
|
+
/**
|
|
42
|
+
* Default open status.
|
|
43
|
+
* Used for uncontrolled version.
|
|
44
|
+
*/
|
|
45
|
+
defaultOpen?: boolean;
|
|
46
|
+
/** Default placement of the popover. */
|
|
47
|
+
placement?: PopoverPlacement;
|
|
48
|
+
/** Whether the popover is a modal or not. */
|
|
49
|
+
isModal?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Whether the component is opened or not.
|
|
52
|
+
* Used for controlled version.
|
|
53
|
+
*/
|
|
54
|
+
isOpen?: boolean;
|
|
55
|
+
/** Method to handle component change. */
|
|
56
|
+
onOpen?: (open: boolean) => void;
|
|
57
|
+
/** Allows other components to be treated as trigger and content. */
|
|
58
|
+
overrideDisplayName?: {
|
|
59
|
+
content?: string;
|
|
60
|
+
trigger?: string;
|
|
61
|
+
};
|
|
62
|
+
/** Role to apply to the popover. */
|
|
63
|
+
role?: 'dialog' | 'menu' | 'listbox';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type StyledPopoverProps = PopoverProps;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
useFloating,
|
|
4
|
+
autoUpdate,
|
|
5
|
+
offset,
|
|
6
|
+
flip,
|
|
7
|
+
shift,
|
|
8
|
+
size,
|
|
9
|
+
useDismiss,
|
|
10
|
+
useRole,
|
|
11
|
+
useInteractions,
|
|
12
|
+
} from '@floating-ui/react';
|
|
13
|
+
import { PopoverProps } from './types';
|
|
14
|
+
|
|
15
|
+
export function usePopover({
|
|
16
|
+
defaultOpen,
|
|
17
|
+
isModal,
|
|
18
|
+
isOpen: propsIsOpen,
|
|
19
|
+
maxWidth,
|
|
20
|
+
minWidth,
|
|
21
|
+
onOpen,
|
|
22
|
+
placement,
|
|
23
|
+
role: propsRole,
|
|
24
|
+
width,
|
|
25
|
+
}: Omit<PopoverProps, 'children'>) {
|
|
26
|
+
const [isOpen, setIsOpen] = useState(propsIsOpen ?? defaultOpen);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
setIsOpen(propsIsOpen ?? defaultOpen);
|
|
30
|
+
}, [propsIsOpen, defaultOpen]);
|
|
31
|
+
|
|
32
|
+
const handleOpen = useCallback(
|
|
33
|
+
(collapsed: boolean) => {
|
|
34
|
+
if (onOpen) {
|
|
35
|
+
onOpen(collapsed);
|
|
36
|
+
}
|
|
37
|
+
if (propsIsOpen === undefined || propsIsOpen === null) {
|
|
38
|
+
setIsOpen(collapsed);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
[onOpen]
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const middleware = [
|
|
45
|
+
offset(2),
|
|
46
|
+
flip({
|
|
47
|
+
fallbackAxisSideDirection: 'end',
|
|
48
|
+
}),
|
|
49
|
+
shift({ padding: 2 }),
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
middleware.push(
|
|
53
|
+
size({
|
|
54
|
+
apply({ availableHeight, availableWidth, rects, elements }) {
|
|
55
|
+
Object.assign(elements.floating.style, {
|
|
56
|
+
maxHeight: `${availableHeight}px`,
|
|
57
|
+
...(minWidth !== undefined && {
|
|
58
|
+
minWidth: `${
|
|
59
|
+
minWidth === 'available-width'
|
|
60
|
+
? availableWidth
|
|
61
|
+
: minWidth === 'trigger-width'
|
|
62
|
+
? rects.reference.width
|
|
63
|
+
: minWidth
|
|
64
|
+
}px`,
|
|
65
|
+
}),
|
|
66
|
+
...(width !== undefined && {
|
|
67
|
+
width: `${
|
|
68
|
+
width === 'available-width' ? availableWidth : width === 'trigger-width' ? rects.reference.width : width
|
|
69
|
+
}px`,
|
|
70
|
+
}),
|
|
71
|
+
...(maxWidth !== undefined && {
|
|
72
|
+
maxWidth: `${
|
|
73
|
+
maxWidth === 'available-width'
|
|
74
|
+
? availableWidth
|
|
75
|
+
: maxWidth === 'trigger-width'
|
|
76
|
+
? rects.reference.width
|
|
77
|
+
: maxWidth
|
|
78
|
+
}px`,
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const data = useFloating({
|
|
86
|
+
placement,
|
|
87
|
+
open: isOpen,
|
|
88
|
+
onOpenChange: handleOpen,
|
|
89
|
+
whileElementsMounted: autoUpdate,
|
|
90
|
+
middleware,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const context = data.context;
|
|
94
|
+
|
|
95
|
+
const dismiss = useDismiss(context);
|
|
96
|
+
const role = useRole(context, { role: propsRole });
|
|
97
|
+
|
|
98
|
+
const interactions = useInteractions([dismiss, role]);
|
|
99
|
+
|
|
100
|
+
return React.useMemo(
|
|
101
|
+
() => ({
|
|
102
|
+
isOpen,
|
|
103
|
+
handleOpen,
|
|
104
|
+
...interactions,
|
|
105
|
+
...data,
|
|
106
|
+
isModal,
|
|
107
|
+
}),
|
|
108
|
+
[isOpen, handleOpen, interactions, data, isModal]
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { PopoverContext } from './context';
|
|
3
|
+
|
|
4
|
+
export const usePopoverContext = () => {
|
|
5
|
+
const context = React.useContext(PopoverContext);
|
|
6
|
+
|
|
7
|
+
if (context == null) {
|
|
8
|
+
throw new Error('Popover components must be wrapped in <Popover />');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return context;
|
|
12
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { useMergeRefs, FloatingPortal, FloatingFocusManager } from '@floating-ui/react';
|
|
4
|
+
|
|
5
|
+
import { Comp } from '@redsift/design-system';
|
|
6
|
+
import { PopoverContentProps } from './types';
|
|
7
|
+
import { usePopoverContext } from '../popover';
|
|
8
|
+
import { StyledPopoverContent } from './styles';
|
|
9
|
+
|
|
10
|
+
const COMPONENT_NAME = 'PopoverContent';
|
|
11
|
+
const CLASSNAME = 'redsift-popover-content';
|
|
12
|
+
const DEFAULT_PROPS: Partial<PopoverContentProps> = {};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The PopoverContent component.
|
|
16
|
+
*/
|
|
17
|
+
export const PopoverContent: Comp<PopoverContentProps, HTMLDivElement> = forwardRef((props, ref) => {
|
|
18
|
+
const { children, className, style, ...forwardedProps } = props;
|
|
19
|
+
const { context: floatingContext, getFloatingProps, isModal, isOpen, refs, strategy, x, y } = usePopoverContext();
|
|
20
|
+
const popoverRef = useMergeRefs([refs.setFloating, ref]);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<FloatingPortal id="redsift-app-container">
|
|
24
|
+
{isOpen && (
|
|
25
|
+
<FloatingFocusManager context={floatingContext} modal={isModal}>
|
|
26
|
+
<StyledPopoverContent
|
|
27
|
+
style={{
|
|
28
|
+
position: strategy,
|
|
29
|
+
top: y ?? 0,
|
|
30
|
+
left: x ?? 0,
|
|
31
|
+
width: 'fit-content',
|
|
32
|
+
...style,
|
|
33
|
+
}}
|
|
34
|
+
{...getFloatingProps(props)}
|
|
35
|
+
className={classNames(PopoverContent.className, className)}
|
|
36
|
+
{...forwardedProps}
|
|
37
|
+
ref={popoverRef}
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
</StyledPopoverContent>
|
|
41
|
+
</FloatingFocusManager>
|
|
42
|
+
)}
|
|
43
|
+
</FloatingPortal>
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
PopoverContent.className = CLASSNAME;
|
|
47
|
+
PopoverContent.defaultProps = DEFAULT_PROPS;
|
|
48
|
+
PopoverContent.displayName = COMPONENT_NAME;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import styled, { css } from 'styled-components';
|
|
2
|
+
import { StyledPopoverContentProps } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Component style.
|
|
6
|
+
*/
|
|
7
|
+
export const StyledPopoverContent = styled.div<StyledPopoverContentProps>`
|
|
8
|
+
${({ display }) => (display ? `display: ${display};` : 'display: flex;')}
|
|
9
|
+
font-family: var(--redsift-typography-body-font-family);
|
|
10
|
+
font-size: var(--redsift-typography-body-font-size);
|
|
11
|
+
font-weight: var(--redsift-typography-body-font-weight);
|
|
12
|
+
line-height: 20px;
|
|
13
|
+
color: var(--redsift-color-neutral-black);
|
|
14
|
+
|
|
15
|
+
${({ padding, paddingBottom, paddingLeft, paddingRight, paddingTop }) =>
|
|
16
|
+
css`
|
|
17
|
+
${padding ? `padding: ${padding};` : ''}
|
|
18
|
+
${paddingBottom ? `padding-bottom: ${paddingBottom};` : ''}
|
|
19
|
+
${paddingLeft ? `padding-left: ${paddingLeft};` : ''}
|
|
20
|
+
${paddingRight ? `padding-right: ${paddingRight};` : ''}
|
|
21
|
+
${paddingTop ? `padding-top: ${paddingTop};` : ''}
|
|
22
|
+
`}
|
|
23
|
+
${({ margin, marginBottom, marginLeft, marginRight, marginTop }) =>
|
|
24
|
+
css`
|
|
25
|
+
${margin ? `margin: ${margin};` : ''}
|
|
26
|
+
${marginBottom ? `margin-bottom: ${marginBottom};` : ''}
|
|
27
|
+
${marginLeft ? `margin-left: ${marginLeft};` : ''}
|
|
28
|
+
${marginRight ? `margin-right: ${marginRight};` : ''}
|
|
29
|
+
${marginTop ? `margin-top: ${marginTop};` : ''}
|
|
30
|
+
`}
|
|
31
|
+
${({ height, maxHeight, maxWidth, minHeight, minWidth, width }) =>
|
|
32
|
+
css`
|
|
33
|
+
${height !== undefined ? `height: ${typeof height === 'number' ? `${height}px` : height};` : ''}
|
|
34
|
+
${maxHeight ? `max-height: ${maxHeight};` : ''}
|
|
35
|
+
${maxWidth ? `max-width: ${maxWidth};` : ''}
|
|
36
|
+
${minHeight ? `min-height: ${minHeight};` : ''}
|
|
37
|
+
${minWidth ? `min-width: ${minWidth};` : ''}
|
|
38
|
+
${width !== undefined ? `width: ${typeof width === 'number' ? `${width}px` : width};` : ''}
|
|
39
|
+
`}
|
|
40
|
+
${({ position, top, bottom, left, right, zIndex }) =>
|
|
41
|
+
css`
|
|
42
|
+
${position ? `position: ${position};` : ''}
|
|
43
|
+
${top ? `top: ${top};` : ''}
|
|
44
|
+
${bottom ? `bottom: ${bottom};` : ''}
|
|
45
|
+
${left ? `left: ${left};` : ''}
|
|
46
|
+
${right ? `right: ${right};` : ''}
|
|
47
|
+
${zIndex ? `z-index: ${zIndex};` : ''}
|
|
48
|
+
`}
|
|
49
|
+
${({
|
|
50
|
+
alignContent,
|
|
51
|
+
alignItems,
|
|
52
|
+
gap,
|
|
53
|
+
gridAutoColumns,
|
|
54
|
+
gridAutoRows,
|
|
55
|
+
gridTemplateAreas,
|
|
56
|
+
gridTemplateColumns,
|
|
57
|
+
gridTemplateRows,
|
|
58
|
+
justifyContent,
|
|
59
|
+
justifyItems,
|
|
60
|
+
}) =>
|
|
61
|
+
css`
|
|
62
|
+
${alignContent ? `align-content: ${alignContent};` : ''}
|
|
63
|
+
${alignItems ? `align-items: ${alignItems};` : ''}
|
|
64
|
+
${gap ? `gap: ${gap};` : ''}
|
|
65
|
+
${gridAutoColumns ? `grid-auto-columns: ${gridAutoColumns};` : ''}
|
|
66
|
+
${gridAutoRows ? `grid-auto-rows: ${gridAutoRows};` : ''}
|
|
67
|
+
${gridTemplateAreas ? `grid-template-areas: ${gridTemplateAreas};` : ''}
|
|
68
|
+
${gridTemplateColumns ? `grid-template-columns: ${gridTemplateColumns};` : ''}
|
|
69
|
+
${gridTemplateRows ? `grid-template-rows: ${gridTemplateRows};` : ''}
|
|
70
|
+
${justifyContent ? `justify-content: ${justifyContent};` : ''}
|
|
71
|
+
${justifyItems ? `justify-items: ${justifyItems};` : ''}
|
|
72
|
+
`}
|
|
73
|
+
${({ alignContent, alignItems, flexDirection, flexWrap, gap, justifyContent }) =>
|
|
74
|
+
css`
|
|
75
|
+
${alignContent ? `align-content: ${alignContent};` : ''}
|
|
76
|
+
${alignItems ? `align-items: ${alignItems};` : ''}
|
|
77
|
+
${flexDirection ? `flex-direction: ${flexDirection};` : ''}
|
|
78
|
+
${flexWrap ? `flex-wrap: ${flexWrap};` : ''}
|
|
79
|
+
gap: ${gap};
|
|
80
|
+
${justifyContent ? `justify-content: ${justifyContent};` : ''}
|
|
81
|
+
`}
|
|
82
|
+
|
|
83
|
+
background-color: var(--redsift-color-neutral-white);
|
|
84
|
+
border-radius: 4px;
|
|
85
|
+
box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2);
|
|
86
|
+
color: var(--redsift-color-neutral-black);
|
|
87
|
+
filter: drop-shadow(0px 4px 5px rgba(0, 0, 0, 0.14)) drop-shadow(0px 1px 10px rgba(0, 0, 0, 0.12));
|
|
88
|
+
max-width: calc(100vw - 48px);
|
|
89
|
+
z-index: var(--redsift-layout-z-index-popover);
|
|
90
|
+
overflow: auto;
|
|
91
|
+
|
|
92
|
+
&:focus-visible {
|
|
93
|
+
outline: none;
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ComponentProps } from 'react';
|
|
2
|
+
import { ContainerProps } from '@redsift/design-system';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Component props.
|
|
6
|
+
*/
|
|
7
|
+
export interface PopoverContentProps
|
|
8
|
+
extends ComponentProps<'div'>,
|
|
9
|
+
ContainerProps {}
|
|
10
|
+
|
|
11
|
+
export type StyledPopoverContentProps = PopoverContentProps;
|