@transferwise/components 0.0.0-experimental-77e4e3a → 0.0.0-experimental-0657ea4
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/build/ExpressiveMoneyInput/Chevron/Chevron.js +10 -2
- package/build/ExpressiveMoneyInput/Chevron/Chevron.js.map +1 -1
- package/build/ExpressiveMoneyInput/Chevron/Chevron.mjs +11 -3
- package/build/ExpressiveMoneyInput/Chevron/Chevron.mjs.map +1 -1
- package/build/ListItem/ListItem.js +5 -5
- package/build/ListItem/ListItem.js.map +1 -1
- package/build/ListItem/ListItem.mjs +5 -5
- package/build/ListItem/ListItem.mjs.map +1 -1
- package/build/common/Panel/Panel.js +13 -25
- package/build/common/Panel/Panel.js.map +1 -1
- package/build/common/Panel/Panel.mjs +10 -22
- package/build/common/Panel/Panel.mjs.map +1 -1
- package/build/common/Panel/Panel.positionUtils.js +68 -0
- package/build/common/Panel/Panel.positionUtils.js.map +1 -0
- package/build/common/Panel/Panel.positionUtils.mjs +63 -0
- package/build/common/Panel/Panel.positionUtils.mjs.map +1 -0
- package/build/common/PropsValues/position.js +0 -25
- package/build/common/PropsValues/position.js.map +1 -1
- package/build/common/PropsValues/position.mjs +1 -24
- package/build/common/PropsValues/position.mjs.map +1 -1
- package/build/main.css +12 -4
- package/build/styles/common/Panel/Panel.css +12 -4
- package/build/styles/main.css +12 -4
- package/build/types/Body/Body.d.ts +2 -2
- package/build/types/ExpressiveMoneyInput/Chevron/Chevron.d.ts.map +1 -1
- package/build/types/IconButton/IconButton.d.ts +1 -1
- package/build/types/MoneyInput/MoneyInput.d.ts +1 -1
- package/build/types/MoneyInput/MoneyInput.d.ts.map +1 -1
- package/build/types/Title/Title.d.ts +2 -2
- package/build/types/Upload/Steps/UploadImageStep/UploadImageStep.d.ts +1 -1
- package/build/types/UploadInput/UploadItem/UploadItemLink.d.ts +1 -1
- package/build/types/common/Panel/Panel.d.ts +1 -1
- package/build/types/common/Panel/Panel.d.ts.map +1 -1
- package/build/types/common/Panel/Panel.positionUtils.d.ts +7 -0
- package/build/types/common/Panel/Panel.positionUtils.d.ts.map +1 -0
- package/build/types/common/PropsValues/position.d.ts +0 -13
- package/build/types/common/PropsValues/position.d.ts.map +1 -1
- package/build/types/common/ResponsivePanel/ResponsivePanel.d.ts +1 -1
- package/package.json +2 -2
- package/src/ExpressiveMoneyInput/Chevron/Chevron.tsx +8 -3
- package/src/ListItem/ListItem.tsx +5 -5
- package/src/ListItem/_stories/ListItem.layout.test.story.tsx +0 -23
- package/src/common/Panel/Panel.css +12 -4
- package/src/common/Panel/Panel.less +27 -9
- package/src/common/Panel/Panel.positionUtils.ts +79 -0
- package/src/common/Panel/Panel.test.tsx +40 -0
- package/src/common/Panel/Panel.tsx +13 -24
- package/src/common/Panel/_stories/Panel.rtl.test.story.tsx +110 -0
- package/src/common/PropsValues/position.ts +0 -50
- package/src/main.css +12 -4
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Placement } from '@floating-ui/react';
|
|
2
1
|
export declare enum Position {
|
|
3
2
|
BOTTOM = "bottom",
|
|
4
3
|
BOTTOM_LEFT = "bottom-left",
|
|
@@ -19,16 +18,4 @@ export type PositionTop = 'top';
|
|
|
19
18
|
export type PositionRight = 'right';
|
|
20
19
|
export type PositionBottom = 'bottom';
|
|
21
20
|
export type PositionLeft = 'left';
|
|
22
|
-
export type PositionTopLeft = 'top-left';
|
|
23
|
-
export type PositionTopRight = 'top-right';
|
|
24
|
-
export type PositionBottomLeft = 'bottom-left';
|
|
25
|
-
export type PositionBottomRight = 'bottom-right';
|
|
26
|
-
export type PositionLeftTop = 'left-top';
|
|
27
|
-
export type PositionLeftBottom = 'left-bottom';
|
|
28
|
-
export type PositionRightTop = 'right-top';
|
|
29
|
-
export type PositionRightBottom = 'right-bottom';
|
|
30
|
-
/** Neptune position values that can be converted to floating-ui Placement */
|
|
31
|
-
export type FloatingPlacement = PositionTop | PositionRight | PositionBottom | PositionLeft | PositionTopLeft | PositionTopRight | PositionRightTop | PositionRightBottom | PositionBottomLeft | PositionBottomRight | PositionLeftTop | PositionLeftBottom;
|
|
32
|
-
/** Maps Neptune position values to floating-ui Placement */
|
|
33
|
-
export declare const positionToPlacement: (position: FloatingPlacement) => Placement;
|
|
34
21
|
//# sourceMappingURL=position.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"position.d.ts","sourceRoot":"","sources":["../../../../src/common/PropsValues/position.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"position.d.ts","sourceRoot":"","sources":["../../../../src/common/PropsValues/position.ts"],"names":[],"mappings":"AAAA,oBAAY,QAAQ;IAClB,MAAM,WAAW;IACjB,WAAW,gBAAgB;IAC3B,YAAY,iBAAiB;IAC7B,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,WAAW,gBAAgB;IAC3B,QAAQ,aAAa;IACrB,KAAK,UAAU;IACf,YAAY,iBAAiB;IAC7B,SAAS,cAAc;IACvB,GAAG,QAAQ;IACX,QAAQ,aAAa;IACrB,SAAS,cAAc;CACxB;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;AACtC,MAAM,MAAM,WAAW,GAAG,KAAK,CAAC;AAChC,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC;AACpC,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;AACtC,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC"}
|
|
@@ -4,7 +4,7 @@ declare const ResponsivePanel: import("react").ForwardRefExoticComponent<{
|
|
|
4
4
|
altAxis?: boolean;
|
|
5
5
|
open?: boolean;
|
|
6
6
|
onClose?: (event: Event | import("react").SyntheticEvent) => void;
|
|
7
|
-
position?: import("
|
|
7
|
+
position?: import("../Panel/Panel.positionUtils").FloatingPlacement;
|
|
8
8
|
anchorRef: import("react").RefObject<Element | null>;
|
|
9
9
|
anchorWidth?: boolean;
|
|
10
10
|
considerHeight?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@transferwise/components",
|
|
3
|
-
"version": "0.0.0-experimental-
|
|
3
|
+
"version": "0.0.0-experimental-0657ea4",
|
|
4
4
|
"description": "Neptune React components",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"storybook": "^10.5.4",
|
|
92
92
|
"storybook-addon-tag-badges": "^3.1.0",
|
|
93
93
|
"storybook-addon-test-codegen": "^3.0.4",
|
|
94
|
-
"@transferwise/less-config": "
|
|
94
|
+
"@transferwise/less-config": "3.2.0",
|
|
95
95
|
"@wise/components-theming": "1.10.3",
|
|
96
96
|
"@wise/wds-configs": "0.0.0"
|
|
97
97
|
},
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { ChevronLeft } from '@transferwise/icons';
|
|
1
|
+
import { ChevronLeft, ChevronRight } from '@transferwise/icons';
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
3
|
import { motion } from 'motion/react';
|
|
4
4
|
|
|
5
|
+
import { useDirection } from '../../common/hooks';
|
|
6
|
+
|
|
5
7
|
interface Props {
|
|
6
8
|
shouldShow: boolean;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
export const Chevron = ({ shouldShow = true }: Props) => {
|
|
12
|
+
const { isRTL } = useDirection();
|
|
13
|
+
const Icon = isRTL ? ChevronRight : ChevronLeft;
|
|
14
|
+
|
|
10
15
|
return (
|
|
11
16
|
<div
|
|
12
17
|
className={clsx(
|
|
@@ -17,7 +22,7 @@ export const Chevron = ({ shouldShow = true }: Props) => {
|
|
|
17
22
|
>
|
|
18
23
|
<motion.div
|
|
19
24
|
animate={{
|
|
20
|
-
x: [12, 0, 0, -12],
|
|
25
|
+
x: isRTL ? [-12, 0, 0, 12] : [12, 0, 0, -12],
|
|
21
26
|
opacity: [0, 1, 1, 0],
|
|
22
27
|
}}
|
|
23
28
|
transition={{
|
|
@@ -28,7 +33,7 @@ export const Chevron = ({ shouldShow = true }: Props) => {
|
|
|
28
33
|
repeatType: 'loop',
|
|
29
34
|
}}
|
|
30
35
|
>
|
|
31
|
-
<
|
|
36
|
+
<Icon />
|
|
32
37
|
</motion.div>
|
|
33
38
|
</div>
|
|
34
39
|
);
|
|
@@ -182,24 +182,24 @@ export const ListItem = ({
|
|
|
182
182
|
const getFeatureClassName = () => {
|
|
183
183
|
const partials = [];
|
|
184
184
|
const hasMedia = Boolean(media);
|
|
185
|
-
const
|
|
185
|
+
const hasVisibleControl = Boolean(control) && !isNavigationIndicatorHidden;
|
|
186
186
|
const hasInfo = Boolean(additionalInfo);
|
|
187
187
|
const hasPrompt = Boolean(prompt) || (disabled && Boolean(disabledPromptMessage));
|
|
188
188
|
|
|
189
189
|
/* eslint-disable functional/immutable-data */
|
|
190
|
-
if (hasMedia &&
|
|
190
|
+
if (hasMedia && hasVisibleControl) {
|
|
191
191
|
partials.push('wds-list-item-hasMedia-hasControl');
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
if (hasMedia && !
|
|
194
|
+
if (hasMedia && !hasVisibleControl) {
|
|
195
195
|
partials.push('wds-list-item-hasMedia-noControl');
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
if (!hasMedia &&
|
|
198
|
+
if (!hasMedia && hasVisibleControl) {
|
|
199
199
|
partials.push('wds-list-item-noMedia-hasControl');
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
if (!hasMedia && !
|
|
202
|
+
if (!hasMedia && !hasVisibleControl) {
|
|
203
203
|
partials.push('wds-list-item-noMedia-noControl');
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -226,29 +226,6 @@ export const ImageAlignment: StoryObj<PreviewStoryArgs> = {
|
|
|
226
226
|
},
|
|
227
227
|
};
|
|
228
228
|
|
|
229
|
-
// Regression: when Navigation has hideIndicator + href, the control column should be
|
|
230
|
-
// removed from the grid so no phantom gap appears on the right side of the item.
|
|
231
|
-
export const NavigationHideIndicator: Story = {
|
|
232
|
-
render: () => (
|
|
233
|
-
<List>
|
|
234
|
-
<ListItem
|
|
235
|
-
title="Stocks"
|
|
236
|
-
valueTitle="− 6.70 GBP"
|
|
237
|
-
valueSubtitle="All time"
|
|
238
|
-
media={<ListItem.AvatarView>{null}</ListItem.AvatarView>}
|
|
239
|
-
control={<ListItem.Navigation href="/test" hideIndicator />}
|
|
240
|
-
/>
|
|
241
|
-
<ListItem
|
|
242
|
-
title="With chevron (baseline)"
|
|
243
|
-
valueTitle="100 GBP"
|
|
244
|
-
valueSubtitle="All time"
|
|
245
|
-
media={<ListItem.AvatarView>{null}</ListItem.AvatarView>}
|
|
246
|
-
control={<ListItem.Navigation href="/test" />}
|
|
247
|
-
/>
|
|
248
|
-
</List>
|
|
249
|
-
),
|
|
250
|
-
};
|
|
251
|
-
|
|
252
229
|
// Our container queries left 1px gap between breakpoint definitions.
|
|
253
230
|
// This test makes sure that LI placed in a parent of {width} still
|
|
254
231
|
// works fine, where: {breakpoint} < {width} < {breakpoint} + 1px
|
|
@@ -39,27 +39,35 @@
|
|
|
39
39
|
.np-panel[data-popper-placement^="top"] > .np-panel__content {
|
|
40
40
|
transform: translateY(10px) scale(0.95);
|
|
41
41
|
}
|
|
42
|
-
.np-panel[data-popper-placement^="top"]
|
|
42
|
+
.np-panel[data-popper-placement^="top"] .np-panel__arrow {
|
|
43
43
|
inset-block-end: -6px;
|
|
44
44
|
}
|
|
45
45
|
.np-panel[data-popper-placement^="right"] > .np-panel__content {
|
|
46
46
|
transform: translateX(-10px) scale(0.95);
|
|
47
47
|
}
|
|
48
|
-
.np-panel[data-popper-placement^="right"]
|
|
48
|
+
.np-panel[data-popper-placement^="right"] .np-panel__arrow {
|
|
49
49
|
inset-inline-start: -6px;
|
|
50
50
|
}
|
|
51
|
+
[dir="rtl"] .np-panel[data-popper-placement^="right"] .np-panel__arrow {
|
|
52
|
+
inset-inline-start: unset;
|
|
53
|
+
inset-inline-end: -6px;
|
|
54
|
+
}
|
|
51
55
|
.np-panel[data-popper-placement^="bottom"] > .np-panel__content {
|
|
52
56
|
transform: translateY(-10px) scale(0.95);
|
|
53
57
|
}
|
|
54
|
-
.np-panel[data-popper-placement^="bottom"]
|
|
58
|
+
.np-panel[data-popper-placement^="bottom"] .np-panel__arrow {
|
|
55
59
|
inset-block-start: -6px;
|
|
56
60
|
}
|
|
57
61
|
.np-panel[data-popper-placement^="left"] > .np-panel__content {
|
|
58
62
|
transform: translateX(10px) scale(0.95);
|
|
59
63
|
}
|
|
60
|
-
.np-panel[data-popper-placement^="left"]
|
|
64
|
+
.np-panel[data-popper-placement^="left"] .np-panel__arrow {
|
|
61
65
|
inset-inline-end: -6px;
|
|
62
66
|
}
|
|
67
|
+
[dir="rtl"] .np-panel[data-popper-placement^="left"] .np-panel__arrow {
|
|
68
|
+
inset-inline-end: unset;
|
|
69
|
+
inset-inline-start: -6px;
|
|
70
|
+
}
|
|
63
71
|
.np-theme-personal .np-panel__content {
|
|
64
72
|
border-radius: 16px;
|
|
65
73
|
border-radius: var(--radius-medium);
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
opacity 0.2s cubic-bezier(0.6, 0.2, 0.1, 1),
|
|
12
12
|
transform 0.2s cubic-bezier(0.6, 0.2, 0.1, 1),
|
|
13
13
|
visibility 0s linear 0.2s;
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
.np-theme-personal & {
|
|
16
16
|
border-radius: var(--radius-small);
|
|
17
17
|
}
|
|
@@ -45,35 +45,53 @@
|
|
|
45
45
|
background: var(--color-background-elevated);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
&[data-popper-placement^="top"]
|
|
49
|
-
|
|
48
|
+
&[data-popper-placement^="top"] {
|
|
49
|
+
> .np-panel__content {
|
|
50
|
+
transform: translateY(10px) scale(0.95);
|
|
51
|
+
}
|
|
50
52
|
|
|
51
53
|
.np-panel__arrow {
|
|
52
54
|
bottom: -6px;
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
&[data-popper-placement^="right"]
|
|
57
|
-
|
|
58
|
+
&[data-popper-placement^="right"] {
|
|
59
|
+
> .np-panel__content {
|
|
60
|
+
transform: translateX(-10px) scale(0.95);
|
|
61
|
+
}
|
|
58
62
|
|
|
59
63
|
.np-panel__arrow {
|
|
60
64
|
left: -6px;
|
|
65
|
+
|
|
66
|
+
[dir="rtl"] & {
|
|
67
|
+
left: unset;
|
|
68
|
+
right: -6px;
|
|
69
|
+
}
|
|
61
70
|
}
|
|
62
71
|
}
|
|
63
72
|
|
|
64
|
-
&[data-popper-placement^="bottom"]
|
|
65
|
-
|
|
73
|
+
&[data-popper-placement^="bottom"] {
|
|
74
|
+
> .np-panel__content {
|
|
75
|
+
transform: translateY(-10px) scale(0.95);
|
|
76
|
+
}
|
|
66
77
|
|
|
67
78
|
.np-panel__arrow {
|
|
68
79
|
top: -6px;
|
|
69
80
|
}
|
|
70
81
|
}
|
|
71
82
|
|
|
72
|
-
&[data-popper-placement^="left"]
|
|
73
|
-
|
|
83
|
+
&[data-popper-placement^="left"] {
|
|
84
|
+
> .np-panel__content {
|
|
85
|
+
transform: translateX(10px) scale(0.95);
|
|
86
|
+
}
|
|
74
87
|
|
|
75
88
|
.np-panel__arrow {
|
|
76
89
|
right: -6px;
|
|
90
|
+
|
|
91
|
+
[dir="rtl"] & {
|
|
92
|
+
right: unset;
|
|
93
|
+
left: -6px;
|
|
94
|
+
}
|
|
77
95
|
}
|
|
78
96
|
}
|
|
79
97
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Placement } from '@floating-ui/react';
|
|
2
|
+
|
|
3
|
+
export type FloatingPlacement =
|
|
4
|
+
| 'top'
|
|
5
|
+
| 'right'
|
|
6
|
+
| 'bottom'
|
|
7
|
+
| 'left'
|
|
8
|
+
| 'top-left'
|
|
9
|
+
| 'top-right'
|
|
10
|
+
| 'right-top'
|
|
11
|
+
| 'right-bottom'
|
|
12
|
+
| 'bottom-left'
|
|
13
|
+
| 'bottom-right'
|
|
14
|
+
| 'left-top'
|
|
15
|
+
| 'left-bottom';
|
|
16
|
+
|
|
17
|
+
export const POPOVER_OFFSET = 16;
|
|
18
|
+
|
|
19
|
+
// By default the flip positioning explores only the opposite alternative. So if left is passed and there's no enough space
|
|
20
|
+
// the right one gets chosen. If there's no space on both sides popover goes back to the initially chosen one left.
|
|
21
|
+
// This mapping forces popover to try the four available positions before going back to the initial chosen one.
|
|
22
|
+
export const fallbackPlacements: Record<Placement, Placement[]> = {
|
|
23
|
+
top: ['bottom', 'right', 'left'],
|
|
24
|
+
bottom: ['top', 'right', 'left'],
|
|
25
|
+
left: ['right', 'top', 'bottom'],
|
|
26
|
+
right: ['left', 'top', 'bottom'],
|
|
27
|
+
'top-start': ['bottom-start', 'top-end', 'bottom-end'],
|
|
28
|
+
'top-end': ['bottom-end', 'top-start', 'bottom-start'],
|
|
29
|
+
'bottom-start': ['top-start', 'bottom-end', 'top-end'],
|
|
30
|
+
'bottom-end': ['top-end', 'bottom-start', 'top-start'],
|
|
31
|
+
'left-start': ['right-start', 'left-end', 'right-end'],
|
|
32
|
+
'left-end': ['right-end', 'left-start', 'right-start'],
|
|
33
|
+
'right-start': ['left-start', 'right-end', 'left-end'],
|
|
34
|
+
'right-end': ['left-end', 'right-start', 'left-start'],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const positionToPlacement = (position: FloatingPlacement): Placement => {
|
|
38
|
+
switch (position) {
|
|
39
|
+
case 'top-left':
|
|
40
|
+
return 'top-start';
|
|
41
|
+
case 'top-right':
|
|
42
|
+
return 'top-end';
|
|
43
|
+
case 'bottom-left':
|
|
44
|
+
return 'bottom-start';
|
|
45
|
+
case 'bottom-right':
|
|
46
|
+
return 'bottom-end';
|
|
47
|
+
case 'left-top':
|
|
48
|
+
return 'left-start';
|
|
49
|
+
case 'left-bottom':
|
|
50
|
+
return 'left-end';
|
|
51
|
+
case 'right-top':
|
|
52
|
+
return 'right-start';
|
|
53
|
+
case 'right-bottom':
|
|
54
|
+
return 'right-end';
|
|
55
|
+
default:
|
|
56
|
+
return position;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const mirrorPositionForRtl = (position: FloatingPlacement): FloatingPlacement => {
|
|
61
|
+
switch (position) {
|
|
62
|
+
case 'left':
|
|
63
|
+
return 'right';
|
|
64
|
+
case 'right':
|
|
65
|
+
return 'left';
|
|
66
|
+
case 'left-top':
|
|
67
|
+
return 'right-top';
|
|
68
|
+
case 'left-bottom':
|
|
69
|
+
return 'right-bottom';
|
|
70
|
+
case 'right-top':
|
|
71
|
+
return 'left-top';
|
|
72
|
+
case 'right-bottom':
|
|
73
|
+
return 'left-bottom';
|
|
74
|
+
default:
|
|
75
|
+
// top-left/top-right/bottom-left/bottom-right map to start/end via
|
|
76
|
+
// positionToPlacement, and floating-ui already flips start/end in RTL.
|
|
77
|
+
return position;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createRef } from 'react';
|
|
2
2
|
|
|
3
3
|
import { Position } from '..';
|
|
4
|
+
import { DirectionProvider } from '../../Provider/Direction/DirectionProvider';
|
|
4
5
|
import { render } from '../../test-utils';
|
|
5
6
|
|
|
6
7
|
import Panel from './Panel';
|
|
@@ -93,4 +94,43 @@ describe('Panel', () => {
|
|
|
93
94
|
expect(getPanel()).toHaveAttribute('data-popper-placement', expectedPlacement);
|
|
94
95
|
});
|
|
95
96
|
});
|
|
97
|
+
|
|
98
|
+
describe('RTL position mirroring', () => {
|
|
99
|
+
it.each<[PanelProps['position'], string]>([
|
|
100
|
+
[Position.LEFT, 'right'],
|
|
101
|
+
[Position.RIGHT, 'left'],
|
|
102
|
+
[Position.LEFT_TOP, 'right-start'],
|
|
103
|
+
[Position.LEFT_BOTTOM, 'right-end'],
|
|
104
|
+
[Position.RIGHT_TOP, 'left-start'],
|
|
105
|
+
[Position.RIGHT_BOTTOM, 'left-end'],
|
|
106
|
+
])('mirrors %s in RTL to floating-ui placement %s', (position, expectedPlacement) => {
|
|
107
|
+
render(
|
|
108
|
+
<DirectionProvider direction="rtl">
|
|
109
|
+
<Panel {...props} position={position} />
|
|
110
|
+
</DirectionProvider>,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(getPanel()).toHaveAttribute('data-popper-placement', expectedPlacement);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it.each<[PanelProps['position'], string]>([
|
|
117
|
+
[Position.TOP, 'top'],
|
|
118
|
+
[Position.BOTTOM, 'bottom'],
|
|
119
|
+
[Position.TOP_LEFT, 'top-start'],
|
|
120
|
+
[Position.TOP_RIGHT, 'top-end'],
|
|
121
|
+
[Position.BOTTOM_LEFT, 'bottom-start'],
|
|
122
|
+
[Position.BOTTOM_RIGHT, 'bottom-end'],
|
|
123
|
+
])(
|
|
124
|
+
'does not mirror %s in RTL (floating-ui handles start/end flip)',
|
|
125
|
+
(position, expectedPlacement) => {
|
|
126
|
+
render(
|
|
127
|
+
<DirectionProvider direction="rtl">
|
|
128
|
+
<Panel {...props} position={position} />
|
|
129
|
+
</DirectionProvider>,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(getPanel()).toHaveAttribute('data-popper-placement', expectedPlacement);
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
});
|
|
96
136
|
});
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
autoUpdate,
|
|
4
4
|
flip as flipMiddleware,
|
|
5
5
|
offset,
|
|
6
|
-
type Placement,
|
|
7
6
|
shift,
|
|
8
7
|
size as sizeMiddleware,
|
|
9
8
|
useFloating,
|
|
@@ -20,29 +19,17 @@ import {
|
|
|
20
19
|
useRef,
|
|
21
20
|
} from 'react';
|
|
22
21
|
|
|
23
|
-
import { Position, type FloatingPlacement, positionToPlacement } from '..';
|
|
24
|
-
import Dimmer from '../../Dimmer';
|
|
25
22
|
import { OverlayIdContext } from '../../Provider/overlay/OverlayIdProvider';
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
[Position.RIGHT]: [Position.LEFT, Position.TOP, Position.BOTTOM],
|
|
37
|
-
'top-start': ['bottom-start', 'top-end', 'bottom-end'],
|
|
38
|
-
'top-end': ['bottom-end', 'top-start', 'bottom-start'],
|
|
39
|
-
'bottom-start': ['top-start', 'bottom-end', 'top-end'],
|
|
40
|
-
'bottom-end': ['top-end', 'bottom-start', 'top-start'],
|
|
41
|
-
'left-start': ['right-start', 'left-end', 'right-end'],
|
|
42
|
-
'left-end': ['right-end', 'left-start', 'right-start'],
|
|
43
|
-
'right-start': ['left-start', 'right-end', 'left-end'],
|
|
44
|
-
'right-end': ['left-end', 'right-start', 'left-start'],
|
|
45
|
-
};
|
|
23
|
+
import Dimmer from '../../Dimmer';
|
|
24
|
+
import useDirection from '../hooks/useDirection';
|
|
25
|
+
import { Position } from '..';
|
|
26
|
+
import {
|
|
27
|
+
type FloatingPlacement,
|
|
28
|
+
POPOVER_OFFSET,
|
|
29
|
+
fallbackPlacements,
|
|
30
|
+
mirrorPositionForRtl,
|
|
31
|
+
positionToPlacement,
|
|
32
|
+
} from './Panel.positionUtils';
|
|
46
33
|
|
|
47
34
|
export type PanelProps = PropsWithChildren<{
|
|
48
35
|
arrow?: boolean;
|
|
@@ -74,7 +61,9 @@ const Panel = forwardRef<HTMLDivElement, PanelProps>(function Panel(
|
|
|
74
61
|
reference,
|
|
75
62
|
) {
|
|
76
63
|
const arrowRef = useRef<HTMLDivElement | null>(null);
|
|
77
|
-
const
|
|
64
|
+
const { isRTL } = useDirection();
|
|
65
|
+
const resolvedPosition = isRTL ? mirrorPositionForRtl(position) : position;
|
|
66
|
+
const floatingPlacement = positionToPlacement(resolvedPosition);
|
|
78
67
|
|
|
79
68
|
const middleware = [];
|
|
80
69
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
import { useRef } from 'react';
|
|
3
|
+
import { withRTLDiff } from '../../../../.storybook/decorators/RtlDiffDecorator';
|
|
4
|
+
import Button from '../../../Button';
|
|
5
|
+
import { Position } from '../../PropsValues/position';
|
|
6
|
+
import Panel from '../Panel';
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
component: Panel,
|
|
10
|
+
title: 'Dialogs/Panel/Tests/RTL Diff',
|
|
11
|
+
tags: ['!autodocs', '!manifest'],
|
|
12
|
+
} satisfies Meta<typeof Panel>;
|
|
13
|
+
|
|
14
|
+
type Story = StoryObj<typeof Panel>;
|
|
15
|
+
|
|
16
|
+
function PanelStory({
|
|
17
|
+
position,
|
|
18
|
+
label,
|
|
19
|
+
}: {
|
|
20
|
+
position: Parameters<typeof Panel>[0]['position'];
|
|
21
|
+
label: string;
|
|
22
|
+
}) {
|
|
23
|
+
const anchorRef = useRef<HTMLButtonElement>(null);
|
|
24
|
+
const margin =
|
|
25
|
+
position?.startsWith('bottom') || position?.startsWith('top') ? '3.5rem 0' : '2.5rem 0';
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<Button ref={anchorRef} style={{ margin }}>
|
|
30
|
+
{label}
|
|
31
|
+
</Button>
|
|
32
|
+
<Panel
|
|
33
|
+
open
|
|
34
|
+
position={position}
|
|
35
|
+
anchorRef={anchorRef}
|
|
36
|
+
arrow
|
|
37
|
+
aria-label={`Panel positioned ${label}`}
|
|
38
|
+
>
|
|
39
|
+
<div style={{ padding: '16px', maxWidth: '200px' }}>
|
|
40
|
+
Panel content positioned at <strong>{label}</strong>
|
|
41
|
+
</div>
|
|
42
|
+
</Panel>
|
|
43
|
+
</>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const Top: Story = {
|
|
48
|
+
render: () => (
|
|
49
|
+
<>
|
|
50
|
+
<PanelStory position={Position.TOP_LEFT} label="top-left" />
|
|
51
|
+
<PanelStory position={Position.TOP} label="top" />
|
|
52
|
+
<PanelStory position={Position.TOP_RIGHT} label="top-right" />
|
|
53
|
+
</>
|
|
54
|
+
),
|
|
55
|
+
...withRTLDiff({ renderAs: 'iframe', hAlign: 'center', vAlign: 'bottom' }),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const Right: Story = {
|
|
59
|
+
render: () => (
|
|
60
|
+
<>
|
|
61
|
+
<PanelStory position={Position.RIGHT_TOP} label="right-top" />
|
|
62
|
+
<PanelStory position={Position.RIGHT} label="right" />
|
|
63
|
+
<PanelStory position={Position.RIGHT_BOTTOM} label="right-bottom" />
|
|
64
|
+
</>
|
|
65
|
+
),
|
|
66
|
+
...withRTLDiff({ renderAs: 'iframe', hAlign: 'left', vAlign: 'center' }),
|
|
67
|
+
};
|
|
68
|
+
export const RightFlipped: Story = {
|
|
69
|
+
render: () => (
|
|
70
|
+
<>
|
|
71
|
+
<PanelStory position={Position.RIGHT_TOP} label="right-top" />
|
|
72
|
+
<PanelStory position={Position.RIGHT} label="right" />
|
|
73
|
+
<PanelStory position={Position.RIGHT_BOTTOM} label="right-bottom" />
|
|
74
|
+
</>
|
|
75
|
+
),
|
|
76
|
+
...withRTLDiff({ renderAs: 'iframe', hAlign: 'right', vAlign: 'center' }),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const Bottom: Story = {
|
|
80
|
+
render: () => (
|
|
81
|
+
<>
|
|
82
|
+
<PanelStory position={Position.BOTTOM_LEFT} label="bottom-left" />
|
|
83
|
+
<PanelStory position={Position.BOTTOM} label="bottom" />
|
|
84
|
+
<PanelStory position={Position.BOTTOM_RIGHT} label="bottom-right" />
|
|
85
|
+
</>
|
|
86
|
+
),
|
|
87
|
+
...withRTLDiff({ renderAs: 'iframe', hAlign: 'center' }),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const Left: Story = {
|
|
91
|
+
render: () => (
|
|
92
|
+
<>
|
|
93
|
+
<PanelStory position={Position.LEFT_TOP} label="left-top" />
|
|
94
|
+
<PanelStory position={Position.LEFT} label="left" />
|
|
95
|
+
<PanelStory position={Position.LEFT_BOTTOM} label="left-bottom" />
|
|
96
|
+
</>
|
|
97
|
+
),
|
|
98
|
+
...withRTLDiff({ renderAs: 'iframe', hAlign: 'right', vAlign: 'center' }),
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const LeftFlipped: Story = {
|
|
102
|
+
render: () => (
|
|
103
|
+
<>
|
|
104
|
+
<PanelStory position={Position.LEFT_TOP} label="left-top" />
|
|
105
|
+
<PanelStory position={Position.LEFT} label="left" />
|
|
106
|
+
<PanelStory position={Position.LEFT_BOTTOM} label="left-bottom" />
|
|
107
|
+
</>
|
|
108
|
+
),
|
|
109
|
+
...withRTLDiff({ renderAs: 'iframe', hAlign: 'left', vAlign: 'center' }),
|
|
110
|
+
};
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { Placement } from '@floating-ui/react';
|
|
2
|
-
|
|
3
1
|
export enum Position {
|
|
4
2
|
BOTTOM = 'bottom',
|
|
5
3
|
BOTTOM_LEFT = 'bottom-left',
|
|
@@ -21,51 +19,3 @@ export type PositionTop = 'top';
|
|
|
21
19
|
export type PositionRight = 'right';
|
|
22
20
|
export type PositionBottom = 'bottom';
|
|
23
21
|
export type PositionLeft = 'left';
|
|
24
|
-
|
|
25
|
-
export type PositionTopLeft = 'top-left';
|
|
26
|
-
export type PositionTopRight = 'top-right';
|
|
27
|
-
export type PositionBottomLeft = 'bottom-left';
|
|
28
|
-
export type PositionBottomRight = 'bottom-right';
|
|
29
|
-
export type PositionLeftTop = 'left-top';
|
|
30
|
-
export type PositionLeftBottom = 'left-bottom';
|
|
31
|
-
export type PositionRightTop = 'right-top';
|
|
32
|
-
export type PositionRightBottom = 'right-bottom';
|
|
33
|
-
|
|
34
|
-
/** Neptune position values that can be converted to floating-ui Placement */
|
|
35
|
-
export type FloatingPlacement =
|
|
36
|
-
| PositionTop
|
|
37
|
-
| PositionRight
|
|
38
|
-
| PositionBottom
|
|
39
|
-
| PositionLeft
|
|
40
|
-
| PositionTopLeft
|
|
41
|
-
| PositionTopRight
|
|
42
|
-
| PositionRightTop
|
|
43
|
-
| PositionRightBottom
|
|
44
|
-
| PositionBottomLeft
|
|
45
|
-
| PositionBottomRight
|
|
46
|
-
| PositionLeftTop
|
|
47
|
-
| PositionLeftBottom;
|
|
48
|
-
|
|
49
|
-
/** Maps Neptune position values to floating-ui Placement */
|
|
50
|
-
export const positionToPlacement = (position: FloatingPlacement): Placement => {
|
|
51
|
-
switch (position) {
|
|
52
|
-
case 'top-left':
|
|
53
|
-
return 'top-start';
|
|
54
|
-
case 'top-right':
|
|
55
|
-
return 'top-end';
|
|
56
|
-
case 'bottom-left':
|
|
57
|
-
return 'bottom-start';
|
|
58
|
-
case 'bottom-right':
|
|
59
|
-
return 'bottom-end';
|
|
60
|
-
case 'left-top':
|
|
61
|
-
return 'left-start';
|
|
62
|
-
case 'left-bottom':
|
|
63
|
-
return 'left-end';
|
|
64
|
-
case 'right-top':
|
|
65
|
-
return 'right-start';
|
|
66
|
-
case 'right-bottom':
|
|
67
|
-
return 'right-end';
|
|
68
|
-
default:
|
|
69
|
-
return position;
|
|
70
|
-
}
|
|
71
|
-
};
|
package/src/main.css
CHANGED
|
@@ -21875,27 +21875,35 @@ button.np-option {
|
|
|
21875
21875
|
.np-panel[data-popper-placement^="top"] > .np-panel__content {
|
|
21876
21876
|
transform: translateY(10px) scale(0.95);
|
|
21877
21877
|
}
|
|
21878
|
-
.np-panel[data-popper-placement^="top"]
|
|
21878
|
+
.np-panel[data-popper-placement^="top"] .np-panel__arrow {
|
|
21879
21879
|
inset-block-end: -6px;
|
|
21880
21880
|
}
|
|
21881
21881
|
.np-panel[data-popper-placement^="right"] > .np-panel__content {
|
|
21882
21882
|
transform: translateX(-10px) scale(0.95);
|
|
21883
21883
|
}
|
|
21884
|
-
.np-panel[data-popper-placement^="right"]
|
|
21884
|
+
.np-panel[data-popper-placement^="right"] .np-panel__arrow {
|
|
21885
21885
|
inset-inline-start: -6px;
|
|
21886
21886
|
}
|
|
21887
|
+
[dir="rtl"] .np-panel[data-popper-placement^="right"] .np-panel__arrow {
|
|
21888
|
+
inset-inline-start: unset;
|
|
21889
|
+
inset-inline-end: -6px;
|
|
21890
|
+
}
|
|
21887
21891
|
.np-panel[data-popper-placement^="bottom"] > .np-panel__content {
|
|
21888
21892
|
transform: translateY(-10px) scale(0.95);
|
|
21889
21893
|
}
|
|
21890
|
-
.np-panel[data-popper-placement^="bottom"]
|
|
21894
|
+
.np-panel[data-popper-placement^="bottom"] .np-panel__arrow {
|
|
21891
21895
|
inset-block-start: -6px;
|
|
21892
21896
|
}
|
|
21893
21897
|
.np-panel[data-popper-placement^="left"] > .np-panel__content {
|
|
21894
21898
|
transform: translateX(10px) scale(0.95);
|
|
21895
21899
|
}
|
|
21896
|
-
.np-panel[data-popper-placement^="left"]
|
|
21900
|
+
.np-panel[data-popper-placement^="left"] .np-panel__arrow {
|
|
21897
21901
|
inset-inline-end: -6px;
|
|
21898
21902
|
}
|
|
21903
|
+
[dir="rtl"] .np-panel[data-popper-placement^="left"] .np-panel__arrow {
|
|
21904
|
+
inset-inline-end: unset;
|
|
21905
|
+
inset-inline-start: -6px;
|
|
21906
|
+
}
|
|
21899
21907
|
.np-theme-personal .np-panel__content {
|
|
21900
21908
|
border-radius: 16px;
|
|
21901
21909
|
border-radius: var(--radius-medium);
|