@klyftig/mobile-ui 1.3.4 → 1.3.6
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/package.json +1 -1
- package/src/components/CenterDialog.tsx +88 -0
- package/src/components/FAB.tsx +11 -6
- package/src/components/ScreenHeader.tsx +10 -2
- package/src/index.ts +2 -0
package/package.json
CHANGED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Modal,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
KeyboardAvoidingView,
|
|
8
|
+
Platform,
|
|
9
|
+
StyleProp,
|
|
10
|
+
ViewStyle,
|
|
11
|
+
} from 'react-native';
|
|
12
|
+
import { useTheme } from '../hooks/useTheme';
|
|
13
|
+
|
|
14
|
+
export interface CenterDialogProps {
|
|
15
|
+
visible: boolean;
|
|
16
|
+
/** Called on backdrop tap and hardware back. */
|
|
17
|
+
onDismiss: () => void;
|
|
18
|
+
children: React.ReactNode;
|
|
19
|
+
/** Wrap the overlay in a KeyboardAvoidingView — use when the dialog hosts text inputs. */
|
|
20
|
+
avoidKeyboard?: boolean;
|
|
21
|
+
/** Disable tap-outside-to-dismiss. Hardware back still calls onDismiss. */
|
|
22
|
+
dismissable?: boolean;
|
|
23
|
+
/** Accessibility label for the tap-to-dismiss backdrop. */
|
|
24
|
+
backdropAccessibilityLabel?: string;
|
|
25
|
+
/** Styles merged into the white card (e.g. `{ alignItems: 'center' }`). */
|
|
26
|
+
contentStyle?: StyleProp<ViewStyle>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Centered alert-style modal: dimmed scrim with tap-to-dismiss + a rounded white card.
|
|
31
|
+
* Callers own the card's content; this only provides the shared shell.
|
|
32
|
+
*/
|
|
33
|
+
export function CenterDialog({
|
|
34
|
+
visible,
|
|
35
|
+
onDismiss,
|
|
36
|
+
children,
|
|
37
|
+
avoidKeyboard = false,
|
|
38
|
+
dismissable = true,
|
|
39
|
+
backdropAccessibilityLabel,
|
|
40
|
+
contentStyle,
|
|
41
|
+
}: CenterDialogProps) {
|
|
42
|
+
const { colors } = useTheme();
|
|
43
|
+
const Overlay = avoidKeyboard ? KeyboardAvoidingView : View;
|
|
44
|
+
const overlayProps = avoidKeyboard
|
|
45
|
+
? { behavior: Platform.OS === 'ios' ? ('padding' as const) : undefined }
|
|
46
|
+
: {};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Modal visible={visible} transparent animationType="fade" onRequestClose={onDismiss}>
|
|
50
|
+
<Overlay style={styles.overlay} {...overlayProps}>
|
|
51
|
+
<TouchableOpacity
|
|
52
|
+
style={styles.backdrop}
|
|
53
|
+
activeOpacity={1}
|
|
54
|
+
onPress={dismissable ? onDismiss : undefined}
|
|
55
|
+
disabled={!dismissable}
|
|
56
|
+
accessibilityRole="button"
|
|
57
|
+
accessibilityLabel={backdropAccessibilityLabel}
|
|
58
|
+
/>
|
|
59
|
+
<View style={[styles.content, { backgroundColor: colors.background.card }, contentStyle]}>
|
|
60
|
+
{children}
|
|
61
|
+
</View>
|
|
62
|
+
</Overlay>
|
|
63
|
+
</Modal>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const styles = StyleSheet.create({
|
|
68
|
+
overlay: {
|
|
69
|
+
flex: 1,
|
|
70
|
+
justifyContent: 'center',
|
|
71
|
+
alignItems: 'center',
|
|
72
|
+
},
|
|
73
|
+
backdrop: {
|
|
74
|
+
...StyleSheet.absoluteFillObject,
|
|
75
|
+
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
76
|
+
},
|
|
77
|
+
content: {
|
|
78
|
+
borderRadius: 20,
|
|
79
|
+
padding: 24,
|
|
80
|
+
width: '85%',
|
|
81
|
+
maxWidth: 360,
|
|
82
|
+
shadowColor: '#000',
|
|
83
|
+
shadowOffset: { width: 0, height: 4 },
|
|
84
|
+
shadowOpacity: 0.15,
|
|
85
|
+
shadowRadius: 12,
|
|
86
|
+
elevation: 8,
|
|
87
|
+
},
|
|
88
|
+
});
|
package/src/components/FAB.tsx
CHANGED
|
@@ -56,17 +56,22 @@ interface FABProps {
|
|
|
56
56
|
overrideIcon?: string;
|
|
57
57
|
/** If set, tapping the main button calls this directly instead of toggling the menu */
|
|
58
58
|
overrideOnPress?: () => void;
|
|
59
|
+
/** Diameter of the main button. Defaults to FAB_SIZE (50). */
|
|
60
|
+
size?: number;
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
export const FAB_SIZE = 50;
|
|
62
64
|
const SUB_BUTTON_SIZE = 44;
|
|
63
65
|
const SUB_BUTTON_SPACING = 58;
|
|
66
|
+
// Main icon ≈ 48% of the button diameter (24 at the default 50px).
|
|
67
|
+
const ICON_RATIO = 0.48;
|
|
64
68
|
|
|
65
69
|
// Fallback colors for sub-buttons when no color is specified
|
|
66
70
|
const ACTION_COLORS = ['#10b981', '#f59e0b', '#0ea5e9', '#8b5cf6', '#ec4899'];
|
|
67
71
|
|
|
68
|
-
export const FAB: React.FC<FABProps> = ({ actions, overrideIcon, overrideOnPress }) => {
|
|
72
|
+
export const FAB: React.FC<FABProps> = ({ actions, overrideIcon, overrideOnPress, size = FAB_SIZE }) => {
|
|
69
73
|
const insets = useSafeAreaInsets();
|
|
74
|
+
const iconSize = Math.round(size * ICON_RATIO);
|
|
70
75
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
71
76
|
const expandAnim = useRef(new Animated.Value(0)).current;
|
|
72
77
|
|
|
@@ -124,8 +129,8 @@ export const FAB: React.FC<FABProps> = ({ actions, overrideIcon, overrideOnPress
|
|
|
124
129
|
style={[
|
|
125
130
|
styles.subButtonRow,
|
|
126
131
|
{
|
|
127
|
-
bottom: fabBottom + (
|
|
128
|
-
right: 16 + (
|
|
132
|
+
bottom: fabBottom + (size - SUB_BUTTON_SIZE) / 2,
|
|
133
|
+
right: 16 + (size - SUB_BUTTON_SIZE) / 2,
|
|
129
134
|
transform: [{ translateY }, { scale: subButtonScale }],
|
|
130
135
|
opacity: subButtonOpacity,
|
|
131
136
|
},
|
|
@@ -157,11 +162,11 @@ export const FAB: React.FC<FABProps> = ({ actions, overrideIcon, overrideOnPress
|
|
|
157
162
|
<AdaptiveGlassView
|
|
158
163
|
variant="clear"
|
|
159
164
|
interactive={true}
|
|
160
|
-
style={[styles.fabGlass, { bottom: fabBottom, right: 16 }]}
|
|
165
|
+
style={[styles.fabGlass, { bottom: fabBottom, right: 16, width: size, height: size, borderRadius: size / 2 }]}
|
|
161
166
|
fallbackStyle={styles.fabFallback}
|
|
162
167
|
>
|
|
163
168
|
<TouchableOpacity
|
|
164
|
-
style={styles.fabTouchable}
|
|
169
|
+
style={[styles.fabTouchable, { borderRadius: size / 2 }]}
|
|
165
170
|
onPress={overrideOnPress ?? toggle}
|
|
166
171
|
activeOpacity={0.85}
|
|
167
172
|
accessibilityRole="button"
|
|
@@ -170,7 +175,7 @@ export const FAB: React.FC<FABProps> = ({ actions, overrideIcon, overrideOnPress
|
|
|
170
175
|
>
|
|
171
176
|
<Icon
|
|
172
177
|
name={overrideIcon ?? (isExpanded ? 'close' : 'plus')}
|
|
173
|
-
size={
|
|
178
|
+
size={iconSize}
|
|
174
179
|
color={GLASS_AVAILABLE ? '#1A1A1A' : '#FFFFFF'}
|
|
175
180
|
/>
|
|
176
181
|
</TouchableOpacity>
|
|
@@ -46,7 +46,8 @@ export function ScreenHeader({
|
|
|
46
46
|
onPress={onLeftPress}
|
|
47
47
|
accessibilityLabel={leftAccessibilityLabel}
|
|
48
48
|
accessibilityRole="button"
|
|
49
|
-
|
|
49
|
+
style={styles.leftButton}
|
|
50
|
+
hitSlop={12}
|
|
50
51
|
>
|
|
51
52
|
<Icon name={leftIcon as any} size={26} color={colors.icon.primary} />
|
|
52
53
|
</TouchableOpacity>
|
|
@@ -86,10 +87,17 @@ const styles = StyleSheet.create({
|
|
|
86
87
|
marginBottom: 8,
|
|
87
88
|
},
|
|
88
89
|
side: {
|
|
89
|
-
width:
|
|
90
|
+
width: 44,
|
|
90
91
|
alignItems: 'center',
|
|
91
92
|
justifyContent: 'center',
|
|
92
93
|
},
|
|
94
|
+
leftButton: {
|
|
95
|
+
width: 44,
|
|
96
|
+
height: 44,
|
|
97
|
+
alignItems: 'center',
|
|
98
|
+
justifyContent: 'center',
|
|
99
|
+
marginLeft: -9,
|
|
100
|
+
},
|
|
93
101
|
center: {
|
|
94
102
|
flex: 1,
|
|
95
103
|
alignItems: 'center',
|
package/src/index.ts
CHANGED
|
@@ -44,6 +44,8 @@ export { WheelPicker } from './components/WheelPicker';
|
|
|
44
44
|
export type { WheelPickerProps } from './components/WheelPicker';
|
|
45
45
|
export { DisclaimerPill } from './components/DisclaimerPill';
|
|
46
46
|
export type { DisclaimerPillProps } from './components/DisclaimerPill';
|
|
47
|
+
export { CenterDialog } from './components/CenterDialog';
|
|
48
|
+
export type { CenterDialogProps } from './components/CenterDialog';
|
|
47
49
|
|
|
48
50
|
// Progress
|
|
49
51
|
export { CircularProgress } from './components/progress/CircularProgress';
|