@bacon-pos/ui 0.11.0 → 0.16.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/lib/commonjs/components/CodeInput/CodeInput.js +28 -9
- package/lib/commonjs/components/CodeInput/CodeInput.js.map +1 -1
- package/lib/commonjs/components/CodeInput/CodeInput.stories.js +28 -1
- package/lib/commonjs/components/CodeInput/CodeInput.stories.js.map +1 -1
- package/lib/commonjs/components/CodeInput/CodeInput.test.js +16 -0
- package/lib/commonjs/components/CodeInput/CodeInput.test.js.map +1 -1
- package/lib/commonjs/components/Dialog/Dialog.js +116 -71
- package/lib/commonjs/components/Dialog/Dialog.js.map +1 -1
- package/lib/commonjs/components/Dialog/StepDialog.js +111 -0
- package/lib/commonjs/components/Dialog/StepDialog.js.map +1 -0
- package/lib/commonjs/components/Dialog/StepDialog.stories.js +157 -0
- package/lib/commonjs/components/Dialog/StepDialog.stories.js.map +1 -0
- package/lib/commonjs/components/Dialog/StepDialog.test.js +118 -0
- package/lib/commonjs/components/Dialog/StepDialog.test.js.map +1 -0
- package/lib/commonjs/components/Dialog/index.js +7 -0
- package/lib/commonjs/components/Dialog/index.js.map +1 -1
- package/lib/commonjs/components/Icon/icons.generated.js +9 -1
- package/lib/commonjs/components/Icon/icons.generated.js.map +1 -1
- package/lib/commonjs/components/List/List.js +45 -0
- package/lib/commonjs/components/List/List.js.map +1 -0
- package/lib/commonjs/components/List/List.stories.js +215 -0
- package/lib/commonjs/components/List/List.stories.js.map +1 -0
- package/lib/commonjs/components/List/List.test.js +94 -0
- package/lib/commonjs/components/List/List.test.js.map +1 -0
- package/lib/commonjs/components/List/ListItem.js +210 -0
- package/lib/commonjs/components/List/ListItem.js.map +1 -0
- package/lib/commonjs/components/List/index.js +20 -0
- package/lib/commonjs/components/List/index.js.map +1 -0
- package/lib/commonjs/components/Menu/Menu.js +11 -3
- package/lib/commonjs/components/Menu/Menu.js.map +1 -1
- package/lib/commonjs/components/ProgressBar/ProgressBar.js +4 -3
- package/lib/commonjs/components/ProgressBar/ProgressBar.js.map +1 -1
- package/lib/commonjs/components/SegmentedButton/SegmentedButton.js +217 -0
- package/lib/commonjs/components/SegmentedButton/SegmentedButton.js.map +1 -0
- package/lib/commonjs/components/SegmentedButton/SegmentedButton.stories.js +215 -0
- package/lib/commonjs/components/SegmentedButton/SegmentedButton.stories.js.map +1 -0
- package/lib/commonjs/components/SegmentedButton/SegmentedButton.test.js +97 -0
- package/lib/commonjs/components/SegmentedButton/SegmentedButton.test.js.map +1 -0
- package/lib/commonjs/components/SegmentedButton/index.js +13 -0
- package/lib/commonjs/components/SegmentedButton/index.js.map +1 -0
- package/lib/commonjs/components/Sidebar/Sidebar.js +20 -3
- package/lib/commonjs/components/Sidebar/Sidebar.js.map +1 -1
- package/lib/commonjs/components/Sidebar/Sidebar.stories.js +17 -1
- package/lib/commonjs/components/Sidebar/Sidebar.stories.js.map +1 -1
- package/lib/commonjs/components/Table/Table.js +69 -6
- package/lib/commonjs/components/Table/Table.js.map +1 -1
- package/lib/commonjs/components/Table/Table.stories.js +118 -1
- package/lib/commonjs/components/Table/Table.stories.js.map +1 -1
- package/lib/commonjs/components/Table/Table.test.js +58 -0
- package/lib/commonjs/components/Table/Table.test.js.map +1 -1
- package/lib/commonjs/components/_internal/ModalHostContext.js +21 -0
- package/lib/commonjs/components/_internal/ModalHostContext.js.map +1 -0
- package/lib/commonjs/components/index.js +37 -15
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/module/components/CodeInput/CodeInput.js +28 -9
- package/lib/module/components/CodeInput/CodeInput.js.map +1 -1
- package/lib/module/components/CodeInput/CodeInput.stories.js +27 -0
- package/lib/module/components/CodeInput/CodeInput.stories.js.map +1 -1
- package/lib/module/components/CodeInput/CodeInput.test.js +16 -0
- package/lib/module/components/CodeInput/CodeInput.test.js.map +1 -1
- package/lib/module/components/Dialog/Dialog.js +116 -71
- package/lib/module/components/Dialog/Dialog.js.map +1 -1
- package/lib/module/components/Dialog/StepDialog.js +107 -0
- package/lib/module/components/Dialog/StepDialog.js.map +1 -0
- package/lib/module/components/Dialog/StepDialog.stories.js +153 -0
- package/lib/module/components/Dialog/StepDialog.stories.js.map +1 -0
- package/lib/module/components/Dialog/StepDialog.test.js +117 -0
- package/lib/module/components/Dialog/StepDialog.test.js.map +1 -0
- package/lib/module/components/Dialog/index.js +1 -0
- package/lib/module/components/Dialog/index.js.map +1 -1
- package/lib/module/components/Icon/icons.generated.js +9 -1
- package/lib/module/components/Icon/icons.generated.js.map +1 -1
- package/lib/module/components/List/List.js +41 -0
- package/lib/module/components/List/List.js.map +1 -0
- package/lib/module/components/List/List.stories.js +211 -0
- package/lib/module/components/List/List.stories.js.map +1 -0
- package/lib/module/components/List/List.test.js +94 -0
- package/lib/module/components/List/List.test.js.map +1 -0
- package/lib/module/components/List/ListItem.js +206 -0
- package/lib/module/components/List/ListItem.js.map +1 -0
- package/lib/module/components/List/index.js +5 -0
- package/lib/module/components/List/index.js.map +1 -0
- package/lib/module/components/Menu/Menu.js +12 -4
- package/lib/module/components/Menu/Menu.js.map +1 -1
- package/lib/module/components/ProgressBar/ProgressBar.js +4 -3
- package/lib/module/components/ProgressBar/ProgressBar.js.map +1 -1
- package/lib/module/components/SegmentedButton/SegmentedButton.js +213 -0
- package/lib/module/components/SegmentedButton/SegmentedButton.js.map +1 -0
- package/lib/module/components/SegmentedButton/SegmentedButton.stories.js +211 -0
- package/lib/module/components/SegmentedButton/SegmentedButton.stories.js.map +1 -0
- package/lib/module/components/SegmentedButton/SegmentedButton.test.js +96 -0
- package/lib/module/components/SegmentedButton/SegmentedButton.test.js.map +1 -0
- package/lib/module/components/SegmentedButton/index.js +4 -0
- package/lib/module/components/SegmentedButton/index.js.map +1 -0
- package/lib/module/components/Sidebar/Sidebar.js +20 -3
- package/lib/module/components/Sidebar/Sidebar.js.map +1 -1
- package/lib/module/components/Sidebar/Sidebar.stories.js +16 -0
- package/lib/module/components/Sidebar/Sidebar.stories.js.map +1 -1
- package/lib/module/components/Table/Table.js +70 -7
- package/lib/module/components/Table/Table.js.map +1 -1
- package/lib/module/components/Table/Table.stories.js +117 -0
- package/lib/module/components/Table/Table.stories.js.map +1 -1
- package/lib/module/components/Table/Table.test.js +58 -0
- package/lib/module/components/Table/Table.test.js.map +1 -1
- package/lib/module/components/_internal/ModalHostContext.js +17 -0
- package/lib/module/components/_internal/ModalHostContext.js.map +1 -0
- package/lib/module/components/index.js +2 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/typescript/commonjs/components/CodeInput/CodeInput.d.ts +5 -1
- package/lib/typescript/commonjs/components/CodeInput/CodeInput.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/Dialog/Dialog.d.ts +13 -1
- package/lib/typescript/commonjs/components/Dialog/Dialog.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/Dialog/StepDialog.d.ts +63 -0
- package/lib/typescript/commonjs/components/Dialog/StepDialog.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/Dialog/index.d.ts +1 -0
- package/lib/typescript/commonjs/components/Dialog/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/Icon/icons.generated.d.ts +8 -0
- package/lib/typescript/commonjs/components/Icon/icons.generated.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/List/List.d.ts +22 -0
- package/lib/typescript/commonjs/components/List/List.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/List/ListItem.d.ts +61 -0
- package/lib/typescript/commonjs/components/List/ListItem.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/List/index.d.ts +5 -0
- package/lib/typescript/commonjs/components/List/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/Menu/Menu.d.ts +4 -2
- package/lib/typescript/commonjs/components/Menu/Menu.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/ProgressBar/ProgressBar.d.ts +3 -1
- package/lib/typescript/commonjs/components/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/SegmentedButton/SegmentedButton.d.ts +68 -0
- package/lib/typescript/commonjs/components/SegmentedButton/SegmentedButton.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/SegmentedButton/index.d.ts +2 -0
- package/lib/typescript/commonjs/components/SegmentedButton/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/Sidebar/Sidebar.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/Table/Table.d.ts +25 -3
- package/lib/typescript/commonjs/components/Table/Table.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/_internal/ModalHostContext.d.ts +11 -0
- package/lib/typescript/commonjs/components/_internal/ModalHostContext.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/index.d.ts +2 -0
- package/lib/typescript/commonjs/components/index.d.ts.map +1 -1
- package/lib/typescript/module/components/CodeInput/CodeInput.d.ts +5 -1
- package/lib/typescript/module/components/CodeInput/CodeInput.d.ts.map +1 -1
- package/lib/typescript/module/components/Dialog/Dialog.d.ts +13 -1
- package/lib/typescript/module/components/Dialog/Dialog.d.ts.map +1 -1
- package/lib/typescript/module/components/Dialog/StepDialog.d.ts +63 -0
- package/lib/typescript/module/components/Dialog/StepDialog.d.ts.map +1 -0
- package/lib/typescript/module/components/Dialog/index.d.ts +1 -0
- package/lib/typescript/module/components/Dialog/index.d.ts.map +1 -1
- package/lib/typescript/module/components/Icon/icons.generated.d.ts +8 -0
- package/lib/typescript/module/components/Icon/icons.generated.d.ts.map +1 -1
- package/lib/typescript/module/components/List/List.d.ts +22 -0
- package/lib/typescript/module/components/List/List.d.ts.map +1 -0
- package/lib/typescript/module/components/List/ListItem.d.ts +61 -0
- package/lib/typescript/module/components/List/ListItem.d.ts.map +1 -0
- package/lib/typescript/module/components/List/index.d.ts +5 -0
- package/lib/typescript/module/components/List/index.d.ts.map +1 -0
- package/lib/typescript/module/components/Menu/Menu.d.ts +4 -2
- package/lib/typescript/module/components/Menu/Menu.d.ts.map +1 -1
- package/lib/typescript/module/components/ProgressBar/ProgressBar.d.ts +3 -1
- package/lib/typescript/module/components/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/lib/typescript/module/components/SegmentedButton/SegmentedButton.d.ts +68 -0
- package/lib/typescript/module/components/SegmentedButton/SegmentedButton.d.ts.map +1 -0
- package/lib/typescript/module/components/SegmentedButton/index.d.ts +2 -0
- package/lib/typescript/module/components/SegmentedButton/index.d.ts.map +1 -0
- package/lib/typescript/module/components/Sidebar/Sidebar.d.ts.map +1 -1
- package/lib/typescript/module/components/Table/Table.d.ts +25 -3
- package/lib/typescript/module/components/Table/Table.d.ts.map +1 -1
- package/lib/typescript/module/components/_internal/ModalHostContext.d.ts +11 -0
- package/lib/typescript/module/components/_internal/ModalHostContext.d.ts.map +1 -0
- package/lib/typescript/module/components/index.d.ts +2 -0
- package/lib/typescript/module/components/index.d.ts.map +1 -1
- package/package.json +16 -14
- package/src/components/CodeInput/CodeInput.tsx +46 -14
- package/src/components/Dialog/Dialog.tsx +92 -31
- package/src/components/Dialog/StepDialog.tsx +173 -0
- package/src/components/Dialog/index.ts +1 -0
- package/src/components/Icon/icons.generated.ts +9 -1
- package/src/components/List/List.tsx +45 -0
- package/src/components/List/ListItem.tsx +214 -0
- package/src/components/List/index.ts +4 -0
- package/src/components/Menu/Menu.tsx +15 -6
- package/src/components/ProgressBar/ProgressBar.tsx +6 -3
- package/src/components/SegmentedButton/SegmentedButton.tsx +269 -0
- package/src/components/SegmentedButton/index.ts +7 -0
- package/src/components/Sidebar/Sidebar.tsx +18 -10
- package/src/components/Table/Table.tsx +93 -8
- package/src/components/_internal/ModalHostContext.tsx +15 -0
- package/src/components/index.ts +2 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useRef, useState, type ReactNode } from 'react';
|
|
1
|
+
import { useContext, useEffect, useRef, useState, type ReactNode } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Animated,
|
|
4
4
|
Dimensions,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type StyleProp,
|
|
13
13
|
type ViewStyle,
|
|
14
14
|
} from 'react-native';
|
|
15
|
+
import { ModalHostContext } from '../_internal/ModalHostContext';
|
|
15
16
|
import { computeMenuPlacement } from './placement';
|
|
16
17
|
|
|
17
18
|
export type OriginVertical = 'top' | 'center' | 'bottom';
|
|
@@ -57,8 +58,10 @@ export interface MenuProps {
|
|
|
57
58
|
insets?: EdgeInsets;
|
|
58
59
|
/**
|
|
59
60
|
* Offset between window coordinates (where the trigger is measured) and the
|
|
60
|
-
* full-screen Modal the menu paints into.
|
|
61
|
-
*
|
|
61
|
+
* full-screen Modal the menu paints into. When omitted it defaults to the
|
|
62
|
+
* Android status bar height, or 0 when rendered inside a full-screen Modal host
|
|
63
|
+
* (e.g. `Dialog`) or on iOS / web. Pass an explicit value to override (e.g. 0
|
|
64
|
+
* when the host runs edge-to-edge / immersive).
|
|
62
65
|
*/
|
|
63
66
|
statusBarHeight?: number;
|
|
64
67
|
/** Controlled open state. Omit for uncontrolled. */
|
|
@@ -105,13 +108,18 @@ export function Menu({
|
|
|
105
108
|
offset = 8,
|
|
106
109
|
autoFit = true,
|
|
107
110
|
insets = ZERO_INSETS,
|
|
108
|
-
statusBarHeight
|
|
111
|
+
statusBarHeight,
|
|
109
112
|
open: openProp,
|
|
110
113
|
onOpenChange,
|
|
111
114
|
screenPadding = 8,
|
|
112
115
|
triggerStyle,
|
|
113
116
|
}: MenuProps) {
|
|
114
117
|
const triggerRef = useRef<View>(null);
|
|
118
|
+
// Inside a full-screen Modal host (e.g. Dialog) the trigger is already measured
|
|
119
|
+
// in full-screen coordinates, so no status-bar compensation is needed there.
|
|
120
|
+
const insideModalHost = useContext(ModalHostContext);
|
|
121
|
+
const effectiveStatusBarHeight =
|
|
122
|
+
statusBarHeight ?? (insideModalHost ? 0 : DEFAULT_STATUS_BAR_HEIGHT);
|
|
115
123
|
const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
|
|
116
124
|
const isControlled = openProp !== undefined;
|
|
117
125
|
const open = isControlled ? openProp : uncontrolledOpen;
|
|
@@ -182,7 +190,7 @@ export function Menu({
|
|
|
182
190
|
insets,
|
|
183
191
|
screenWidth: screen.width,
|
|
184
192
|
screenHeight: screen.height,
|
|
185
|
-
statusBarHeight,
|
|
193
|
+
statusBarHeight: effectiveStatusBarHeight,
|
|
186
194
|
autoFit,
|
|
187
195
|
})
|
|
188
196
|
: null;
|
|
@@ -231,7 +239,8 @@ export function Menu({
|
|
|
231
239
|
transform: [{ scale: placed ? anim.interpolate({ inputRange: [0, 1], outputRange: [0.96, 1] }) : 1 }],
|
|
232
240
|
}}
|
|
233
241
|
>
|
|
234
|
-
{
|
|
242
|
+
{/* The overlay is itself a full-screen Modal host for nested menus. */}
|
|
243
|
+
<ModalHostContext.Provider value={true}>{content}</ModalHostContext.Provider>
|
|
235
244
|
</Animated.View>
|
|
236
245
|
</Pressable>
|
|
237
246
|
</Modal>
|
|
@@ -24,6 +24,8 @@ export interface ProgressBarProps {
|
|
|
24
24
|
trackColor?: string;
|
|
25
25
|
/** Track border color. Defaults to `border.default`. */
|
|
26
26
|
borderColor?: string;
|
|
27
|
+
/** Draw the track border (and inner inset). Set false for a flat, thin bar. Defaults to true. */
|
|
28
|
+
bordered?: boolean;
|
|
27
29
|
/** Animate width changes. Defaults to true. */
|
|
28
30
|
animated?: boolean;
|
|
29
31
|
/** Override the container style. */
|
|
@@ -51,6 +53,7 @@ export function ProgressBar({
|
|
|
51
53
|
color,
|
|
52
54
|
trackColor,
|
|
53
55
|
borderColor,
|
|
56
|
+
bordered = true,
|
|
54
57
|
animated = true,
|
|
55
58
|
style,
|
|
56
59
|
accessibilityLabel,
|
|
@@ -79,7 +82,7 @@ export function ProgressBar({
|
|
|
79
82
|
extrapolate: 'clamp',
|
|
80
83
|
});
|
|
81
84
|
|
|
82
|
-
const inset = 2.5;
|
|
85
|
+
const inset = bordered ? 2.5 : 0;
|
|
83
86
|
|
|
84
87
|
return (
|
|
85
88
|
<View
|
|
@@ -92,8 +95,8 @@ export function ProgressBar({
|
|
|
92
95
|
style={{
|
|
93
96
|
height,
|
|
94
97
|
borderRadius: height / 2,
|
|
95
|
-
borderWidth: 1.5,
|
|
96
|
-
borderColor: borderColor ?? theme.colors.border.default,
|
|
98
|
+
borderWidth: bordered ? 1.5 : 0,
|
|
99
|
+
borderColor: bordered ? borderColor ?? theme.colors.border.default : 'transparent',
|
|
97
100
|
backgroundColor: trackColor ?? theme.colors.background.subtle,
|
|
98
101
|
padding: inset,
|
|
99
102
|
justifyContent: 'center',
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
Easing,
|
|
5
|
+
Pressable,
|
|
6
|
+
View,
|
|
7
|
+
type LayoutChangeEvent,
|
|
8
|
+
type PressableStateCallbackType,
|
|
9
|
+
type StyleProp,
|
|
10
|
+
type ViewStyle,
|
|
11
|
+
} from 'react-native';
|
|
12
|
+
import { useTheme } from '../../theme';
|
|
13
|
+
import type { RadiusToken } from '../../theme/spacing';
|
|
14
|
+
import type { TypographyVariant } from '../../theme/typography';
|
|
15
|
+
import { Icon } from '../Icon/Icon';
|
|
16
|
+
import type { IconName } from '../Icon/icons.generated';
|
|
17
|
+
import { Text } from '../Text/Text';
|
|
18
|
+
|
|
19
|
+
export type SegmentedButtonSize = 'sm' | 'md';
|
|
20
|
+
/** `default` = soft-rounded rectangle; `round` = fully rounded (pill). */
|
|
21
|
+
export type SegmentedButtonShape = 'default' | 'round';
|
|
22
|
+
|
|
23
|
+
export interface SegmentedOption {
|
|
24
|
+
/** Visible label. */
|
|
25
|
+
label: string;
|
|
26
|
+
/** Unique value reported on change. */
|
|
27
|
+
value: string;
|
|
28
|
+
/** Optional icon shown next to the label. */
|
|
29
|
+
icon?: IconName;
|
|
30
|
+
/** Icon side. Defaults to `left`. */
|
|
31
|
+
iconPosition?: 'left' | 'right';
|
|
32
|
+
/** Disable just this segment. */
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SegmentedButtonProps {
|
|
37
|
+
/** The segments. Two or more recommended; any count is supported. */
|
|
38
|
+
options: SegmentedOption[];
|
|
39
|
+
/** Selected value (controlled). */
|
|
40
|
+
value?: string;
|
|
41
|
+
/** Initial selected value (uncontrolled). Defaults to the first option. */
|
|
42
|
+
defaultValue?: string;
|
|
43
|
+
/** Change handler. */
|
|
44
|
+
onChange?: (value: string) => void;
|
|
45
|
+
/** Size: `sm` (16px text) or `md` (18px text, default). */
|
|
46
|
+
size?: SegmentedButtonSize;
|
|
47
|
+
/** Corner style: `default` (rounded rect) or `round` (pill). Defaults to `default`. */
|
|
48
|
+
shape?: SegmentedButtonShape;
|
|
49
|
+
/** Override the track corner radius in px (segments follow). Wins over `shape`. */
|
|
50
|
+
radius?: number;
|
|
51
|
+
/** Stretch to the container; segments share the width equally. */
|
|
52
|
+
fullWidth?: boolean;
|
|
53
|
+
/** Fixed track width in px (overrides `fullWidth`). */
|
|
54
|
+
width?: number;
|
|
55
|
+
/** Disable the whole control. */
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
/** Show the track border. Defaults to true. */
|
|
58
|
+
bordered?: boolean;
|
|
59
|
+
/** Track background color. Defaults to `background.muted`. */
|
|
60
|
+
trackColor?: string;
|
|
61
|
+
/** Selected segment background. Defaults to `action.inverse` (dark). */
|
|
62
|
+
selectedColor?: string;
|
|
63
|
+
/** Selected segment text/icon color. Defaults to `action.inverseForeground`. */
|
|
64
|
+
selectedTextColor?: string;
|
|
65
|
+
/** Unselected text/icon color. Defaults to `text.secondary`. */
|
|
66
|
+
textColor?: string;
|
|
67
|
+
/** Icon size override. Defaults to the size preset. */
|
|
68
|
+
iconSize?: number;
|
|
69
|
+
/** Slide the selected highlight between segments. Defaults to true. */
|
|
70
|
+
animated?: boolean;
|
|
71
|
+
/** Override the track container style. */
|
|
72
|
+
style?: StyleProp<ViewStyle>;
|
|
73
|
+
/** Accessibility label for the group. */
|
|
74
|
+
accessibilityLabel?: string;
|
|
75
|
+
testID?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const SIZES: Record<
|
|
79
|
+
SegmentedButtonSize,
|
|
80
|
+
{
|
|
81
|
+
paddingH: number;
|
|
82
|
+
paddingV: number;
|
|
83
|
+
gap: number;
|
|
84
|
+
trackPadding: number;
|
|
85
|
+
text: TypographyVariant;
|
|
86
|
+
icon: number;
|
|
87
|
+
radius: RadiusToken;
|
|
88
|
+
}
|
|
89
|
+
> = {
|
|
90
|
+
sm: { paddingH: 12, paddingV: 10, gap: 6, trackPadding: 4, text: 'bodyMedium', icon: 18, radius: 'md' },
|
|
91
|
+
md: { paddingH: 16, paddingV: 12, gap: 8, trackPadding: 5, text: 'bodyLarge', icon: 20, radius: 'lg' },
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* SegmentedButton — faithful to the Bacon POS Figma "Segmented button". A single
|
|
96
|
+
* connected control of 2+ options where exactly one is selected: the active
|
|
97
|
+
* segment fills dark with inverse text; the rest are transparent with secondary
|
|
98
|
+
* text, all inside a rounded track. Supports `sm`/`md` sizes, `default`/`round`
|
|
99
|
+
* shapes (or a custom `radius`), hug / `fullWidth` / fixed `width` layouts, icons,
|
|
100
|
+
* per-segment disabling, and color overrides. Controlled or uncontrolled.
|
|
101
|
+
*/
|
|
102
|
+
export function SegmentedButton({
|
|
103
|
+
options,
|
|
104
|
+
value,
|
|
105
|
+
defaultValue,
|
|
106
|
+
onChange,
|
|
107
|
+
size = 'md',
|
|
108
|
+
shape = 'default',
|
|
109
|
+
radius,
|
|
110
|
+
fullWidth = false,
|
|
111
|
+
width,
|
|
112
|
+
disabled = false,
|
|
113
|
+
bordered = true,
|
|
114
|
+
trackColor,
|
|
115
|
+
selectedColor,
|
|
116
|
+
selectedTextColor,
|
|
117
|
+
textColor,
|
|
118
|
+
iconSize,
|
|
119
|
+
animated = true,
|
|
120
|
+
style,
|
|
121
|
+
accessibilityLabel,
|
|
122
|
+
testID,
|
|
123
|
+
}: SegmentedButtonProps) {
|
|
124
|
+
const theme = useTheme();
|
|
125
|
+
const cfg = SIZES[size];
|
|
126
|
+
|
|
127
|
+
const isControlled = value !== undefined;
|
|
128
|
+
const [uncontrolled, setUncontrolled] = useState(defaultValue ?? options[0]?.value ?? '');
|
|
129
|
+
const selected = isControlled ? value : uncontrolled;
|
|
130
|
+
|
|
131
|
+
const select = (next: string) => {
|
|
132
|
+
if (!isControlled) setUncontrolled(next);
|
|
133
|
+
onChange?.(next);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Sliding highlight (thumb): we measure each segment's position/size and glide
|
|
137
|
+
// an absolutely-positioned fill to the selected one.
|
|
138
|
+
const layouts = useRef<Record<string, { x: number; width: number }>>({});
|
|
139
|
+
const thumbX = useRef(new Animated.Value(0)).current;
|
|
140
|
+
const thumbW = useRef(new Animated.Value(0)).current;
|
|
141
|
+
const placed = useRef(false);
|
|
142
|
+
const [measured, setMeasured] = useState(false);
|
|
143
|
+
|
|
144
|
+
const placeThumb = (x: number, width: number, withAnim: boolean) => {
|
|
145
|
+
if (withAnim && animated) {
|
|
146
|
+
Animated.parallel([
|
|
147
|
+
Animated.timing(thumbX, { toValue: x, duration: 200, easing: Easing.out(Easing.cubic), useNativeDriver: false }),
|
|
148
|
+
Animated.timing(thumbW, { toValue: width, duration: 200, easing: Easing.out(Easing.cubic), useNativeDriver: false }),
|
|
149
|
+
]).start();
|
|
150
|
+
} else {
|
|
151
|
+
thumbX.setValue(x);
|
|
152
|
+
thumbW.setValue(width);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Animate to the selected segment when it changes (once layouts are known).
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
const l = layouts.current[selected];
|
|
159
|
+
if (!l) return;
|
|
160
|
+
placeThumb(l.x, l.width, placed.current);
|
|
161
|
+
if (!placed.current) {
|
|
162
|
+
placed.current = true;
|
|
163
|
+
setMeasured(true);
|
|
164
|
+
}
|
|
165
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
166
|
+
}, [selected, animated]);
|
|
167
|
+
|
|
168
|
+
const onSegmentLayout = (value: string) => (e: LayoutChangeEvent) => {
|
|
169
|
+
const { x, width } = e.nativeEvent.layout;
|
|
170
|
+
layouts.current[value] = { x, width };
|
|
171
|
+
// Place/keep the thumb aligned with the selected segment (initial mount and
|
|
172
|
+
// on relayout/resize) without animating.
|
|
173
|
+
if (value === selected) {
|
|
174
|
+
placeThumb(x, width, false);
|
|
175
|
+
if (!placed.current) {
|
|
176
|
+
placed.current = true;
|
|
177
|
+
setMeasured(true);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const trackRadius = radius ?? (shape === 'round' ? theme.radii.pill : theme.radii.xl);
|
|
183
|
+
const segmentRadius =
|
|
184
|
+
shape === 'round'
|
|
185
|
+
? theme.radii.pill
|
|
186
|
+
: radius != null
|
|
187
|
+
? Math.max(radius - cfg.trackPadding, 0)
|
|
188
|
+
: theme.radii[cfg.radius];
|
|
189
|
+
|
|
190
|
+
const selectedBg = selectedColor ?? theme.colors.action.inverse;
|
|
191
|
+
const selectedFg = selectedTextColor ?? theme.colors.action.inverseForeground;
|
|
192
|
+
const restFg = textColor ?? theme.colors.text.secondary;
|
|
193
|
+
const resolvedIconSize = iconSize ?? cfg.icon;
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<View
|
|
197
|
+
testID={testID}
|
|
198
|
+
accessibilityRole="radiogroup"
|
|
199
|
+
accessibilityLabel={accessibilityLabel}
|
|
200
|
+
style={[
|
|
201
|
+
{
|
|
202
|
+
padding: cfg.trackPadding,
|
|
203
|
+
borderRadius: trackRadius,
|
|
204
|
+
borderWidth: bordered ? 1 : 0,
|
|
205
|
+
borderColor: theme.colors.border.default,
|
|
206
|
+
backgroundColor: trackColor ?? theme.colors.background.muted,
|
|
207
|
+
alignSelf: fullWidth && width == null ? 'stretch' : 'flex-start',
|
|
208
|
+
width: width ?? (fullWidth ? '100%' : undefined),
|
|
209
|
+
opacity: disabled ? 0.5 : 1,
|
|
210
|
+
},
|
|
211
|
+
style,
|
|
212
|
+
]}
|
|
213
|
+
>
|
|
214
|
+
{/* Inner row: shares an unpadded coordinate space with the thumb so the
|
|
215
|
+
measured segment x/width line up exactly. */}
|
|
216
|
+
<View style={{ flexDirection: 'row', alignItems: 'stretch', gap: cfg.gap, position: 'relative' }}>
|
|
217
|
+
<Animated.View
|
|
218
|
+
pointerEvents="none"
|
|
219
|
+
style={{
|
|
220
|
+
position: 'absolute',
|
|
221
|
+
top: 0,
|
|
222
|
+
bottom: 0,
|
|
223
|
+
left: thumbX,
|
|
224
|
+
width: thumbW,
|
|
225
|
+
borderRadius: segmentRadius,
|
|
226
|
+
backgroundColor: selectedBg,
|
|
227
|
+
opacity: measured ? 1 : 0,
|
|
228
|
+
}}
|
|
229
|
+
/>
|
|
230
|
+
{options.map((opt) => {
|
|
231
|
+
const isSelected = opt.value === selected;
|
|
232
|
+
const isDisabled = disabled || opt.disabled;
|
|
233
|
+
const fg = isSelected ? selectedFg : restFg;
|
|
234
|
+
const iconNode = opt.icon ? <Icon name={opt.icon} size={resolvedIconSize} color={fg} /> : null;
|
|
235
|
+
const iconRight = opt.iconPosition === 'right';
|
|
236
|
+
return (
|
|
237
|
+
<Pressable
|
|
238
|
+
key={opt.value}
|
|
239
|
+
onLayout={onSegmentLayout(opt.value)}
|
|
240
|
+
onPress={isDisabled ? undefined : () => select(opt.value)}
|
|
241
|
+
disabled={isDisabled}
|
|
242
|
+
accessibilityRole="button"
|
|
243
|
+
accessibilityState={{ selected: isSelected, disabled: isDisabled }}
|
|
244
|
+
accessibilityLabel={opt.label}
|
|
245
|
+
testID={`segment-${opt.value}`}
|
|
246
|
+
style={({ pressed }: PressableStateCallbackType): StyleProp<ViewStyle> => ({
|
|
247
|
+
flex: fullWidth || width != null ? 1 : undefined,
|
|
248
|
+
flexDirection: 'row',
|
|
249
|
+
alignItems: 'center',
|
|
250
|
+
justifyContent: 'center',
|
|
251
|
+
gap: cfg.gap,
|
|
252
|
+
paddingHorizontal: cfg.paddingH,
|
|
253
|
+
paddingVertical: cfg.paddingV,
|
|
254
|
+
borderRadius: segmentRadius,
|
|
255
|
+
opacity: opt.disabled ? 0.5 : pressed && !isSelected ? 0.6 : 1,
|
|
256
|
+
})}
|
|
257
|
+
>
|
|
258
|
+
{iconNode && !iconRight ? iconNode : null}
|
|
259
|
+
<Text variant={cfg.text} weight="medium" color={fg} numberOfLines={1}>
|
|
260
|
+
{opt.label}
|
|
261
|
+
</Text>
|
|
262
|
+
{iconNode && iconRight ? iconNode : null}
|
|
263
|
+
</Pressable>
|
|
264
|
+
);
|
|
265
|
+
})}
|
|
266
|
+
</View>
|
|
267
|
+
</View>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
@@ -234,6 +234,15 @@ export function Sidebar({
|
|
|
234
234
|
extrapolate: 'clamp',
|
|
235
235
|
});
|
|
236
236
|
|
|
237
|
+
// The logo glides to center the same way (measured so any custom logo works),
|
|
238
|
+
// at a constant size — no discrete size/alignment swap, so no flicker.
|
|
239
|
+
const [logoWidth, setLogoWidth] = useState(0);
|
|
240
|
+
const logoTranslateX = width.interpolate({
|
|
241
|
+
inputRange: [collapsedWidth, expandedWidth],
|
|
242
|
+
outputRange: [Math.max(0, (collapsedWidth - 2 * PADDING - logoWidth) / 2), 0],
|
|
243
|
+
extrapolate: 'clamp',
|
|
244
|
+
});
|
|
245
|
+
|
|
237
246
|
const selectItem = (item: SidebarItem) => {
|
|
238
247
|
if (!activeControlled) setActiveInternal(item.key);
|
|
239
248
|
item.onPress?.();
|
|
@@ -246,7 +255,7 @@ export function Sidebar({
|
|
|
246
255
|
onCollapsedChange?.(next);
|
|
247
256
|
};
|
|
248
257
|
|
|
249
|
-
const logoNode = logo ?? <BaconLogo height={
|
|
258
|
+
const logoNode = logo ?? <BaconLogo height={40} />;
|
|
250
259
|
const toggle = showToggle ? (
|
|
251
260
|
<Animated.View style={{ transform: [{ translateX: toggleTranslateX }] }}>
|
|
252
261
|
<Animated.View style={{ transform: [{ rotate: arrowRotate }] }}>
|
|
@@ -276,15 +285,14 @@ export function Sidebar({
|
|
|
276
285
|
style,
|
|
277
286
|
]}
|
|
278
287
|
>
|
|
279
|
-
{/* Logo */}
|
|
280
|
-
<View
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
{logoNode}
|
|
288
|
+
{/* Logo — constant size, glides to center on collapse (no flicker) */}
|
|
289
|
+
<View style={{ minHeight: 56, alignItems: 'flex-start', justifyContent: 'center' }}>
|
|
290
|
+
<Animated.View
|
|
291
|
+
onLayout={(e) => setLogoWidth(e.nativeEvent.layout.width)}
|
|
292
|
+
style={{ transform: [{ translateX: logoTranslateX }] }}
|
|
293
|
+
>
|
|
294
|
+
{logoNode}
|
|
295
|
+
</Animated.View>
|
|
288
296
|
</View>
|
|
289
297
|
|
|
290
298
|
{/* Tabs (scrolls when overflowing; footer stays pinned below) */}
|
|
@@ -11,13 +11,30 @@ import {
|
|
|
11
11
|
type ViewStyle,
|
|
12
12
|
} from 'react-native';
|
|
13
13
|
import { useTheme } from '../../theme';
|
|
14
|
+
import type { DropdownEntry } from '../Dropdown/Dropdown';
|
|
14
15
|
import { Checkbox } from '../Checkbox/Checkbox';
|
|
15
16
|
import { Icon } from '../Icon/Icon';
|
|
17
|
+
import type { IconName } from '../Icon/icons.generated';
|
|
18
|
+
import { MenuButton } from '../Menu/MenuButton';
|
|
16
19
|
import { Spinner } from '../Spinner/Spinner';
|
|
17
20
|
import { Text } from '../Text/Text';
|
|
18
21
|
import { ScrollArea } from '../_internal/ScrollArea';
|
|
19
22
|
|
|
20
23
|
export type ColumnAlign = 'left' | 'center' | 'right';
|
|
24
|
+
|
|
25
|
+
/** A structured row action (inline icon, or a row in the overflow "More" menu). */
|
|
26
|
+
export interface TableAction {
|
|
27
|
+
key: string;
|
|
28
|
+
label: string;
|
|
29
|
+
icon: IconName;
|
|
30
|
+
onPress?: () => void;
|
|
31
|
+
/** Render in the error color (red icon inline, red row in the menu). */
|
|
32
|
+
destructive?: boolean;
|
|
33
|
+
/** Dim and block the action. */
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
/** Icon color override (ignored when `destructive`). */
|
|
36
|
+
color?: string;
|
|
37
|
+
}
|
|
21
38
|
export type SortDirection = 'asc' | 'desc';
|
|
22
39
|
export interface TableSort {
|
|
23
40
|
key: string;
|
|
@@ -63,9 +80,17 @@ export interface TableProps<T> {
|
|
|
63
80
|
defaultExpandedKeys?: string[];
|
|
64
81
|
onExpandedChange?: (keys: string[]) => void;
|
|
65
82
|
|
|
66
|
-
/** Trailing per-row actions (e.g.
|
|
83
|
+
/** Trailing per-row actions, free-form (e.g. custom buttons) — mutually exclusive with `expandable`. */
|
|
67
84
|
renderActions?: (row: T, index: number) => ReactNode;
|
|
68
|
-
/**
|
|
85
|
+
/** Structured per-row actions: the first `maxInlineActions` render as inline icons, the rest collapse into a "More" menu. */
|
|
86
|
+
actions?: (row: T, index: number) => TableAction[];
|
|
87
|
+
/** How many actions render inline before overflowing into the More menu. Defaults to 2. */
|
|
88
|
+
maxInlineActions?: number;
|
|
89
|
+
/** Icon for the overflow menu trigger. Defaults to `more`. */
|
|
90
|
+
moreIcon?: IconName;
|
|
91
|
+
/** Width of the overflow More menu. */
|
|
92
|
+
actionsMenuWidth?: number;
|
|
93
|
+
/** Width reserved for the actions column. Defaults to fit the actions. */
|
|
69
94
|
actionsWidth?: number;
|
|
70
95
|
|
|
71
96
|
/** Row selection via checkboxes. */
|
|
@@ -135,7 +160,11 @@ export function Table<T>({
|
|
|
135
160
|
defaultExpandedKeys,
|
|
136
161
|
onExpandedChange,
|
|
137
162
|
renderActions,
|
|
138
|
-
|
|
163
|
+
actions,
|
|
164
|
+
maxInlineActions = 2,
|
|
165
|
+
moreIcon = 'more',
|
|
166
|
+
actionsMenuWidth,
|
|
167
|
+
actionsWidth,
|
|
139
168
|
selectable = false,
|
|
140
169
|
selectedKeys,
|
|
141
170
|
defaultSelectedKeys,
|
|
@@ -159,8 +188,11 @@ export function Table<T>({
|
|
|
159
188
|
const isMobile = screenWidth < mobileBreakpoint;
|
|
160
189
|
|
|
161
190
|
// Expandable and actions are mutually exclusive; expandable wins if both set.
|
|
162
|
-
const hasActions = !expandable && !!renderActions;
|
|
163
|
-
const
|
|
191
|
+
const hasActions = !expandable && (!!renderActions || !!actions);
|
|
192
|
+
const ACTION_SLOT = 44; // sm icon-button footprint (40 + gap)
|
|
193
|
+
const actionsColWidth =
|
|
194
|
+
actionsWidth ?? (renderActions ? 96 : (maxInlineActions + 1) * ACTION_SLOT);
|
|
195
|
+
const trailingWidth = expandable ? CHEVRON_WIDTH : hasActions ? actionsColWidth : 0;
|
|
164
196
|
|
|
165
197
|
// ---- sort ----
|
|
166
198
|
const sortControlled = sort !== undefined;
|
|
@@ -245,6 +277,57 @@ export function Table<T>({
|
|
|
245
277
|
return content as ReactNode;
|
|
246
278
|
};
|
|
247
279
|
|
|
280
|
+
// Actions cell: free-form `renderActions`, or structured `actions` with the
|
|
281
|
+
// first N inline and the rest in a "More" menu (all in the menu on mobile).
|
|
282
|
+
const renderActionsCell = (row: T, index: number): ReactNode => {
|
|
283
|
+
if (renderActions) return renderActions(row, index);
|
|
284
|
+
if (!actions) return null;
|
|
285
|
+
const list = actions(row, index);
|
|
286
|
+
const inlineCount = isMobile ? 0 : maxInlineActions;
|
|
287
|
+
const inline = list.slice(0, inlineCount);
|
|
288
|
+
const overflow = list.slice(inlineCount);
|
|
289
|
+
return (
|
|
290
|
+
<>
|
|
291
|
+
{inline.map((a) => (
|
|
292
|
+
<Pressable
|
|
293
|
+
key={a.key}
|
|
294
|
+
onPress={a.disabled ? undefined : a.onPress}
|
|
295
|
+
disabled={a.disabled}
|
|
296
|
+
hitSlop={8}
|
|
297
|
+
accessibilityRole="button"
|
|
298
|
+
accessibilityLabel={a.label}
|
|
299
|
+
style={{ width: 40, height: 40, alignItems: 'center', justifyContent: 'center', opacity: a.disabled ? 0.5 : 1 }}
|
|
300
|
+
>
|
|
301
|
+
<Icon
|
|
302
|
+
name={a.icon}
|
|
303
|
+
size={24}
|
|
304
|
+
color={a.destructive ? theme.colors.feedback.error : a.color ?? theme.colors.text.secondary}
|
|
305
|
+
/>
|
|
306
|
+
</Pressable>
|
|
307
|
+
))}
|
|
308
|
+
{overflow.length > 0 ? (
|
|
309
|
+
<MenuButton
|
|
310
|
+
icon={moreIcon}
|
|
311
|
+
accessibilityLabel="More actions"
|
|
312
|
+
variant="ghost"
|
|
313
|
+
size="sm"
|
|
314
|
+
menuWidth={actionsMenuWidth}
|
|
315
|
+
options={overflow.map(
|
|
316
|
+
(a): DropdownEntry => ({
|
|
317
|
+
key: a.key,
|
|
318
|
+
label: a.label,
|
|
319
|
+
disabled: a.disabled,
|
|
320
|
+
destructive: a.destructive,
|
|
321
|
+
leading: { kind: 'icon', name: a.icon, ...(a.destructive || !a.color ? {} : { color: a.color }) },
|
|
322
|
+
onPress: a.onPress,
|
|
323
|
+
}),
|
|
324
|
+
)}
|
|
325
|
+
/>
|
|
326
|
+
) : null}
|
|
327
|
+
</>
|
|
328
|
+
);
|
|
329
|
+
};
|
|
330
|
+
|
|
248
331
|
// ---------- Desktop ----------
|
|
249
332
|
const header = (
|
|
250
333
|
<View
|
|
@@ -347,8 +430,8 @@ export function Table<T>({
|
|
|
347
430
|
<Icon name="chevron-down" size={24} color="secondary" />
|
|
348
431
|
</View>
|
|
349
432
|
) : hasActions ? (
|
|
350
|
-
<View style={{ width:
|
|
351
|
-
{
|
|
433
|
+
<View style={{ width: actionsColWidth, flexShrink: 0, flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end', gap: 4 }}>
|
|
434
|
+
{renderActionsCell(row, index)}
|
|
352
435
|
</View>
|
|
353
436
|
) : null}
|
|
354
437
|
</Pressable>
|
|
@@ -418,7 +501,9 @@ export function Table<T>({
|
|
|
418
501
|
) : null}
|
|
419
502
|
</View>
|
|
420
503
|
{hasActions ? (
|
|
421
|
-
<View style={{ flexDirection: 'row', justifyContent: 'flex-end', gap: 4 }}>
|
|
504
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end', gap: 4 }}>
|
|
505
|
+
{renderActionsCell(row, index)}
|
|
506
|
+
</View>
|
|
422
507
|
) : null}
|
|
423
508
|
</Pressable>
|
|
424
509
|
{expandable && isOpen && renderExpanded ? (
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* True when the subtree is rendered inside a full-screen (statusBarTranslucent)
|
|
5
|
+
* Modal host — e.g. `Dialog`, or a `Menu` overlay. Anchored menus use this to
|
|
6
|
+
* skip the status-bar coordinate compensation: inside such a host, a trigger
|
|
7
|
+
* measured with `measureInWindow` is already in full-screen coordinates that
|
|
8
|
+
* match the menu's own Modal, so adding the status bar height would push the
|
|
9
|
+
* menu down (a visible gap). Outside a host, the compensation is still applied.
|
|
10
|
+
*/
|
|
11
|
+
export const ModalHostContext = createContext(false);
|
|
12
|
+
|
|
13
|
+
export function useInsideModalHost(): boolean {
|
|
14
|
+
return useContext(ModalHostContext);
|
|
15
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -16,11 +16,13 @@ export * from './IconButton';
|
|
|
16
16
|
export * from './Input';
|
|
17
17
|
export * from './Keypad';
|
|
18
18
|
export * from './LinkButton';
|
|
19
|
+
export * from './List';
|
|
19
20
|
export * from './Menu';
|
|
20
21
|
export * from './ProductCard';
|
|
21
22
|
export * from './ProgressBar';
|
|
22
23
|
export * from './QuantityStepper';
|
|
23
24
|
export * from './Radio';
|
|
25
|
+
export * from './SegmentedButton';
|
|
24
26
|
export * from './Select';
|
|
25
27
|
export * from './Sidebar';
|
|
26
28
|
export * from './Spinner';
|