@bacon-pos/ui 0.7.0 → 0.7.2
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/lib/commonjs/components/Icon/icons.generated.js +3 -0
- package/lib/commonjs/components/Icon/icons.generated.js.map +1 -1
- package/lib/commonjs/components/Menu/Menu.js +25 -52
- package/lib/commonjs/components/Menu/Menu.js.map +1 -1
- package/lib/commonjs/components/Menu/MenuButton.js +4 -0
- package/lib/commonjs/components/Menu/MenuButton.js.map +1 -1
- package/lib/commonjs/components/Menu/SelectButton.js +4 -0
- package/lib/commonjs/components/Menu/SelectButton.js.map +1 -1
- package/lib/commonjs/components/Menu/UserMenu.js +4 -0
- package/lib/commonjs/components/Menu/UserMenu.js.map +1 -1
- package/lib/commonjs/components/Menu/placement.js +84 -0
- package/lib/commonjs/components/Menu/placement.js.map +1 -0
- package/lib/commonjs/components/Menu/placement.test.js +186 -0
- package/lib/commonjs/components/Menu/placement.test.js.map +1 -0
- package/lib/commonjs/components/Select/Select.js +2 -0
- package/lib/commonjs/components/Select/Select.js.map +1 -1
- package/lib/module/components/Icon/icons.generated.js +3 -0
- package/lib/module/components/Icon/icons.generated.js.map +1 -1
- package/lib/module/components/Menu/Menu.js +27 -54
- package/lib/module/components/Menu/Menu.js.map +1 -1
- package/lib/module/components/Menu/MenuButton.js +4 -0
- package/lib/module/components/Menu/MenuButton.js.map +1 -1
- package/lib/module/components/Menu/SelectButton.js +4 -0
- package/lib/module/components/Menu/SelectButton.js.map +1 -1
- package/lib/module/components/Menu/UserMenu.js +4 -0
- package/lib/module/components/Menu/UserMenu.js.map +1 -1
- package/lib/module/components/Menu/placement.js +80 -0
- package/lib/module/components/Menu/placement.js.map +1 -0
- package/lib/module/components/Menu/placement.test.js +186 -0
- package/lib/module/components/Menu/placement.test.js.map +1 -0
- package/lib/module/components/Select/Select.js +2 -0
- package/lib/module/components/Select/Select.js.map +1 -1
- package/lib/typescript/commonjs/components/Icon/icons.generated.d.ts +3 -0
- package/lib/typescript/commonjs/components/Icon/icons.generated.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/Menu/Menu.d.ts +7 -1
- package/lib/typescript/commonjs/components/Menu/Menu.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/Menu/MenuButton.d.ts +6 -2
- package/lib/typescript/commonjs/components/Menu/MenuButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/Menu/SelectButton.d.ts +6 -2
- package/lib/typescript/commonjs/components/Menu/SelectButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/Menu/UserMenu.d.ts +6 -2
- package/lib/typescript/commonjs/components/Menu/UserMenu.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/Menu/placement.d.ts +47 -0
- package/lib/typescript/commonjs/components/Menu/placement.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/Menu/placement.test.d.ts +2 -0
- package/lib/typescript/commonjs/components/Menu/placement.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/Select/Select.d.ts +3 -1
- package/lib/typescript/commonjs/components/Select/Select.d.ts.map +1 -1
- package/lib/typescript/module/components/Icon/icons.generated.d.ts +3 -0
- package/lib/typescript/module/components/Icon/icons.generated.d.ts.map +1 -1
- package/lib/typescript/module/components/Menu/Menu.d.ts +7 -1
- package/lib/typescript/module/components/Menu/Menu.d.ts.map +1 -1
- package/lib/typescript/module/components/Menu/MenuButton.d.ts +6 -2
- package/lib/typescript/module/components/Menu/MenuButton.d.ts.map +1 -1
- package/lib/typescript/module/components/Menu/SelectButton.d.ts +6 -2
- package/lib/typescript/module/components/Menu/SelectButton.d.ts.map +1 -1
- package/lib/typescript/module/components/Menu/UserMenu.d.ts +6 -2
- package/lib/typescript/module/components/Menu/UserMenu.d.ts.map +1 -1
- package/lib/typescript/module/components/Menu/placement.d.ts +47 -0
- package/lib/typescript/module/components/Menu/placement.d.ts.map +1 -0
- package/lib/typescript/module/components/Menu/placement.test.d.ts +2 -0
- package/lib/typescript/module/components/Menu/placement.test.d.ts.map +1 -0
- package/lib/typescript/module/components/Select/Select.d.ts +3 -1
- package/lib/typescript/module/components/Select/Select.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Icon/icons.generated.ts +3 -0
- package/src/components/Menu/Menu.tsx +37 -46
- package/src/components/Menu/MenuButton.tsx +9 -1
- package/src/components/Menu/SelectButton.tsx +9 -1
- package/src/components/Menu/UserMenu.tsx +9 -1
- package/src/components/Menu/placement.ts +107 -0
- package/src/components/Select/Select.tsx +4 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Dropdown, type DropdownEntry } from '../Dropdown/Dropdown';
|
|
2
2
|
import { IconButton, type IconButtonShape, type IconButtonSize, type IconButtonVariant } from '../IconButton/IconButton';
|
|
3
3
|
import type { IconName } from '../Icon/icons.generated';
|
|
4
|
-
import { Menu, type MenuOrigin } from './Menu';
|
|
4
|
+
import { Menu, type EdgeInsets, type MenuOrigin } from './Menu';
|
|
5
5
|
|
|
6
6
|
export interface MenuButtonProps {
|
|
7
7
|
/** The menu rows (see `Dropdown`). */
|
|
@@ -26,6 +26,10 @@ export interface MenuButtonProps {
|
|
|
26
26
|
menuOrigin?: MenuOrigin;
|
|
27
27
|
/** Gap between trigger and menu. Defaults to 8. */
|
|
28
28
|
offset?: number;
|
|
29
|
+
/** Safe-area insets to keep clear. */
|
|
30
|
+
insets?: EdgeInsets;
|
|
31
|
+
/** Status-bar coordinate offset for the menu (see `Menu`). Pass 0 when immersive. */
|
|
32
|
+
statusBarHeight?: number;
|
|
29
33
|
/** Menu width. Defaults to the `Dropdown` default (350). */
|
|
30
34
|
menuWidth?: number;
|
|
31
35
|
/** Draw dividers between rows. Defaults to the `Dropdown` default (true). */
|
|
@@ -56,6 +60,8 @@ export function MenuButton({
|
|
|
56
60
|
anchorOrigin = { vertical: 'bottom', horizontal: 'right' },
|
|
57
61
|
menuOrigin = { vertical: 'top', horizontal: 'right' },
|
|
58
62
|
offset = 8,
|
|
63
|
+
insets,
|
|
64
|
+
statusBarHeight,
|
|
59
65
|
menuWidth,
|
|
60
66
|
showDividers,
|
|
61
67
|
closeOnSelect = true,
|
|
@@ -67,6 +73,8 @@ export function MenuButton({
|
|
|
67
73
|
anchorOrigin={anchorOrigin}
|
|
68
74
|
menuOrigin={menuOrigin}
|
|
69
75
|
offset={offset}
|
|
76
|
+
insets={insets}
|
|
77
|
+
statusBarHeight={statusBarHeight}
|
|
70
78
|
open={open}
|
|
71
79
|
onOpenChange={onOpenChange}
|
|
72
80
|
renderTrigger={({ toggle }) => (
|
|
@@ -4,7 +4,7 @@ import { Button, type ButtonSize, type ButtonVariant } from '../Button/Button';
|
|
|
4
4
|
import { Dropdown, type DropdownEntry } from '../Dropdown/Dropdown';
|
|
5
5
|
import { Icon } from '../Icon/Icon';
|
|
6
6
|
import type { IconName } from '../Icon/icons.generated';
|
|
7
|
-
import { Menu, type MenuOrigin } from './Menu';
|
|
7
|
+
import { Menu, type EdgeInsets, type MenuOrigin } from './Menu';
|
|
8
8
|
|
|
9
9
|
/** A selectable option. Its `icon` shows both in the row and on the trigger when chosen. */
|
|
10
10
|
export interface SelectButtonOption {
|
|
@@ -45,6 +45,10 @@ export interface SelectButtonProps {
|
|
|
45
45
|
menuOrigin?: MenuOrigin;
|
|
46
46
|
/** Gap between trigger and menu. Defaults to 8. */
|
|
47
47
|
offset?: number;
|
|
48
|
+
/** Safe-area insets to keep clear. */
|
|
49
|
+
insets?: EdgeInsets;
|
|
50
|
+
/** Status-bar coordinate offset for the menu (see `Menu`). Pass 0 when immersive. */
|
|
51
|
+
statusBarHeight?: number;
|
|
48
52
|
/** Menu width. Defaults to the `Dropdown` default (350). */
|
|
49
53
|
menuWidth?: number;
|
|
50
54
|
/** Draw dividers between rows. Defaults to the `Dropdown` default (true). */
|
|
@@ -98,6 +102,8 @@ export function SelectButton({
|
|
|
98
102
|
anchorOrigin = { vertical: 'bottom', horizontal: 'left' },
|
|
99
103
|
menuOrigin = { vertical: 'top', horizontal: 'left' },
|
|
100
104
|
offset = 8,
|
|
105
|
+
insets,
|
|
106
|
+
statusBarHeight,
|
|
101
107
|
menuWidth,
|
|
102
108
|
showDividers,
|
|
103
109
|
closeOnSelect = true,
|
|
@@ -134,6 +140,8 @@ export function SelectButton({
|
|
|
134
140
|
anchorOrigin={anchorOrigin}
|
|
135
141
|
menuOrigin={menuOrigin}
|
|
136
142
|
offset={offset}
|
|
143
|
+
insets={insets}
|
|
144
|
+
statusBarHeight={statusBarHeight}
|
|
137
145
|
open={open}
|
|
138
146
|
onOpenChange={onOpenChange}
|
|
139
147
|
triggerStyle={fullWidth ? { alignSelf: 'stretch' } : undefined}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ImageSourcePropType } from 'react-native';
|
|
2
2
|
import { Dropdown, type DropdownEntry } from '../Dropdown/Dropdown';
|
|
3
3
|
import { UserChip, type UserChipSize } from '../UserChip/UserChip';
|
|
4
|
-
import { Menu, type MenuOrigin } from './Menu';
|
|
4
|
+
import { Menu, type EdgeInsets, type MenuOrigin } from './Menu';
|
|
5
5
|
|
|
6
6
|
export interface UserMenuProps {
|
|
7
7
|
/** The menu rows (see `Dropdown`). */
|
|
@@ -28,6 +28,10 @@ export interface UserMenuProps {
|
|
|
28
28
|
menuOrigin?: MenuOrigin;
|
|
29
29
|
/** Gap between trigger and menu. Defaults to 8. */
|
|
30
30
|
offset?: number;
|
|
31
|
+
/** Safe-area insets to keep clear. */
|
|
32
|
+
insets?: EdgeInsets;
|
|
33
|
+
/** Status-bar coordinate offset for the menu (see `Menu`). Pass 0 when immersive. */
|
|
34
|
+
statusBarHeight?: number;
|
|
31
35
|
/** Menu width. Defaults to the `Dropdown` default (350). */
|
|
32
36
|
menuWidth?: number;
|
|
33
37
|
/** Draw dividers between rows. Defaults to the `Dropdown` default (true). */
|
|
@@ -58,6 +62,8 @@ export function UserMenu({
|
|
|
58
62
|
anchorOrigin = { vertical: 'bottom', horizontal: 'left' },
|
|
59
63
|
menuOrigin = { vertical: 'top', horizontal: 'left' },
|
|
60
64
|
offset = 8,
|
|
65
|
+
insets,
|
|
66
|
+
statusBarHeight,
|
|
61
67
|
menuWidth,
|
|
62
68
|
showDividers,
|
|
63
69
|
closeOnSelect = true,
|
|
@@ -69,6 +75,8 @@ export function UserMenu({
|
|
|
69
75
|
anchorOrigin={anchorOrigin}
|
|
70
76
|
menuOrigin={menuOrigin}
|
|
71
77
|
offset={offset}
|
|
78
|
+
insets={insets}
|
|
79
|
+
statusBarHeight={statusBarHeight}
|
|
72
80
|
open={open}
|
|
73
81
|
onOpenChange={onOpenChange}
|
|
74
82
|
renderTrigger={({ toggle }) => (
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { EdgeInsets, MenuOrigin } from './Menu';
|
|
2
|
+
|
|
3
|
+
const V_FRAC = { top: 0, center: 0.5, bottom: 1 } as const;
|
|
4
|
+
const H_FRAC = { left: 0, center: 0.5, right: 1 } as const;
|
|
5
|
+
|
|
6
|
+
export interface PlacementInput {
|
|
7
|
+
/** Trigger rect in window coordinates (as returned by `measureInWindow`). */
|
|
8
|
+
rect: { x: number; y: number; width: number; height: number };
|
|
9
|
+
/** Measured menu size (0 before the first layout). */
|
|
10
|
+
menuWidth: number;
|
|
11
|
+
menuHeight: number;
|
|
12
|
+
/** The content's natural (uncapped) height — drives the flip / cap decisions. */
|
|
13
|
+
naturalHeight: number;
|
|
14
|
+
anchorOrigin: MenuOrigin;
|
|
15
|
+
menuOrigin: MenuOrigin;
|
|
16
|
+
offset: number;
|
|
17
|
+
screenPadding: number;
|
|
18
|
+
insets: EdgeInsets;
|
|
19
|
+
/** Window size (`Dimensions.get('window')`). */
|
|
20
|
+
screenWidth: number;
|
|
21
|
+
screenHeight: number;
|
|
22
|
+
/**
|
|
23
|
+
* Difference between the window's coordinate origin (where `measureInWindow`
|
|
24
|
+
* reports) and the full-screen Modal's origin. On Android with a translucent
|
|
25
|
+
* status bar this is the status bar height; 0 on iOS / web / immersive.
|
|
26
|
+
*/
|
|
27
|
+
statusBarHeight: number;
|
|
28
|
+
autoFit: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Placement {
|
|
32
|
+
top: number;
|
|
33
|
+
left: number;
|
|
34
|
+
/** Auto-fit cap for the content so it scrolls instead of overflowing. */
|
|
35
|
+
maxHeight?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function clamp(value: number, min: number, max: number): number {
|
|
39
|
+
if (max < min) return min;
|
|
40
|
+
return Math.min(Math.max(value, min), max);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Pure placement math for `Menu`. Kept side-effect-free so it can be unit-tested
|
|
45
|
+
* exhaustively (status-bar offset, flip, cap, clamp) independent of RN layout.
|
|
46
|
+
*
|
|
47
|
+
* The key correctness point: the trigger is measured in *window* coordinates but
|
|
48
|
+
* the menu is painted in a full-screen (`statusBarTranslucent`) Modal, so we add
|
|
49
|
+
* `statusBarHeight` when converting the trigger position into the Modal's space.
|
|
50
|
+
* Available-space math cancels the offset out, so it uses window coordinates.
|
|
51
|
+
*/
|
|
52
|
+
export function computeMenuPlacement(input: PlacementInput): Placement {
|
|
53
|
+
const {
|
|
54
|
+
rect,
|
|
55
|
+
menuWidth,
|
|
56
|
+
menuHeight,
|
|
57
|
+
naturalHeight,
|
|
58
|
+
anchorOrigin,
|
|
59
|
+
menuOrigin,
|
|
60
|
+
offset,
|
|
61
|
+
screenPadding,
|
|
62
|
+
insets,
|
|
63
|
+
screenWidth,
|
|
64
|
+
screenHeight,
|
|
65
|
+
statusBarHeight,
|
|
66
|
+
autoFit,
|
|
67
|
+
} = input;
|
|
68
|
+
|
|
69
|
+
const isDropConfig =
|
|
70
|
+
(anchorOrigin.vertical === 'bottom' && menuOrigin.vertical === 'top') ||
|
|
71
|
+
(anchorOrigin.vertical === 'top' && menuOrigin.vertical === 'bottom');
|
|
72
|
+
const preferredUp = anchorOrigin.vertical === 'top' && menuOrigin.vertical === 'bottom';
|
|
73
|
+
|
|
74
|
+
let effAnchor = anchorOrigin;
|
|
75
|
+
let effMenu = menuOrigin;
|
|
76
|
+
let maxHeight: number | undefined;
|
|
77
|
+
|
|
78
|
+
if (autoFit && isDropConfig) {
|
|
79
|
+
// Space math is in window coordinates (the status-bar offset cancels out).
|
|
80
|
+
const spaceBelow = screenHeight - insets.bottom - (rect.y + rect.height) - offset - screenPadding;
|
|
81
|
+
const spaceAbove = rect.y - insets.top - offset - screenPadding;
|
|
82
|
+
let up = preferredUp;
|
|
83
|
+
if (!preferredUp) up = naturalHeight > spaceBelow && spaceAbove > spaceBelow;
|
|
84
|
+
else up = !(naturalHeight > spaceAbove && spaceBelow > spaceAbove);
|
|
85
|
+
const chosen = up ? spaceAbove : spaceBelow;
|
|
86
|
+
if (naturalHeight > chosen && chosen > 0) maxHeight = chosen;
|
|
87
|
+
effAnchor = { horizontal: anchorOrigin.horizontal, vertical: up ? 'top' : 'bottom' };
|
|
88
|
+
effMenu = { horizontal: menuOrigin.horizontal, vertical: up ? 'bottom' : 'top' };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Convert the trigger position into the Modal's (full-screen) coordinate space.
|
|
92
|
+
const anchorX = rect.x + H_FRAC[effAnchor.horizontal] * rect.width;
|
|
93
|
+
const anchorY = rect.y + statusBarHeight + V_FRAC[effAnchor.vertical] * rect.height;
|
|
94
|
+
const offsetY = effMenu.vertical === 'top' ? offset : effMenu.vertical === 'bottom' ? -offset : 0;
|
|
95
|
+
|
|
96
|
+
let left = anchorX - H_FRAC[effMenu.horizontal] * menuWidth;
|
|
97
|
+
let top = anchorY - V_FRAC[effMenu.vertical] * menuHeight + offsetY;
|
|
98
|
+
|
|
99
|
+
left = clamp(left, screenPadding + insets.left, screenWidth - insets.right - menuWidth - screenPadding);
|
|
100
|
+
top = clamp(
|
|
101
|
+
top,
|
|
102
|
+
statusBarHeight + screenPadding + insets.top,
|
|
103
|
+
screenHeight + statusBarHeight - insets.bottom - menuHeight - screenPadding,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return { top, left, maxHeight };
|
|
107
|
+
}
|
|
@@ -96,6 +96,8 @@ export interface SelectProps {
|
|
|
96
96
|
autoFit?: boolean;
|
|
97
97
|
/** Safe-area insets to keep clear. */
|
|
98
98
|
insets?: EdgeInsets;
|
|
99
|
+
/** Status-bar coordinate offset for the menu (see `Menu`). Pass 0 when immersive. */
|
|
100
|
+
statusBarHeight?: number;
|
|
99
101
|
/** Close after selecting. Defaults to `!multiple`. */
|
|
100
102
|
closeOnSelect?: boolean;
|
|
101
103
|
/** Container style. */
|
|
@@ -133,6 +135,7 @@ export function Select({
|
|
|
133
135
|
offset = 8,
|
|
134
136
|
autoFit = true,
|
|
135
137
|
insets,
|
|
138
|
+
statusBarHeight,
|
|
136
139
|
closeOnSelect,
|
|
137
140
|
style,
|
|
138
141
|
}: SelectProps) {
|
|
@@ -216,6 +219,7 @@ export function Select({
|
|
|
216
219
|
offset={offset}
|
|
217
220
|
autoFit={autoFit}
|
|
218
221
|
insets={insets}
|
|
222
|
+
statusBarHeight={statusBarHeight}
|
|
219
223
|
triggerStyle={{ alignSelf: 'stretch' }}
|
|
220
224
|
renderTrigger={({ open, toggle }) => {
|
|
221
225
|
const showRing = open && !disabled;
|