@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.
Files changed (72) hide show
  1. package/lib/commonjs/components/Icon/icons.generated.js +3 -0
  2. package/lib/commonjs/components/Icon/icons.generated.js.map +1 -1
  3. package/lib/commonjs/components/Menu/Menu.js +25 -52
  4. package/lib/commonjs/components/Menu/Menu.js.map +1 -1
  5. package/lib/commonjs/components/Menu/MenuButton.js +4 -0
  6. package/lib/commonjs/components/Menu/MenuButton.js.map +1 -1
  7. package/lib/commonjs/components/Menu/SelectButton.js +4 -0
  8. package/lib/commonjs/components/Menu/SelectButton.js.map +1 -1
  9. package/lib/commonjs/components/Menu/UserMenu.js +4 -0
  10. package/lib/commonjs/components/Menu/UserMenu.js.map +1 -1
  11. package/lib/commonjs/components/Menu/placement.js +84 -0
  12. package/lib/commonjs/components/Menu/placement.js.map +1 -0
  13. package/lib/commonjs/components/Menu/placement.test.js +186 -0
  14. package/lib/commonjs/components/Menu/placement.test.js.map +1 -0
  15. package/lib/commonjs/components/Select/Select.js +2 -0
  16. package/lib/commonjs/components/Select/Select.js.map +1 -1
  17. package/lib/module/components/Icon/icons.generated.js +3 -0
  18. package/lib/module/components/Icon/icons.generated.js.map +1 -1
  19. package/lib/module/components/Menu/Menu.js +27 -54
  20. package/lib/module/components/Menu/Menu.js.map +1 -1
  21. package/lib/module/components/Menu/MenuButton.js +4 -0
  22. package/lib/module/components/Menu/MenuButton.js.map +1 -1
  23. package/lib/module/components/Menu/SelectButton.js +4 -0
  24. package/lib/module/components/Menu/SelectButton.js.map +1 -1
  25. package/lib/module/components/Menu/UserMenu.js +4 -0
  26. package/lib/module/components/Menu/UserMenu.js.map +1 -1
  27. package/lib/module/components/Menu/placement.js +80 -0
  28. package/lib/module/components/Menu/placement.js.map +1 -0
  29. package/lib/module/components/Menu/placement.test.js +186 -0
  30. package/lib/module/components/Menu/placement.test.js.map +1 -0
  31. package/lib/module/components/Select/Select.js +2 -0
  32. package/lib/module/components/Select/Select.js.map +1 -1
  33. package/lib/typescript/commonjs/components/Icon/icons.generated.d.ts +3 -0
  34. package/lib/typescript/commonjs/components/Icon/icons.generated.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/components/Menu/Menu.d.ts +7 -1
  36. package/lib/typescript/commonjs/components/Menu/Menu.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/components/Menu/MenuButton.d.ts +6 -2
  38. package/lib/typescript/commonjs/components/Menu/MenuButton.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/components/Menu/SelectButton.d.ts +6 -2
  40. package/lib/typescript/commonjs/components/Menu/SelectButton.d.ts.map +1 -1
  41. package/lib/typescript/commonjs/components/Menu/UserMenu.d.ts +6 -2
  42. package/lib/typescript/commonjs/components/Menu/UserMenu.d.ts.map +1 -1
  43. package/lib/typescript/commonjs/components/Menu/placement.d.ts +47 -0
  44. package/lib/typescript/commonjs/components/Menu/placement.d.ts.map +1 -0
  45. package/lib/typescript/commonjs/components/Menu/placement.test.d.ts +2 -0
  46. package/lib/typescript/commonjs/components/Menu/placement.test.d.ts.map +1 -0
  47. package/lib/typescript/commonjs/components/Select/Select.d.ts +3 -1
  48. package/lib/typescript/commonjs/components/Select/Select.d.ts.map +1 -1
  49. package/lib/typescript/module/components/Icon/icons.generated.d.ts +3 -0
  50. package/lib/typescript/module/components/Icon/icons.generated.d.ts.map +1 -1
  51. package/lib/typescript/module/components/Menu/Menu.d.ts +7 -1
  52. package/lib/typescript/module/components/Menu/Menu.d.ts.map +1 -1
  53. package/lib/typescript/module/components/Menu/MenuButton.d.ts +6 -2
  54. package/lib/typescript/module/components/Menu/MenuButton.d.ts.map +1 -1
  55. package/lib/typescript/module/components/Menu/SelectButton.d.ts +6 -2
  56. package/lib/typescript/module/components/Menu/SelectButton.d.ts.map +1 -1
  57. package/lib/typescript/module/components/Menu/UserMenu.d.ts +6 -2
  58. package/lib/typescript/module/components/Menu/UserMenu.d.ts.map +1 -1
  59. package/lib/typescript/module/components/Menu/placement.d.ts +47 -0
  60. package/lib/typescript/module/components/Menu/placement.d.ts.map +1 -0
  61. package/lib/typescript/module/components/Menu/placement.test.d.ts +2 -0
  62. package/lib/typescript/module/components/Menu/placement.test.d.ts.map +1 -0
  63. package/lib/typescript/module/components/Select/Select.d.ts +3 -1
  64. package/lib/typescript/module/components/Select/Select.d.ts.map +1 -1
  65. package/package.json +1 -1
  66. package/src/components/Icon/icons.generated.ts +3 -0
  67. package/src/components/Menu/Menu.tsx +37 -46
  68. package/src/components/Menu/MenuButton.tsx +9 -1
  69. package/src/components/Menu/SelectButton.tsx +9 -1
  70. package/src/components/Menu/UserMenu.tsx +9 -1
  71. package/src/components/Menu/placement.ts +107 -0
  72. 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;