@oxyhq/bloom 0.16.0 → 0.16.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/dialog/Dialog.js +45 -157
- package/lib/commonjs/dialog/Dialog.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.web.js +64 -198
- package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
- package/lib/commonjs/dialog/DialogBottomSheet.js +216 -0
- package/lib/commonjs/dialog/DialogBottomSheet.js.map +1 -0
- package/lib/commonjs/dialog/DialogContent.js +155 -0
- package/lib/commonjs/dialog/DialogContent.js.map +1 -0
- package/lib/commonjs/dialog/placement.js +12 -1
- package/lib/commonjs/dialog/placement.js.map +1 -1
- package/lib/module/dialog/Dialog.js +48 -160
- package/lib/module/dialog/Dialog.js.map +1 -1
- package/lib/module/dialog/Dialog.web.js +66 -200
- package/lib/module/dialog/Dialog.web.js.map +1 -1
- package/lib/module/dialog/DialogBottomSheet.js +211 -0
- package/lib/module/dialog/DialogBottomSheet.js.map +1 -0
- package/lib/module/dialog/DialogContent.js +148 -0
- package/lib/module/dialog/DialogContent.js.map +1 -0
- package/lib/module/dialog/placement.js +11 -0
- package/lib/module/dialog/placement.js.map +1 -1
- package/lib/typescript/commonjs/dialog/Dialog.d.ts +1 -1
- package/lib/typescript/commonjs/dialog/Dialog.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/Dialog.web.d.ts +11 -8
- package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/DialogBottomSheet.d.ts +29 -0
- package/lib/typescript/commonjs/dialog/DialogBottomSheet.d.ts.map +1 -0
- package/lib/typescript/commonjs/dialog/DialogContent.d.ts +32 -0
- package/lib/typescript/commonjs/dialog/DialogContent.d.ts.map +1 -0
- package/lib/typescript/commonjs/dialog/placement.d.ts +10 -0
- package/lib/typescript/commonjs/dialog/placement.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/types.d.ts +12 -0
- package/lib/typescript/commonjs/dialog/types.d.ts.map +1 -1
- package/lib/typescript/module/dialog/Dialog.d.ts +1 -1
- package/lib/typescript/module/dialog/Dialog.d.ts.map +1 -1
- package/lib/typescript/module/dialog/Dialog.web.d.ts +11 -8
- package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/module/dialog/DialogBottomSheet.d.ts +29 -0
- package/lib/typescript/module/dialog/DialogBottomSheet.d.ts.map +1 -0
- package/lib/typescript/module/dialog/DialogContent.d.ts +32 -0
- package/lib/typescript/module/dialog/DialogContent.d.ts.map +1 -0
- package/lib/typescript/module/dialog/placement.d.ts +10 -0
- package/lib/typescript/module/dialog/placement.d.ts.map +1 -1
- package/lib/typescript/module/dialog/types.d.ts +12 -0
- package/lib/typescript/module/dialog/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/BottomSheetGesture.test.tsx +135 -0
- package/src/__tests__/DialogBottomSheet.test.tsx +259 -0
- package/src/dialog/Dialog.tsx +41 -171
- package/src/dialog/Dialog.web.tsx +56 -211
- package/src/dialog/DialogBottomSheet.tsx +250 -0
- package/src/dialog/DialogContent.tsx +157 -0
- package/src/dialog/placement.ts +11 -0
- package/src/dialog/types.ts +12 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Text,
|
|
4
|
+
TouchableOpacity,
|
|
5
|
+
View,
|
|
6
|
+
type GestureResponderEvent,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
|
|
9
|
+
import type { ThemeColors } from '../theme/types';
|
|
10
|
+
import { useTheme } from '../theme/use-theme';
|
|
11
|
+
import { useDialogContext } from './context';
|
|
12
|
+
import type { DialogAction, DialogActionColor } from './types';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Shared dialog content primitives used by every `<Dialog>` placement on both
|
|
16
|
+
* platforms — the centered panel, the side-sheets, and the `BottomSheet`-backed
|
|
17
|
+
* bottom placement. Keeping them here is the single source of truth for the
|
|
18
|
+
* declarative `title` / `description` / `actions` chrome, so the surfaces never
|
|
19
|
+
* drift in spacing, typography, or action-button behaviour.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Renders the dialog's body: optional declarative title + description, any
|
|
24
|
+
* `children`, then the action row. The `titleId`/`descriptionId` are wired by
|
|
25
|
+
* the caller onto the dialog role element for `aria-labelledby` /
|
|
26
|
+
* `aria-describedby`.
|
|
27
|
+
*/
|
|
28
|
+
export function DialogBody({
|
|
29
|
+
titleId,
|
|
30
|
+
descriptionId,
|
|
31
|
+
title,
|
|
32
|
+
description,
|
|
33
|
+
actions,
|
|
34
|
+
children,
|
|
35
|
+
}: {
|
|
36
|
+
titleId: string;
|
|
37
|
+
descriptionId: string;
|
|
38
|
+
title?: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
actions?: DialogAction[];
|
|
41
|
+
children?: React.ReactNode;
|
|
42
|
+
}) {
|
|
43
|
+
const theme = useTheme();
|
|
44
|
+
return (
|
|
45
|
+
<>
|
|
46
|
+
{title ? (
|
|
47
|
+
<Text
|
|
48
|
+
nativeID={titleId}
|
|
49
|
+
style={{
|
|
50
|
+
fontSize: 22,
|
|
51
|
+
fontWeight: '600',
|
|
52
|
+
color: theme.colors.text,
|
|
53
|
+
paddingBottom: description ? 4 : 16,
|
|
54
|
+
lineHeight: 30,
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
{title}
|
|
58
|
+
</Text>
|
|
59
|
+
) : null}
|
|
60
|
+
{description ? (
|
|
61
|
+
<Text
|
|
62
|
+
nativeID={descriptionId}
|
|
63
|
+
style={{
|
|
64
|
+
fontSize: 16,
|
|
65
|
+
color: theme.colors.textSecondary,
|
|
66
|
+
paddingBottom: 16,
|
|
67
|
+
lineHeight: 22,
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
{description}
|
|
71
|
+
</Text>
|
|
72
|
+
) : null}
|
|
73
|
+
{children}
|
|
74
|
+
{actions && actions.length > 0 ? <ActionRow actions={actions} /> : null}
|
|
75
|
+
</>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function ActionRow({ actions }: { actions: DialogAction[] }) {
|
|
80
|
+
return (
|
|
81
|
+
<View style={{ width: '100%', gap: 8, justifyContent: 'flex-end' }}>
|
|
82
|
+
{actions.map((action, idx) => (
|
|
83
|
+
<ActionButton key={`${action.label}-${idx}`} action={action} />
|
|
84
|
+
))}
|
|
85
|
+
</View>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function ActionButton({ action }: { action: DialogAction }) {
|
|
90
|
+
const { close } = useDialogContext();
|
|
91
|
+
const theme = useTheme();
|
|
92
|
+
const color: DialogActionColor = action.color ?? 'default';
|
|
93
|
+
const shouldCloseOnPress = action.shouldCloseOnPress ?? true;
|
|
94
|
+
|
|
95
|
+
const { background, foreground } = getActionPalette(color, theme.colors);
|
|
96
|
+
|
|
97
|
+
const handlePress = useCallback(
|
|
98
|
+
(e: GestureResponderEvent) => {
|
|
99
|
+
const onPress = action.onPress;
|
|
100
|
+
if (color === 'cancel') {
|
|
101
|
+
// Cancel always dismisses; consumer's onPress (rare) runs after.
|
|
102
|
+
close(onPress ? () => onPress(e) : undefined);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (shouldCloseOnPress) {
|
|
106
|
+
close(onPress ? () => onPress(e) : undefined);
|
|
107
|
+
} else {
|
|
108
|
+
onPress?.(e);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
[action.onPress, close, color, shouldCloseOnPress],
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<TouchableOpacity
|
|
116
|
+
style={{
|
|
117
|
+
borderRadius: 9999,
|
|
118
|
+
alignItems: 'center',
|
|
119
|
+
justifyContent: 'center',
|
|
120
|
+
backgroundColor: background,
|
|
121
|
+
opacity: action.disabled ? 0.5 : 1,
|
|
122
|
+
paddingVertical: 12,
|
|
123
|
+
paddingHorizontal: 24,
|
|
124
|
+
}}
|
|
125
|
+
onPress={handlePress}
|
|
126
|
+
disabled={action.disabled}
|
|
127
|
+
activeOpacity={0.7}
|
|
128
|
+
testID={action.testID}
|
|
129
|
+
>
|
|
130
|
+
<Text style={{ fontSize: 16, fontWeight: '500', color: foreground }}>
|
|
131
|
+
{action.label}
|
|
132
|
+
</Text>
|
|
133
|
+
</TouchableOpacity>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function getActionPalette(
|
|
138
|
+
color: DialogActionColor,
|
|
139
|
+
colors: ThemeColors,
|
|
140
|
+
): { background: string; foreground: string } {
|
|
141
|
+
switch (color) {
|
|
142
|
+
case 'destructive':
|
|
143
|
+
return {
|
|
144
|
+
background: colors.negative,
|
|
145
|
+
foreground: colors.negativeForeground,
|
|
146
|
+
};
|
|
147
|
+
case 'cancel':
|
|
148
|
+
return { background: colors.contrast50, foreground: colors.text };
|
|
149
|
+
case 'default':
|
|
150
|
+
return { background: colors.primary, foreground: colors.primaryForeground };
|
|
151
|
+
/* c8 ignore next 3 -- TS exhaustiveness check guards this branch */
|
|
152
|
+
default: {
|
|
153
|
+
const _exhaustive: never = color;
|
|
154
|
+
return { background: colors.primary, foreground: colors.primaryForeground };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
package/src/dialog/placement.ts
CHANGED
|
@@ -60,6 +60,17 @@ export const DEFAULT_MAX_HEIGHT_RATIO = 0.9;
|
|
|
60
60
|
/** Corner radius shared by the side-sheet panel and the bottom-sheet top. */
|
|
61
61
|
export const PANEL_RADIUS = 20;
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Default inner padding (px) of the dialog content container, applied uniformly
|
|
65
|
+
* across every placement (centered panel, side-sheet body, bottom-sheet body)
|
|
66
|
+
* so the chrome reads identically regardless of which surface resolves. This is
|
|
67
|
+
* the legacy value — omitting the `contentPadding` prop keeps 20px everywhere,
|
|
68
|
+
* byte-for-byte unchanged. A host that owns its own insets (e.g. custom
|
|
69
|
+
* `children` that already manage padding) can override it via
|
|
70
|
+
* `Dialog`'s `contentPadding` prop (e.g. `contentPadding={0}`).
|
|
71
|
+
*/
|
|
72
|
+
export const DEFAULT_DIALOG_CONTENT_PADDING = 20;
|
|
73
|
+
|
|
63
74
|
/** Open/close transition duration (ms). ~280ms ease-out reads smooth. */
|
|
64
75
|
export const ANIMATION_DURATION = 280;
|
|
65
76
|
|
package/src/dialog/types.ts
CHANGED
|
@@ -134,6 +134,18 @@ export type DialogProps = React.PropsWithChildren<{
|
|
|
134
134
|
inset?: DialogInset;
|
|
135
135
|
/** Whether to render the drag handle in bottom-sheet mode. Defaults to `true`. */
|
|
136
136
|
showHandle?: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Inner padding (px) of the dialog content container, applied uniformly across
|
|
139
|
+
* every placement (centered panel, side-sheet body, bottom-sheet body).
|
|
140
|
+
* Defaults to `20`.
|
|
141
|
+
*
|
|
142
|
+
* The default chrome padding is correct for the declarative
|
|
143
|
+
* `title`/`description`/`actions` layout, but a host passing pure custom
|
|
144
|
+
* `children` that already own their insets can set `contentPadding={0}` to
|
|
145
|
+
* render flush content and manage padding itself. Omitting the prop keeps the
|
|
146
|
+
* 20px default on every surface.
|
|
147
|
+
*/
|
|
148
|
+
contentPadding?: number;
|
|
137
149
|
/** Whether tapping the backdrop dismisses the dialog. Defaults to `true`. */
|
|
138
150
|
dismissOnBackdrop?: boolean;
|
|
139
151
|
/**
|