@lumx/react 3.11.0 → 3.11.1-alpha.1
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/package.json
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"url": "https://github.com/lumapps/design-system/issues"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@lumx/core": "^3.11.
|
|
10
|
-
"@lumx/icons": "^3.11.
|
|
9
|
+
"@lumx/core": "^3.11.1-alpha.1",
|
|
10
|
+
"@lumx/icons": "^3.11.1-alpha.1",
|
|
11
11
|
"@popperjs/core": "^2.5.4",
|
|
12
12
|
"body-scroll-lock": "^3.1.5",
|
|
13
13
|
"classnames": "^2.3.2",
|
|
@@ -110,5 +110,5 @@
|
|
|
110
110
|
"build:storybook": "storybook build"
|
|
111
111
|
},
|
|
112
112
|
"sideEffects": false,
|
|
113
|
-
"version": "3.11.
|
|
113
|
+
"version": "3.11.1-alpha.1"
|
|
114
114
|
}
|
|
@@ -14,11 +14,17 @@ import {
|
|
|
14
14
|
Popover,
|
|
15
15
|
Size,
|
|
16
16
|
Elevation,
|
|
17
|
+
Message,
|
|
18
|
+
FlexBox,
|
|
19
|
+
FlexBoxProps,
|
|
20
|
+
IconButtonProps,
|
|
17
21
|
} from '@lumx/react';
|
|
18
22
|
import range from 'lodash/range';
|
|
19
23
|
import { withCombinations } from '@lumx/react/stories/decorators/withCombinations';
|
|
20
24
|
import { getSelectArgType } from '@lumx/react/stories/controls/selectArgType';
|
|
21
25
|
import { withChromaticForceScreenSize } from '@lumx/react/stories/decorators/withChromaticForceScreenSize';
|
|
26
|
+
import { FitAnchorWidth } from '@lumx/react/components/popover/constants';
|
|
27
|
+
import { withUndefined } from '@lumx/react/stories/controls/withUndefined';
|
|
22
28
|
|
|
23
29
|
export default {
|
|
24
30
|
title: 'LumX components/popover/Popover',
|
|
@@ -110,164 +116,153 @@ export const Placements = {
|
|
|
110
116
|
],
|
|
111
117
|
};
|
|
112
118
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
onClose={toggleOpen}
|
|
149
|
-
fitWithinViewportHeight
|
|
150
|
-
>
|
|
151
|
-
<List>
|
|
152
|
-
<ListItem before={<Icon icon={mdiAccount} />} className="lumx-spacing-margin-right-huge">
|
|
153
|
-
<span>{text}</span>
|
|
154
|
-
</ListItem>
|
|
155
|
-
</List>
|
|
156
|
-
</Popover>
|
|
157
|
-
</div>
|
|
158
|
-
);
|
|
119
|
+
/**
|
|
120
|
+
* Demo all fitAnchorWidth configurations
|
|
121
|
+
*/
|
|
122
|
+
export const FitToAnchorWidth = {
|
|
123
|
+
render({ anchorText, fitAnchorWidth }: any) {
|
|
124
|
+
const anchorRef = useRef(null);
|
|
125
|
+
return (
|
|
126
|
+
<>
|
|
127
|
+
<Chip className="lumx-spacing-margin-huge" ref={anchorRef} size="s">
|
|
128
|
+
{anchorText}
|
|
129
|
+
</Chip>
|
|
130
|
+
<Popover
|
|
131
|
+
isOpen
|
|
132
|
+
className="lumx-spacing-padding"
|
|
133
|
+
placement="top"
|
|
134
|
+
anchorRef={anchorRef}
|
|
135
|
+
fitToAnchorWidth={fitAnchorWidth}
|
|
136
|
+
>
|
|
137
|
+
Popover {fitAnchorWidth}
|
|
138
|
+
</Popover>
|
|
139
|
+
</>
|
|
140
|
+
);
|
|
141
|
+
},
|
|
142
|
+
decorators: [
|
|
143
|
+
withCombinations({
|
|
144
|
+
combinations: {
|
|
145
|
+
cols: {
|
|
146
|
+
'Small Anchor': { anchorText: 'Small' },
|
|
147
|
+
'Large Anchor': { anchorText: 'Very very very very large anchor' },
|
|
148
|
+
},
|
|
149
|
+
rows: { key: 'fitAnchorWidth', options: withUndefined(Object.values(FitAnchorWidth)) },
|
|
150
|
+
},
|
|
151
|
+
cellStyle: { padding: 16 },
|
|
152
|
+
}),
|
|
153
|
+
],
|
|
159
154
|
};
|
|
160
155
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
156
|
+
/**
|
|
157
|
+
* Testing update of the popover on anchor and popover resize and move
|
|
158
|
+
*/
|
|
159
|
+
export const TestUpdatingChildrenAndMovingAnchor = {
|
|
160
|
+
render() {
|
|
161
|
+
const anchorRef = useRef(null);
|
|
162
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
163
|
+
|
|
164
|
+
const toggleOpen = () => setIsOpen(!isOpen);
|
|
164
165
|
|
|
165
|
-
|
|
166
|
+
const [text, setText] = useState('Initial large span of text');
|
|
167
|
+
const [anchorSize, setAnchorSize] = useState<IconButtonProps['size']>('m');
|
|
168
|
+
const [anchorPosition, setAnchorPosition] = useState<FlexBoxProps['vAlign']>('center');
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
if (isOpen) {
|
|
171
|
+
const timers = [
|
|
172
|
+
setTimeout(() => setText('Text'), 1000),
|
|
173
|
+
setTimeout(() => setAnchorSize('s'), 1500),
|
|
174
|
+
setTimeout(() => setAnchorPosition('left'), 2000),
|
|
175
|
+
];
|
|
176
|
+
return () => timers.forEach(clearTimeout);
|
|
177
|
+
}
|
|
178
|
+
setText('Initial large span of text');
|
|
179
|
+
setAnchorSize('m');
|
|
180
|
+
setAnchorPosition('center');
|
|
181
|
+
return undefined;
|
|
182
|
+
}, [isOpen]);
|
|
166
183
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
</div>
|
|
202
|
-
);
|
|
184
|
+
return (
|
|
185
|
+
<FlexBox orientation="vertical" gap="huge">
|
|
186
|
+
<Message kind="info">
|
|
187
|
+
Test popover text resize (after 1sec), anchor resize (after 1.5sec) and anchor move (after 2sec)
|
|
188
|
+
</Message>
|
|
189
|
+
<FlexBox orientation="horizontal" vAlign={anchorPosition}>
|
|
190
|
+
<IconButton
|
|
191
|
+
label="Notifications"
|
|
192
|
+
className="lumx-spacing-margin-right-huge"
|
|
193
|
+
ref={anchorRef}
|
|
194
|
+
emphasis={Emphasis.low}
|
|
195
|
+
icon={mdiBell}
|
|
196
|
+
size={anchorSize}
|
|
197
|
+
onClick={toggleOpen}
|
|
198
|
+
/>
|
|
199
|
+
<Popover
|
|
200
|
+
closeOnClickAway
|
|
201
|
+
closeOnEscape
|
|
202
|
+
isOpen={isOpen}
|
|
203
|
+
anchorRef={anchorRef}
|
|
204
|
+
placement={Placement.BOTTOM_END}
|
|
205
|
+
onClose={toggleOpen}
|
|
206
|
+
fitWithinViewportHeight
|
|
207
|
+
hasArrow
|
|
208
|
+
>
|
|
209
|
+
<Text as="p" className="lumx-spacing-padding-huge">
|
|
210
|
+
{text}
|
|
211
|
+
</Text>
|
|
212
|
+
</Popover>
|
|
213
|
+
</FlexBox>
|
|
214
|
+
</FlexBox>
|
|
215
|
+
);
|
|
216
|
+
},
|
|
217
|
+
parameters: { chromatic: { disable: true } },
|
|
203
218
|
};
|
|
204
219
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const container = {
|
|
215
|
-
alignItems: 'center',
|
|
216
|
-
display: 'flex',
|
|
217
|
-
justifyContent: 'center',
|
|
218
|
-
flexDirection: 'column',
|
|
219
|
-
gap: 150,
|
|
220
|
-
marginTop: 150,
|
|
221
|
-
} as const;
|
|
220
|
+
/**
|
|
221
|
+
* Testing popover with scroll inside
|
|
222
|
+
*/
|
|
223
|
+
export const TestScrollingPopover = {
|
|
224
|
+
render() {
|
|
225
|
+
const anchorRef = useRef(null);
|
|
226
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
222
227
|
|
|
223
|
-
|
|
224
|
-
const widthSmallAnchorRef = useRef(null);
|
|
225
|
-
const widthLargeAnchorRef = useRef(null);
|
|
226
|
-
const minWidthAnchorRef = useRef(null);
|
|
227
|
-
const defaultWidthAnchorRef = useRef(null);
|
|
228
|
+
const toggleOpen = () => setIsOpen(!isOpen);
|
|
228
229
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
<
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
<div>
|
|
264
|
-
<Chip ref={defaultWidthAnchorRef} size={Size.s}>
|
|
265
|
-
VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLargeAnchor
|
|
266
|
-
</Chip>
|
|
230
|
+
return (
|
|
231
|
+
<div style={{ float: 'right' }} className="lumx-spacing-margin-right-huge">
|
|
232
|
+
<IconButton
|
|
233
|
+
label="Notifications"
|
|
234
|
+
className="lumx-spacing-margin-right-huge"
|
|
235
|
+
ref={anchorRef}
|
|
236
|
+
emphasis={Emphasis.low}
|
|
237
|
+
icon={mdiBell}
|
|
238
|
+
size={Size.m}
|
|
239
|
+
onClick={toggleOpen}
|
|
240
|
+
/>
|
|
241
|
+
<Popover
|
|
242
|
+
closeOnClickAway
|
|
243
|
+
closeOnEscape
|
|
244
|
+
isOpen={isOpen}
|
|
245
|
+
anchorRef={anchorRef}
|
|
246
|
+
placement={Placement.BOTTOM_END}
|
|
247
|
+
onClose={toggleOpen}
|
|
248
|
+
fitWithinViewportHeight
|
|
249
|
+
>
|
|
250
|
+
<List style={{ overflowY: 'auto' }}>
|
|
251
|
+
{range(100).map((n: number) => {
|
|
252
|
+
return (
|
|
253
|
+
<ListItem
|
|
254
|
+
key={`key-${n}`}
|
|
255
|
+
before={<Icon icon={mdiAccount} />}
|
|
256
|
+
className="lumx-spacing-margin-right-huge"
|
|
257
|
+
>
|
|
258
|
+
<span>{`List item ${n} and some text`}</span>
|
|
259
|
+
</ListItem>
|
|
260
|
+
);
|
|
261
|
+
})}
|
|
262
|
+
</List>
|
|
263
|
+
</Popover>
|
|
267
264
|
</div>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
</div>
|
|
272
|
-
);
|
|
265
|
+
);
|
|
266
|
+
},
|
|
267
|
+
parameters: { chromatic: { disable: true } },
|
|
273
268
|
};
|
|
@@ -74,7 +74,6 @@ type Options = Pick<
|
|
|
74
74
|
| 'fitWithinViewportHeight'
|
|
75
75
|
| 'boundaryRef'
|
|
76
76
|
| 'anchorRef'
|
|
77
|
-
| 'children'
|
|
78
77
|
| 'placement'
|
|
79
78
|
| 'style'
|
|
80
79
|
| 'zIndex'
|
|
@@ -97,13 +96,11 @@ export function usePopoverStyle({
|
|
|
97
96
|
fitWithinViewportHeight,
|
|
98
97
|
boundaryRef,
|
|
99
98
|
anchorRef,
|
|
100
|
-
children,
|
|
101
99
|
placement,
|
|
102
100
|
style,
|
|
103
101
|
zIndex,
|
|
104
102
|
}: Options): Output {
|
|
105
103
|
const [popperElement, setPopperElement] = useState<null | HTMLElement>(null);
|
|
106
|
-
|
|
107
104
|
const [arrowElement, setArrowElement] = useState<null | HTMLElement>(null);
|
|
108
105
|
|
|
109
106
|
const actualOffset: [number, number] = [offset?.along ?? 0, (offset?.away ?? 0) + (hasArrow ? ARROW_SIZE : 0)];
|
|
@@ -137,9 +134,32 @@ export function usePopoverStyle({
|
|
|
137
134
|
}
|
|
138
135
|
|
|
139
136
|
const { styles, attributes, state, update } = usePopper(anchorRef.current, popperElement, { placement, modifiers });
|
|
137
|
+
|
|
138
|
+
// Auto update popover
|
|
140
139
|
useEffect(() => {
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
const { current: anchorElement } = anchorRef;
|
|
141
|
+
if (!update || !popperElement || !anchorElement || !WINDOW?.ResizeObserver) {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Only update once per frame
|
|
146
|
+
let frame: number | undefined;
|
|
147
|
+
function limitedUpdate() {
|
|
148
|
+
if (frame) return;
|
|
149
|
+
frame = requestAnimationFrame(() => {
|
|
150
|
+
update?.();
|
|
151
|
+
frame = undefined;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// On anchor or popover resize
|
|
156
|
+
const resizeObserver = new ResizeObserver(limitedUpdate);
|
|
157
|
+
resizeObserver.observe(anchorElement);
|
|
158
|
+
resizeObserver.observe(popperElement);
|
|
159
|
+
return () => {
|
|
160
|
+
resizeObserver.disconnect();
|
|
161
|
+
};
|
|
162
|
+
}, [anchorRef, popperElement, update]);
|
|
143
163
|
|
|
144
164
|
const position = state?.placement ?? placement;
|
|
145
165
|
|
|
@@ -150,8 +170,12 @@ export function usePopoverStyle({
|
|
|
150
170
|
newStyles.maxHeight = WINDOW?.innerHeight || DOCUMENT?.documentElement.clientHeight;
|
|
151
171
|
}
|
|
152
172
|
|
|
173
|
+
// Do not show the popover while it's not properly placed
|
|
174
|
+
if (!newStyles.transform) newStyles.visibility = 'hidden';
|
|
175
|
+
|
|
153
176
|
return newStyles;
|
|
154
177
|
}, [style, styles.popper, zIndex, fitWithinViewportHeight]);
|
|
178
|
+
|
|
155
179
|
return {
|
|
156
180
|
styles: { arrow: styles.arrow, popover: popoverStyle },
|
|
157
181
|
attributes,
|