@hyphen/hyphen-components 5.8.0 → 6.0.0
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/dist/components/DateInput/DateInput.d.ts +4 -2
- package/dist/components/Popover/Popover.d.ts +8 -80
- package/dist/components/Popover/Popover.stories.d.ts +2 -9
- package/dist/css/index.css +1 -1
- package/dist/css/utilities.css +21 -9
- package/dist/css/variables.css +8 -4
- package/dist/hyphen-components.cjs.development.js +455 -564
- package/dist/hyphen-components.cjs.development.js.map +1 -1
- package/dist/hyphen-components.cjs.production.min.js +1 -1
- package/dist/hyphen-components.cjs.production.min.js.map +1 -1
- package/dist/hyphen-components.esm.js +452 -566
- package/dist/hyphen-components.esm.js.map +1 -1
- package/dist/lib/tokens.d.ts +3 -3
- package/dist/types/index.d.ts +1 -1
- package/package.json +10 -12
- package/src/components/Box/Box.stories.tsx +2 -2
- package/src/components/DateInput/DateInput.stories.tsx +3 -3
- package/src/components/DateInput/DateInput.test.tsx +19 -101
- package/src/components/DateInput/DateInput.tsx +101 -78
- package/src/components/Popover/Popover.mdx +7 -90
- package/src/components/Popover/Popover.module.scss +52 -58
- package/src/components/Popover/Popover.stories.tsx +81 -442
- package/src/components/Popover/Popover.test.tsx +106 -88
- package/src/components/Popover/Popover.tsx +25 -267
- package/src/hooks/useOpenClose/useOpenClose.mdx +1 -5
- package/src/hooks/useOpenClose/useOpenClose.stories.tsx +23 -20
- package/src/types/index.ts +1 -1
|
@@ -1,70 +1,102 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { render, screen, waitFor
|
|
3
|
-
import {
|
|
4
|
-
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import {
|
|
4
|
+
Popover,
|
|
5
|
+
PopoverTrigger,
|
|
6
|
+
PopoverContent,
|
|
7
|
+
PopoverPortal,
|
|
8
|
+
} from './Popover';
|
|
9
|
+
|
|
10
|
+
// Helper to render a controlled Popover
|
|
11
|
+
type ControlledPopoverProps = {
|
|
12
|
+
isOpen?: boolean;
|
|
13
|
+
onClickOutside?: (event: Event) => void;
|
|
14
|
+
placement?: string;
|
|
15
|
+
withPortal?: boolean;
|
|
16
|
+
portalTarget?: HTMLElement;
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
contentProps?: Record<string, any>;
|
|
19
|
+
[key: string]: any;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function ControlledPopover({
|
|
23
|
+
isOpen = true,
|
|
24
|
+
onClickOutside,
|
|
25
|
+
placement = 'right',
|
|
26
|
+
withPortal = false,
|
|
27
|
+
portalTarget,
|
|
28
|
+
children,
|
|
29
|
+
contentProps = {},
|
|
30
|
+
...rest
|
|
31
|
+
}: ControlledPopoverProps) {
|
|
32
|
+
return (
|
|
33
|
+
<Popover open={isOpen} {...rest}>
|
|
34
|
+
<PopoverTrigger asChild>
|
|
35
|
+
<button>trigger</button>
|
|
36
|
+
</PopoverTrigger>
|
|
37
|
+
{withPortal ? (
|
|
38
|
+
<PopoverPortal container={portalTarget}>
|
|
39
|
+
<PopoverContent
|
|
40
|
+
side={
|
|
41
|
+
placement.split('-')[0] as 'right' | 'top' | 'bottom' | 'left'
|
|
42
|
+
}
|
|
43
|
+
align={
|
|
44
|
+
(placement.split('-')[1] as
|
|
45
|
+
| 'center'
|
|
46
|
+
| 'end'
|
|
47
|
+
| 'start'
|
|
48
|
+
| undefined) || 'center'
|
|
49
|
+
}
|
|
50
|
+
{...contentProps}
|
|
51
|
+
onInteractOutside={onClickOutside}
|
|
52
|
+
>
|
|
53
|
+
{children}
|
|
54
|
+
</PopoverContent>
|
|
55
|
+
</PopoverPortal>
|
|
56
|
+
) : (
|
|
57
|
+
<PopoverContent
|
|
58
|
+
side={placement.split('-')[0] as 'right' | 'top' | 'bottom' | 'left'}
|
|
59
|
+
align={
|
|
60
|
+
(placement.split('-')[1] as
|
|
61
|
+
| 'center'
|
|
62
|
+
| 'end'
|
|
63
|
+
| 'start'
|
|
64
|
+
| undefined) || 'center'
|
|
65
|
+
}
|
|
66
|
+
{...contentProps}
|
|
67
|
+
onInteractOutside={onClickOutside}
|
|
68
|
+
>
|
|
69
|
+
{children}
|
|
70
|
+
</PopoverContent>
|
|
71
|
+
)}
|
|
72
|
+
</Popover>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
5
75
|
|
|
6
76
|
describe('Popover', () => {
|
|
7
77
|
describe('Default', () => {
|
|
8
78
|
it('Renders a popover with default props', async () => {
|
|
9
|
-
// NOTE: popperJS is throwing a warning due to missing act, but it is unclear how to fix these.
|
|
10
|
-
// https://github.com/popperjs/react-popper/issues/368
|
|
11
79
|
render(
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
</
|
|
80
|
+
<ControlledPopover>
|
|
81
|
+
<span>hello</span>
|
|
82
|
+
</ControlledPopover>
|
|
15
83
|
);
|
|
16
|
-
|
|
17
84
|
const popoverContent = screen.getByText('hello');
|
|
18
|
-
const popoverContainer = screen.getByRole('dialog');
|
|
19
85
|
const trigger = screen.getByText('trigger');
|
|
20
86
|
expect(popoverContent).toBeInTheDocument();
|
|
21
87
|
expect(trigger).toBeInTheDocument();
|
|
22
|
-
expect(trigger).toHaveAttribute('
|
|
23
|
-
|
|
24
|
-
expect(
|
|
25
|
-
expect(popoverContainer).toHaveAttribute('aria-hidden', 'false');
|
|
26
|
-
expect(popoverContainer).toHaveClass('background-color-primary');
|
|
27
|
-
expect(popoverContainer).toHaveClass('p-sm');
|
|
28
|
-
await waitFor(() =>
|
|
29
|
-
expect(popoverContainer).toHaveAttribute(
|
|
30
|
-
'data-popper-placement',
|
|
31
|
-
'right'
|
|
32
|
-
)
|
|
33
|
-
);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('Callbacks', () => {
|
|
38
|
-
it('Fires a callback when a user clicks outside the popover', () => {
|
|
39
|
-
const mockedOnClickOutside = jest.fn();
|
|
40
|
-
const { container } = render(
|
|
41
|
-
<Popover
|
|
42
|
-
isOpen
|
|
43
|
-
content={<>hello</>}
|
|
44
|
-
onClickOutside={mockedOnClickOutside}
|
|
45
|
-
>
|
|
46
|
-
<p>trigger</p>
|
|
47
|
-
</Popover>
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
const popover = screen.getByText('hello');
|
|
51
|
-
const trigger = screen.getByText('trigger');
|
|
52
|
-
expect(popover).toBeInTheDocument();
|
|
53
|
-
fireEvent.click(popover);
|
|
54
|
-
fireEvent.click(trigger);
|
|
55
|
-
fireEvent.click(container);
|
|
56
|
-
fireEvent.keyUp(container, { key: 'Escape' });
|
|
57
|
-
expect(mockedOnClickOutside).toBeCalledTimes(2);
|
|
88
|
+
expect(trigger).toHaveAttribute('type', 'button');
|
|
89
|
+
// Radix PopoverContent does not have role="dialog" by default
|
|
90
|
+
expect(popoverContent.parentElement).toHaveClass('PopoverContent');
|
|
58
91
|
});
|
|
59
92
|
});
|
|
60
93
|
|
|
61
94
|
describe('Placement', () => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
'
|
|
65
|
-
'
|
|
66
|
-
'
|
|
67
|
-
'left',
|
|
95
|
+
const positions = [
|
|
96
|
+
'top-center',
|
|
97
|
+
'bottom-center',
|
|
98
|
+
'right-center',
|
|
99
|
+
'left-center',
|
|
68
100
|
'top-start',
|
|
69
101
|
'top-end',
|
|
70
102
|
'bottom-start',
|
|
@@ -74,22 +106,22 @@ describe('Popover', () => {
|
|
|
74
106
|
'left-start',
|
|
75
107
|
'left-end',
|
|
76
108
|
];
|
|
77
|
-
|
|
78
109
|
positions.forEach((position) => {
|
|
79
|
-
it(`Places the
|
|
110
|
+
it(`Places the popover correctly in position: ${position} when prop is passed`, async () => {
|
|
80
111
|
render(
|
|
81
|
-
<
|
|
82
|
-
<
|
|
83
|
-
</
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
const popoverContainer = screen.getByRole('dialog');
|
|
87
|
-
await waitFor(() =>
|
|
88
|
-
expect(popoverContainer).toHaveAttribute(
|
|
89
|
-
'data-popper-placement',
|
|
90
|
-
position
|
|
91
|
-
)
|
|
112
|
+
<ControlledPopover placement={position}>
|
|
113
|
+
<span>hello</span>
|
|
114
|
+
</ControlledPopover>
|
|
92
115
|
);
|
|
116
|
+
const popoverContent = screen.getByText('hello').parentElement;
|
|
117
|
+
// Radix sets data-side and data-align
|
|
118
|
+
const [side, align] = position.split('-');
|
|
119
|
+
await waitFor(() => {
|
|
120
|
+
expect(popoverContent).toHaveAttribute('data-side', side);
|
|
121
|
+
if (align) {
|
|
122
|
+
expect(popoverContent).toHaveAttribute('data-align', align);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
93
125
|
});
|
|
94
126
|
});
|
|
95
127
|
});
|
|
@@ -97,31 +129,17 @@ describe('Popover', () => {
|
|
|
97
129
|
describe('Portal', () => {
|
|
98
130
|
it('Renders the Popover in the body if withPortal is true.', async () => {
|
|
99
131
|
render(
|
|
100
|
-
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
content={
|
|
106
|
-
<button type="button" id="inside-button">
|
|
107
|
-
hello
|
|
108
|
-
</button>
|
|
109
|
-
}
|
|
110
|
-
withPortal
|
|
111
|
-
portalTarget={document.body}
|
|
112
|
-
>
|
|
113
|
-
<p>trigger</p>
|
|
114
|
-
</Popover>
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
</>
|
|
132
|
+
<ControlledPopover withPortal portalTarget={document.body}>
|
|
133
|
+
<button type="button" id="inside-button">
|
|
134
|
+
hello
|
|
135
|
+
</button>
|
|
136
|
+
</ControlledPopover>
|
|
118
137
|
);
|
|
119
|
-
|
|
120
138
|
await waitFor(() => {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
'
|
|
124
|
-
);
|
|
139
|
+
// Should be in the body
|
|
140
|
+
expect(
|
|
141
|
+
document.body.querySelector('#inside-button')
|
|
142
|
+
).toBeInTheDocument();
|
|
125
143
|
});
|
|
126
144
|
});
|
|
127
145
|
});
|
|
@@ -1,277 +1,35 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
|
|
3
|
-
FC,
|
|
4
|
-
isValidElement,
|
|
5
|
-
ReactNode,
|
|
6
|
-
useEffect,
|
|
7
|
-
useRef,
|
|
8
|
-
useState,
|
|
9
|
-
RefObject,
|
|
10
|
-
} from 'react';
|
|
11
|
-
import { createPortal } from 'react-dom';
|
|
12
|
-
import { usePopper } from 'react-popper';
|
|
13
|
-
import { Placement } from '@popperjs/core';
|
|
14
|
-
import FocusTrap from 'focus-trap-react';
|
|
1
|
+
import React, { forwardRef, ElementRef, ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
15
3
|
import classNames from 'classnames';
|
|
16
|
-
import { BackgroundColor } from '../../types';
|
|
17
4
|
import styles from './Popover.module.scss';
|
|
18
|
-
import { Box, BoxProps } from '../Box/Box';
|
|
19
|
-
import { mergeRefs } from '../../lib';
|
|
20
5
|
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Custom class to apply to the alert.
|
|
24
|
-
*/
|
|
25
|
-
className?: string;
|
|
26
|
-
/**
|
|
27
|
-
* The trigger element
|
|
28
|
-
*/
|
|
29
|
-
children: ReactNode;
|
|
30
|
-
/**
|
|
31
|
-
* Content of the tooltip. Can be any JSX node.
|
|
32
|
-
*/
|
|
33
|
-
content: ReactNode;
|
|
34
|
-
/**
|
|
35
|
-
* The Popover is a controlled input, and will be shown when `isOpen === true`.
|
|
36
|
-
*/
|
|
37
|
-
isOpen: boolean;
|
|
38
|
-
/**
|
|
39
|
-
* Color of the arrow background. NOTE: That the arrowColor will default to the
|
|
40
|
-
* `background` color applied in the `contentContainerProps`, but can be overwritten
|
|
41
|
-
* by passing a specific value here.
|
|
42
|
-
*/
|
|
43
|
-
arrowColor?: BackgroundColor;
|
|
44
|
-
/**
|
|
45
|
-
* An object matching the interface of the `Box` component props.
|
|
46
|
-
* This is useful for styling the tooltip container using all the options available in
|
|
47
|
-
* a `Box`.
|
|
48
|
-
*/
|
|
49
|
-
contentContainerProps?: BoxProps;
|
|
50
|
-
/**
|
|
51
|
-
* Whether the arrow is shown.
|
|
52
|
-
*/
|
|
53
|
-
hasArrow?: boolean;
|
|
54
|
-
/**
|
|
55
|
-
* How far (in pixels) the Popover element will be from the target.
|
|
56
|
-
* Note that this is from the edge of the target to the edge of the popover content,
|
|
57
|
-
* and it DOES NOT include the arrow element.
|
|
58
|
-
*/
|
|
59
|
-
offsetFromTarget?: number;
|
|
60
|
-
/**
|
|
61
|
-
* Callback function to handle when a user clicks outside the Popover
|
|
62
|
-
*/
|
|
63
|
-
onClickOutside?: (event: MouseEvent | KeyboardEvent) => void;
|
|
64
|
-
/**
|
|
65
|
-
* The placement (position) of the Popover relative to its trigger.
|
|
66
|
-
*/
|
|
67
|
-
placement?: Placement;
|
|
68
|
-
/**
|
|
69
|
-
* Whether you want to trap focus in the Popover element when it is open.
|
|
70
|
-
* Read more about focus traps:
|
|
71
|
-
* [Here](https://allyjs.io/tutorials/accessible-dialog.html#trapping-focus-inside-the-dialog)
|
|
72
|
-
*/
|
|
73
|
-
trapFocus?: boolean;
|
|
74
|
-
/**
|
|
75
|
-
* Additional props to be spread to rendered element
|
|
76
|
-
*/
|
|
77
|
-
[x: string]: any; // eslint-disable-line
|
|
78
|
-
} & (
|
|
79
|
-
| {
|
|
80
|
-
/**
|
|
81
|
-
* Whether the element should be rendered outside its DOM structure
|
|
82
|
-
* for reasons of placement. Use this when the element is being cut-off or
|
|
83
|
-
* re-positioned due to lack of space in the parent container.
|
|
84
|
-
* NOTE: `portalTarget` is required if this is true.
|
|
85
|
-
*/
|
|
86
|
-
withPortal: true;
|
|
87
|
-
/**
|
|
88
|
-
* The target element where the Popover will be portaled to, when `withPortal === true`.
|
|
89
|
-
* `document.body` will work for many cases, but you can also use a custom container for this.
|
|
90
|
-
* Only required if withPortal is true.
|
|
91
|
-
*/
|
|
92
|
-
portalTarget: HTMLElement;
|
|
93
|
-
}
|
|
94
|
-
| {
|
|
95
|
-
withPortal?: false;
|
|
96
|
-
portalTarget?: never;
|
|
97
|
-
}
|
|
98
|
-
);
|
|
6
|
+
const Popover = PopoverPrimitive.Root;
|
|
99
7
|
|
|
100
|
-
const
|
|
101
|
-
background: 'primary',
|
|
102
|
-
padding: 'sm',
|
|
103
|
-
radius: 'sm',
|
|
104
|
-
shadow: 'md',
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
export const Popover: FC<PopoverProps> = ({
|
|
108
|
-
className,
|
|
109
|
-
isOpen,
|
|
110
|
-
children,
|
|
111
|
-
content,
|
|
112
|
-
arrowColor = undefined,
|
|
113
|
-
contentContainerProps = { ...contentContainerDefaults },
|
|
114
|
-
hasArrow = true,
|
|
115
|
-
offsetFromTarget = 12,
|
|
116
|
-
onClickOutside = undefined,
|
|
117
|
-
placement = 'right',
|
|
118
|
-
withPortal = false,
|
|
119
|
-
portalTarget,
|
|
120
|
-
trapFocus = false,
|
|
121
|
-
...restProps
|
|
122
|
-
}) => {
|
|
123
|
-
const triggerRef = useRef<HTMLElement>(null);
|
|
124
|
-
const popperRef = useRef<HTMLElement>(null);
|
|
125
|
-
const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null);
|
|
126
|
-
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
const handleClickOutside = (event: MouseEvent) => {
|
|
129
|
-
const popover = popperRef.current;
|
|
130
|
-
const trigger = triggerRef.current;
|
|
131
|
-
|
|
132
|
-
if (!popover || !trigger) {
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (event.target === trigger || trigger?.contains(event.target as Node)) {
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (
|
|
141
|
-
event.target !== popover &&
|
|
142
|
-
!popover?.contains(event.target as Node)
|
|
143
|
-
) {
|
|
144
|
-
if (onClickOutside) onClickOutside(event);
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const handleKeyUp = (event: KeyboardEvent) => {
|
|
149
|
-
if (event.key === 'Escape') {
|
|
150
|
-
if (onClickOutside) onClickOutside(event);
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
if (onClickOutside) {
|
|
155
|
-
document.body.addEventListener('click', handleClickOutside, false);
|
|
156
|
-
document.body.addEventListener('keyup', handleKeyUp);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return () => {
|
|
160
|
-
if (onClickOutside) {
|
|
161
|
-
document.body.removeEventListener('click', handleClickOutside, false);
|
|
162
|
-
document.body.removeEventListener('keyup', handleKeyUp);
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
}, [onClickOutside]);
|
|
166
|
-
|
|
167
|
-
const { styles: popperStyles, attributes } = usePopper(
|
|
168
|
-
triggerRef.current,
|
|
169
|
-
popperRef.current,
|
|
170
|
-
{
|
|
171
|
-
placement,
|
|
172
|
-
modifiers: [
|
|
173
|
-
{
|
|
174
|
-
name: 'arrow',
|
|
175
|
-
options: { element: arrowElement },
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
name: 'offset',
|
|
179
|
-
options: {
|
|
180
|
-
offset: [0, offsetFromTarget],
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
],
|
|
184
|
-
}
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
const containerBoxProps = {
|
|
188
|
-
...contentContainerDefaults,
|
|
189
|
-
...contentContainerProps,
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
const computedArrowColor = arrowColor || containerBoxProps.background;
|
|
193
|
-
|
|
194
|
-
const arrowClasses = classNames(
|
|
195
|
-
styles['popover-arrow'],
|
|
196
|
-
`background-color-${computedArrowColor}`,
|
|
197
|
-
{
|
|
198
|
-
'display-none': !hasArrow,
|
|
199
|
-
}
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
const renderPopperContent = () => {
|
|
203
|
-
const renderPopperBox = () => (
|
|
204
|
-
<Box
|
|
205
|
-
ref={popperRef}
|
|
206
|
-
className={classNames(styles.popover, className)}
|
|
207
|
-
style={popperStyles.popper}
|
|
208
|
-
role="dialog"
|
|
209
|
-
aria-label="Popover"
|
|
210
|
-
aria-hidden={!isOpen}
|
|
211
|
-
{...containerBoxProps}
|
|
212
|
-
{...attributes.popper}
|
|
213
|
-
{...restProps}
|
|
214
|
-
>
|
|
215
|
-
<div
|
|
216
|
-
ref={setArrowElement}
|
|
217
|
-
style={popperStyles.arrow}
|
|
218
|
-
className={arrowClasses}
|
|
219
|
-
data-popper-arrow
|
|
220
|
-
/>
|
|
221
|
-
{content}
|
|
222
|
-
</Box>
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
return trapFocus ? (
|
|
226
|
-
<FocusTrap
|
|
227
|
-
active={isOpen}
|
|
228
|
-
focusTrapOptions={{
|
|
229
|
-
clickOutsideDeactivates: true,
|
|
230
|
-
}}
|
|
231
|
-
>
|
|
232
|
-
{renderPopperBox()}
|
|
233
|
-
</FocusTrap>
|
|
234
|
-
) : (
|
|
235
|
-
renderPopperBox()
|
|
236
|
-
);
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
const childrenWithRef = React.Children.map(children, (child) => {
|
|
240
|
-
const childProps = {
|
|
241
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
242
|
-
ref: triggerRef as RefObject<HTMLElement> | ((instance: any) => void),
|
|
243
|
-
role: 'button',
|
|
244
|
-
'aria-expanded': isOpen,
|
|
245
|
-
'aria-haspopup': true,
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
// Merge local ref with any ref passed originally to child component.
|
|
249
|
-
// We have to cast with `as` so TS compiler doesn't complain since ReactNode/ReactChild types don't
|
|
250
|
-
// explicitly declare ref as a property in the object.
|
|
251
|
-
if ((child as ReactNode & { ref: any })?.ref) {
|
|
252
|
-
// eslint-disable-line @typescript-eslint/no-explicit-any
|
|
253
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
254
|
-
childProps.ref = mergeRefs([
|
|
255
|
-
(child as ReactNode & { ref: any })?.ref,
|
|
256
|
-
childProps.ref,
|
|
257
|
-
]);
|
|
258
|
-
}
|
|
8
|
+
const PopoverTrigger = PopoverPrimitive.Trigger;
|
|
259
9
|
|
|
260
|
-
|
|
261
|
-
return cloneElement(child, childProps);
|
|
262
|
-
}
|
|
10
|
+
const PopoverAnchor = PopoverPrimitive.Anchor;
|
|
263
11
|
|
|
264
|
-
|
|
265
|
-
});
|
|
12
|
+
const PopoverPortal = PopoverPrimitive.Portal;
|
|
266
13
|
|
|
14
|
+
const PopoverContent = forwardRef<
|
|
15
|
+
ElementRef<typeof PopoverPrimitive.Content>,
|
|
16
|
+
ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
|
17
|
+
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => {
|
|
267
18
|
return (
|
|
268
|
-
|
|
269
|
-
{
|
|
270
|
-
{
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
</>
|
|
19
|
+
<PopoverPrimitive.Content
|
|
20
|
+
ref={ref}
|
|
21
|
+
align={align}
|
|
22
|
+
sideOffset={sideOffset}
|
|
23
|
+
className={classNames(styles.PopoverContent, className)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
276
26
|
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
Popover,
|
|
31
|
+
PopoverTrigger,
|
|
32
|
+
PopoverAnchor,
|
|
33
|
+
PopoverPortal,
|
|
34
|
+
PopoverContent,
|
|
277
35
|
};
|
|
@@ -8,8 +8,4 @@ import * as Stories from './useOpenClose.stories';
|
|
|
8
8
|
|
|
9
9
|
The `useOpenClose` hook helps handle common open, close, or toggle scenarios. It can be used to control components such as `Modal`, `MediaModal`, `Drawer`, `Details` and even `Popover`.
|
|
10
10
|
|
|
11
|
-
<Canvas
|
|
12
|
-
|
|
13
|
-
## Parameters
|
|
14
|
-
|
|
15
|
-
<ArgTypes of={Stories.BasicUsage} />
|
|
11
|
+
<Canvas sourceState="shown" of={Stories.BasicUsage} />
|
|
@@ -2,8 +2,13 @@ import React from 'react';
|
|
|
2
2
|
import type { Meta } from '@storybook/react-vite';
|
|
3
3
|
import { UseOpenCloseProps, useOpenClose } from './useOpenClose';
|
|
4
4
|
import { Button } from '../../components/Button/Button';
|
|
5
|
+
import {
|
|
6
|
+
Popover,
|
|
7
|
+
PopoverContent,
|
|
8
|
+
PopoverPortal,
|
|
9
|
+
PopoverTrigger,
|
|
10
|
+
} from '../../components/Popover/Popover';
|
|
5
11
|
import { Box } from '../../components/Box/Box';
|
|
6
|
-
import { Popover } from '../../components/Popover/Popover';
|
|
7
12
|
|
|
8
13
|
const meta: Meta<typeof useOpenClose> = {
|
|
9
14
|
title: 'Hooks/useOpenClose',
|
|
@@ -16,26 +21,24 @@ export default meta;
|
|
|
16
21
|
|
|
17
22
|
export const BasicUsage: React.FC<UseOpenCloseProps> = () => {
|
|
18
23
|
const { isOpen, handleOpen, handleClose } = useOpenClose();
|
|
19
|
-
|
|
20
|
-
<>
|
|
21
|
-
<Box padding="lg" gap="md">
|
|
22
|
-
<Box as="p">Hello!</Box>
|
|
23
|
-
</Box>
|
|
24
|
-
</>
|
|
25
|
-
);
|
|
24
|
+
|
|
26
25
|
return (
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
<Box gap="2xl" direction="row">
|
|
27
|
+
<Popover open={isOpen}>
|
|
28
|
+
<PopoverTrigger asChild>
|
|
29
|
+
<Button variant="primary" type="button" onClick={handleOpen}>
|
|
30
|
+
Open Popover
|
|
31
|
+
</Button>
|
|
32
|
+
</PopoverTrigger>
|
|
33
|
+
<PopoverPortal>
|
|
34
|
+
<PopoverContent onInteractOutside={handleClose}>
|
|
35
|
+
<p>Hello!</p>
|
|
36
|
+
</PopoverContent>
|
|
37
|
+
</PopoverPortal>
|
|
38
|
+
</Popover>
|
|
39
|
+
<Button variant="secondary" onClick={handleOpen}>
|
|
40
|
+
also opens
|
|
38
41
|
</Button>
|
|
39
|
-
</
|
|
42
|
+
</Box>
|
|
40
43
|
);
|
|
41
44
|
};
|
package/src/types/index.ts
CHANGED
|
@@ -129,7 +129,7 @@ export type CssWhiteSpaceValue =
|
|
|
129
129
|
| 'pre-wrap'
|
|
130
130
|
| 'pre-line';
|
|
131
131
|
|
|
132
|
-
export type CssWordBreakValue = 'normal' | '
|
|
132
|
+
export type CssWordBreakValue = 'normal' | 'all' | 'keep';
|
|
133
133
|
|
|
134
134
|
export type CssTextAlignValue = 'left' | 'center' | 'right';
|
|
135
135
|
|