@oneplatformdev/react-native-uikit 0.0.0-stage → 1.1.1

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 (68) hide show
  1. package/README.md +376 -2
  2. package/bin/cli.cjs +372 -0
  3. package/package.json +34 -4
  4. package/registry/manifest.json +113 -0
  5. package/src/components/BottomModal/index.ts +2 -0
  6. package/src/components/BottomModal/presenters/useBottomModal.ts +114 -0
  7. package/src/components/BottomModal/presenters/useBottomModalInsets.ts +19 -0
  8. package/src/components/BottomModal/types/types.ts +23 -0
  9. package/src/components/BottomModal/ui/BottomModal.tsx +110 -0
  10. package/src/components/BottomModal/ui/styles.ts +115 -0
  11. package/src/components/Button/index.ts +8 -0
  12. package/src/components/Button/presenters/useButton.ts +72 -0
  13. package/src/components/Button/types/types.ts +39 -0
  14. package/src/components/Button/ui/Button.tsx +121 -0
  15. package/src/components/Button/ui/styles.ts +82 -0
  16. package/src/components/ConnectionContainer/index.ts +2 -0
  17. package/src/components/ConnectionContainer/presenters/useConnectionContainer.ts +77 -0
  18. package/src/components/ConnectionContainer/types/types.ts +14 -0
  19. package/src/components/ConnectionContainer/ui/ConnectionContainer.tsx +51 -0
  20. package/src/components/ConnectionContainer/ui/styles.ts +35 -0
  21. package/src/components/CustomAlert/index.ts +2 -0
  22. package/src/components/CustomAlert/presenters/useCustomAlert.ts +24 -0
  23. package/src/components/CustomAlert/types/types.ts +21 -0
  24. package/src/components/CustomAlert/ui/CustomAlert.tsx +79 -0
  25. package/src/components/CustomAlert/ui/styles.ts +71 -0
  26. package/src/components/HeaderWithBackButton/index.ts +2 -0
  27. package/src/components/HeaderWithBackButton/presenters/useHeaderWithBackButton.ts +17 -0
  28. package/src/components/HeaderWithBackButton/types/types.ts +22 -0
  29. package/src/components/HeaderWithBackButton/ui/HeaderWithBackButton.tsx +91 -0
  30. package/src/components/HeaderWithBackButton/ui/styles.ts +58 -0
  31. package/src/components/Input/index.ts +8 -0
  32. package/src/components/Input/presenters/useInput.ts +104 -0
  33. package/src/components/Input/types/types.ts +36 -0
  34. package/src/components/Input/ui/Input.tsx +161 -0
  35. package/src/components/Input/ui/styles.ts +80 -0
  36. package/src/components/Loader/index.ts +2 -0
  37. package/src/components/Loader/types/types.ts +12 -0
  38. package/src/components/Loader/ui/Loader.tsx +29 -0
  39. package/src/components/Loader/ui/styles.ts +14 -0
  40. package/src/components/Logger/index.ts +2 -0
  41. package/src/components/Logger/presenters/useLogger.ts +13 -0
  42. package/src/components/Logger/types/types.ts +56 -0
  43. package/src/components/Logger/ui/Logger.tsx +85 -0
  44. package/src/components/Logger/ui/components/LoggerItem.tsx +117 -0
  45. package/src/components/Logger/ui/styles.ts +121 -0
  46. package/src/components/Logger/utils/groupLoggerEntries.ts +40 -0
  47. package/src/components/Logger/utils/loggerPresentation.ts +69 -0
  48. package/src/components/ScreenContainer/index.ts +2 -0
  49. package/src/components/ScreenContainer/presenters/useScreenContainer.ts +12 -0
  50. package/src/components/ScreenContainer/types/types.ts +19 -0
  51. package/src/components/ScreenContainer/ui/ScreenContainer.tsx +83 -0
  52. package/src/components/ScreenContainer/ui/styles.ts +31 -0
  53. package/src/components/Typography/index.ts +2 -0
  54. package/src/components/Typography/types/types.ts +14 -0
  55. package/src/components/Typography/ui/Typography.tsx +37 -0
  56. package/src/components/Typography/ui/styles.ts +26 -0
  57. package/src/index.ts +13 -0
  58. package/src/provider/LocalizationContext.tsx +14 -0
  59. package/src/provider/ModeContext.tsx +9 -0
  60. package/src/provider/index.ts +12 -0
  61. package/src/provider/types.ts +31 -0
  62. package/src/theme/ThemeProvider.tsx +7 -0
  63. package/src/theme/UIProvider.tsx +60 -0
  64. package/src/theme/createTheme.ts +66 -0
  65. package/src/theme/defaultTheme.ts +215 -0
  66. package/src/theme/index.ts +4 -0
  67. package/src/theme/types.ts +265 -0
  68. package/src/utils/index.ts +94 -0
@@ -0,0 +1,51 @@
1
+ import React, { useMemo } from 'react';
2
+ import { TouchableOpacity, View } from 'react-native';
3
+ import { SafeAreaView } from 'react-native-safe-area-context';
4
+ import { Typography } from '../../Typography';
5
+ import { useUITheme } from '../../../theme';
6
+ import { useScaling } from '../../../utils';
7
+ import { getStyles } from './styles';
8
+ import type { ConnectionContainerProps } from '../types/types';
9
+ import { useConnectionContainer } from '../presenters/useConnectionContainer';
10
+
11
+ export const ConnectionContainer = ({
12
+ isConnected,
13
+ disconnectedText,
14
+ reconnectedText,
15
+ children,
16
+ disconnectedIcon,
17
+ reconnectedIcon,
18
+ dismissAccessibilityHint,
19
+ style,
20
+ }: ConnectionContainerProps) => {
21
+ const { colors, spacing } = useUITheme();
22
+ const scaling = useScaling();
23
+ const styles = useMemo(() => getStyles(colors, spacing, scaling), [colors, scaling, spacing]);
24
+ const { visible, isReconnected, text, onHideBanner } = useConnectionContainer({
25
+ isConnected,
26
+ disconnectedText,
27
+ reconnectedText,
28
+ });
29
+
30
+ if (!visible) return children == null ? null : <>{children}</>;
31
+
32
+ return (
33
+ <>
34
+ {children}
35
+ <SafeAreaView edges={['top', 'left', 'right']} pointerEvents="box-none" style={[styles.container, style]}>
36
+ <TouchableOpacity
37
+ accessibilityRole="button"
38
+ accessibilityLabel={text}
39
+ accessibilityHint={dismissAccessibilityHint}
40
+ activeOpacity={0.8}
41
+ onPress={onHideBanner}
42
+ >
43
+ <View style={[styles.content, isReconnected ? styles.reconnected : styles.disconnected]}>
44
+ {isReconnected ? reconnectedIcon : disconnectedIcon}
45
+ <Typography text={text} variant="label" weight="bold" style={styles.text} accessibilityLiveRegion="polite" />
46
+ </View>
47
+ </TouchableOpacity>
48
+ </SafeAreaView>
49
+ </>
50
+ );
51
+ };
@@ -0,0 +1,35 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import type { UIColors, UISpacing } from '../../../theme';
3
+ import type { ScalingFunctions } from '../../../utils';
4
+
5
+ export const getStyles = (colors: UIColors, spacing: UISpacing, scaling: ScalingFunctions) => {
6
+ const styles = StyleSheet.create({
7
+ container: {
8
+ position: 'absolute',
9
+ top: 0,
10
+ left: 0,
11
+ right: 0,
12
+ zIndex: 10,
13
+ },
14
+ reconnected: {
15
+ backgroundColor: colors.success,
16
+ },
17
+ disconnected: {
18
+ backgroundColor: colors.error,
19
+ },
20
+ content: {
21
+ flexDirection: 'row',
22
+ alignItems: 'center',
23
+ justifyContent: 'center',
24
+ gap: scaling.scaleHorizontal(spacing.sm),
25
+ paddingVertical: scaling.scaleVertical(spacing.md),
26
+ paddingHorizontal: scaling.scaleHorizontal(spacing.lg),
27
+ },
28
+ text: {
29
+ flexShrink: 1,
30
+ color: colors.onPrimary,
31
+ },
32
+ });
33
+
34
+ return styles;
35
+ };
@@ -0,0 +1,2 @@
1
+ export { CustomAlert } from './ui/CustomAlert';
2
+ export type { CustomAlertCloseAction, CustomAlertProps } from './types/types';
@@ -0,0 +1,24 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { Animated } from 'react-native';
3
+ import type { CustomAlertProps } from '../types/types';
4
+
5
+ export const useCustomAlert = ({ visible, onClose }: Pick<CustomAlertProps, 'visible' | 'onClose'>) => {
6
+ const [isVisible, setIsVisible] = useState(visible);
7
+ const backdropOpacity = useRef(new Animated.Value(0)).current;
8
+ const scaleAnim = useRef(new Animated.Value(0.8)).current;
9
+ const onClosePress = useCallback(() => onClose(), [onClose]);
10
+
11
+ useEffect(() => {
12
+ if (visible) setIsVisible(true);
13
+ const animation = Animated.parallel([
14
+ Animated.timing(backdropOpacity, { toValue: visible ? 1 : 0, duration: 200, useNativeDriver: true }),
15
+ Animated.timing(scaleAnim, { toValue: visible ? 1 : 0.8, duration: 200, useNativeDriver: true }),
16
+ ]);
17
+ animation.start(({ finished }) => {
18
+ if (finished && !visible) setIsVisible(false);
19
+ });
20
+ return () => animation.stop();
21
+ }, [visible, backdropOpacity, scaleAnim]);
22
+
23
+ return { isVisible, backdropOpacity, scaleAnim, onClosePress };
24
+ };
@@ -0,0 +1,21 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { StyleProp, ViewStyle } from 'react-native';
3
+
4
+ export interface CustomAlertCloseAction {
5
+ icon: ReactNode;
6
+ accessibilityLabel: string;
7
+ accessibilityHint?: string;
8
+ }
9
+
10
+ export interface CustomAlertProps {
11
+ visible: boolean;
12
+ onClose: () => void;
13
+ header?: ReactNode;
14
+ children?: ReactNode;
15
+ footer?: ReactNode;
16
+ showHandle?: boolean;
17
+ isKeyboardAvoiding?: boolean;
18
+ closeAction?: CustomAlertCloseAction;
19
+ style?: StyleProp<ViewStyle>;
20
+ contentStyle?: StyleProp<ViewStyle>;
21
+ }
@@ -0,0 +1,79 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Animated, Modal, Pressable, ScrollView, View } from 'react-native';
3
+ import { KeyboardAvoidingView } from 'react-native-keyboard-controller';
4
+ import { Typography } from '../../Typography';
5
+ import { useUITheme } from '../../../theme';
6
+ import { useScaling } from '../../../utils';
7
+ import { useCustomAlert } from '../presenters/useCustomAlert';
8
+ import { getAlertAnimatedStyles, getStyles } from './styles';
9
+ import type { CustomAlertProps } from '../types/types';
10
+
11
+ export const CustomAlert = ({
12
+ visible,
13
+ onClose,
14
+ header,
15
+ children,
16
+ footer,
17
+ showHandle = false,
18
+ isKeyboardAvoiding = false,
19
+ closeAction,
20
+ style,
21
+ contentStyle,
22
+ }: CustomAlertProps) => {
23
+ const { colors, spacing, radius } = useUITheme();
24
+ const scaling = useScaling();
25
+ const styles = useMemo(() => getStyles(colors, spacing, radius, scaling), [colors, radius, scaling, spacing]);
26
+ const { isVisible, backdropOpacity, scaleAnim, onClosePress } = useCustomAlert({ visible, onClose });
27
+ const { backdropStyle, alertStyle } = useMemo(
28
+ () => getAlertAnimatedStyles(backdropOpacity, scaleAnim),
29
+ [backdropOpacity, scaleAnim],
30
+ );
31
+
32
+ if (!isVisible) return null;
33
+
34
+ return (
35
+ <Modal visible={isVisible} transparent animationType="none" statusBarTranslucent onRequestClose={onClosePress}>
36
+ <KeyboardAvoidingView enabled={isKeyboardAvoiding} behavior="padding" style={styles.modalContainer}>
37
+ <Animated.View pointerEvents="none" style={[styles.backdrop, backdropStyle]} />
38
+ <Pressable
39
+ accessible={false}
40
+ importantForAccessibility="no"
41
+ onPress={onClosePress}
42
+ style={styles.backdropTouch}
43
+ />
44
+
45
+ <Animated.View
46
+ accessibilityViewIsModal
47
+ onAccessibilityEscape={onClosePress}
48
+ style={[styles.alertContainer, alertStyle, style]}
49
+ >
50
+ <ScrollView bounces={false} showsVerticalScrollIndicator={false} keyboardShouldPersistTaps="handled">
51
+ {showHandle ? <View style={styles.handle} /> : null}
52
+ {header != null || closeAction ? (
53
+ <View style={styles.header}>
54
+ <View style={styles.headerRow}>
55
+ {typeof header === 'string' ? (
56
+ <Typography text={header} variant="title" style={styles.headerText} />
57
+ ) : header}
58
+ {closeAction ? (
59
+ <Pressable
60
+ accessibilityRole="button"
61
+ accessibilityLabel={closeAction.accessibilityLabel}
62
+ accessibilityHint={closeAction.accessibilityHint}
63
+ onPress={onClosePress}
64
+ style={styles.closeButton}
65
+ >
66
+ {closeAction.icon}
67
+ </Pressable>
68
+ ) : null}
69
+ </View>
70
+ </View>
71
+ ) : null}
72
+ {children != null ? <View style={[styles.content, contentStyle]}>{children}</View> : null}
73
+ {footer != null ? <View style={styles.footer}>{footer}</View> : null}
74
+ </ScrollView>
75
+ </Animated.View>
76
+ </KeyboardAvoidingView>
77
+ </Modal>
78
+ );
79
+ };
@@ -0,0 +1,71 @@
1
+ import { Animated, StyleSheet } from 'react-native';
2
+ import type { UIColors, UIRadius, UISpacing } from '../../../theme';
3
+ import type { ScalingFunctions } from '../../../utils';
4
+
5
+ export const getStyles = (colors: UIColors, spacing: UISpacing, radius: UIRadius, scaling: ScalingFunctions) => {
6
+ const styles = StyleSheet.create({
7
+ modalContainer: {
8
+ flex: 1,
9
+ justifyContent: 'center',
10
+ alignItems: 'center',
11
+ },
12
+ backdrop: {
13
+ ...StyleSheet.absoluteFill,
14
+ backgroundColor: colors.text,
15
+ },
16
+ backdropTouch: {
17
+ ...StyleSheet.absoluteFill,
18
+ },
19
+ alertContainer: {
20
+ width: '90%',
21
+ maxHeight: '85%',
22
+ backgroundColor: colors.background,
23
+ borderRadius: scaling.scaleHorizontal(radius.lg),
24
+ borderWidth: scaling.scaleHorizontal(1),
25
+ borderColor: colors.border,
26
+ overflow: 'hidden',
27
+ },
28
+ handle: {
29
+ width: scaling.scaleHorizontal(56),
30
+ height: scaling.scaleVertical(5),
31
+ borderRadius: scaling.scaleHorizontal(radius.full),
32
+ backgroundColor: colors.border,
33
+ alignSelf: 'center',
34
+ marginTop: scaling.scaleVertical(spacing.md),
35
+ },
36
+ header: {
37
+ paddingHorizontal: scaling.scaleHorizontal(spacing.lg),
38
+ paddingVertical: scaling.scaleVertical(spacing.md),
39
+ },
40
+ headerRow: {
41
+ minHeight: scaling.scaleVertical(28),
42
+ alignItems: 'center',
43
+ justifyContent: 'center',
44
+ },
45
+ headerText: {
46
+ paddingHorizontal: scaling.scaleHorizontal(36),
47
+ textAlign: 'center',
48
+ },
49
+ closeButton: {
50
+ position: 'absolute',
51
+ top: 0,
52
+ right: 0,
53
+ padding: scaling.scaleHorizontal(spacing.xs),
54
+ },
55
+ content: {
56
+ paddingHorizontal: scaling.scaleHorizontal(spacing.lg),
57
+ },
58
+ footer: {
59
+ paddingHorizontal: scaling.scaleHorizontal(spacing.lg),
60
+ paddingTop: scaling.scaleVertical(spacing.md),
61
+ paddingBottom: scaling.scaleVertical(spacing.xl),
62
+ },
63
+ });
64
+
65
+ return styles;
66
+ };
67
+
68
+ export const getAlertAnimatedStyles = (backdropOpacity: Animated.Value, scaleAnim: Animated.Value) => ({
69
+ backdropStyle: { opacity: backdropOpacity.interpolate({ inputRange: [0, 1], outputRange: [0, 0.5] }) },
70
+ alertStyle: { opacity: backdropOpacity, transform: [{ scale: scaleAnim }] },
71
+ });
@@ -0,0 +1,2 @@
1
+ export { HeaderWithBackButton } from './ui/HeaderWithBackButton';
2
+ export type { HeaderWithBackButtonProps } from './types/types';
@@ -0,0 +1,17 @@
1
+ import { useState } from 'react';
2
+ import type { HeaderWithBackButtonProps } from '../types/types';
3
+
4
+ export const useHeaderWithBackButton = (titleAlign: HeaderWithBackButtonProps['titleAlign']) => {
5
+ const [leftWidth, setLeftWidth] = useState<number>();
6
+ const [rightWidth, setRightWidth] = useState<number>();
7
+ const centered = titleAlign === 'center';
8
+
9
+ return {
10
+ centered,
11
+ leftWidth,
12
+ rightWidth,
13
+ setLeftWidth,
14
+ setRightWidth,
15
+ sideWidth: Math.max(leftWidth ?? 0, rightWidth ?? 0),
16
+ };
17
+ };
@@ -0,0 +1,22 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { StyleProp, ViewStyle } from 'react-native';
3
+ import type { TypographyProps } from '../../Typography';
4
+
5
+ export interface HeaderWithBackButtonProps {
6
+ title: string | number;
7
+ subtitle?: string;
8
+ onBackPress: () => void;
9
+ backIcon: ReactNode;
10
+ backAccessibilityLabel: string;
11
+ backAccessibilityHint?: string;
12
+ backDisabled?: boolean;
13
+ rightAccessory?: ReactNode;
14
+ titleAlign?: 'left' | 'center';
15
+ titleVariant?: TypographyProps['variant'];
16
+ titleSize?: TypographyProps['size'];
17
+ subtitleVariant?: TypographyProps['variant'];
18
+ subtitleSize?: TypographyProps['size'];
19
+ subtitleNumberOfLines?: number;
20
+ style?: StyleProp<ViewStyle>;
21
+ contentStyle?: StyleProp<ViewStyle>;
22
+ }
@@ -0,0 +1,91 @@
1
+ import React, { useMemo } from 'react';
2
+ import { TouchableOpacity, View } from 'react-native';
3
+ import { useUITheme } from '../../../theme';
4
+ import { useScaling } from '../../../utils';
5
+ import { Typography } from '../../Typography';
6
+ import { useHeaderWithBackButton } from '../presenters/useHeaderWithBackButton';
7
+ import { getStyles } from './styles';
8
+ import type { HeaderWithBackButtonProps } from '../types/types';
9
+
10
+ export const HeaderWithBackButton = ({
11
+ title,
12
+ subtitle,
13
+ onBackPress,
14
+ backIcon,
15
+ backAccessibilityLabel,
16
+ backAccessibilityHint,
17
+ backDisabled = false,
18
+ rightAccessory,
19
+ titleAlign = 'left',
20
+ titleVariant = 'h3',
21
+ titleSize,
22
+ subtitleVariant = 'caption',
23
+ subtitleSize,
24
+ subtitleNumberOfLines = 1,
25
+ style,
26
+ contentStyle,
27
+ }: HeaderWithBackButtonProps) => {
28
+ const { colors, spacing } = useUITheme();
29
+ const scaling = useScaling();
30
+ const styles = useMemo(() => getStyles(colors, spacing, scaling), [colors, spacing, scaling]);
31
+ const { leftWidth, rightWidth, setLeftWidth, setRightWidth, centered, sideWidth } = useHeaderWithBackButton(titleAlign);
32
+
33
+ return (
34
+ <View style={[styles.container, style]}>
35
+ <View style={centered ? { width: sideWidth } : undefined}>
36
+ <View
37
+ style={styles.left}
38
+ onLayout={centered ? event => setLeftWidth(event.nativeEvent.layout.width) : undefined}
39
+ >
40
+ {!backDisabled && (
41
+ <TouchableOpacity
42
+ accessibilityRole="button"
43
+ accessibilityLabel={backAccessibilityLabel}
44
+ accessibilityHint={backAccessibilityHint}
45
+ activeOpacity={0.7}
46
+ hitSlop={styles.hitSlop}
47
+ onPress={onBackPress}
48
+ style={styles.backButton}
49
+ >
50
+ {backIcon}
51
+ </TouchableOpacity>
52
+ )}
53
+ </View>
54
+ </View>
55
+
56
+ <View
57
+ style={[
58
+ styles.content,
59
+ centered && leftWidth !== undefined && rightWidth !== undefined ? null : centered ? { opacity: 0 } : null,
60
+ contentStyle,
61
+ ]}
62
+ >
63
+ <Typography
64
+ text={title}
65
+ variant={titleVariant}
66
+ size={titleSize}
67
+ numberOfLines={1}
68
+ style={[styles.title, centered && styles.centeredText]}
69
+ />
70
+ {subtitle ? (
71
+ <Typography
72
+ text={subtitle}
73
+ variant={subtitleVariant}
74
+ size={subtitleSize}
75
+ numberOfLines={subtitleNumberOfLines}
76
+ style={[styles.subtitle, centered && styles.centeredText]}
77
+ />
78
+ ) : null}
79
+ </View>
80
+
81
+ <View style={centered ? { width: sideWidth } : undefined}>
82
+ <View
83
+ style={rightAccessory ? styles.right : undefined}
84
+ onLayout={centered ? event => setRightWidth(event.nativeEvent.layout.width) : undefined}
85
+ >
86
+ {rightAccessory}
87
+ </View>
88
+ </View>
89
+ </View>
90
+ );
91
+ };
@@ -0,0 +1,58 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import type { UIColors, UISpacing } from '../../../theme';
3
+ import type { ScalingFunctions } from '../../../utils';
4
+
5
+ export const getStyles = (colors: UIColors, spacing: UISpacing, scaling: ScalingFunctions) => {
6
+ const backSize = scaling.scaleVertical(18);
7
+ const sideGap = scaling.scaleHorizontal(spacing.md);
8
+
9
+ return StyleSheet.create({
10
+ container: {
11
+ flexDirection: 'row',
12
+ alignItems: 'center',
13
+ paddingHorizontal: scaling.scaleHorizontal(spacing.lg),
14
+ paddingVertical: scaling.scaleVertical(spacing.xs),
15
+ },
16
+ left: {
17
+ flexDirection: 'row',
18
+ alignItems: 'center',
19
+ alignSelf: 'flex-start',
20
+ minWidth: backSize + sideGap,
21
+ paddingRight: sideGap,
22
+ },
23
+ backButton: {
24
+ width: backSize,
25
+ height: backSize,
26
+ alignItems: 'center',
27
+ justifyContent: 'center',
28
+ },
29
+ hitSlop: {
30
+ top: scaling.scaleVertical(spacing.sm),
31
+ bottom: scaling.scaleVertical(spacing.sm),
32
+ left: scaling.scaleHorizontal(spacing.sm),
33
+ right: scaling.scaleHorizontal(spacing.sm),
34
+ },
35
+ content: {
36
+ flex: 1,
37
+ minWidth: 0,
38
+ justifyContent: 'center',
39
+ },
40
+ centeredText: {
41
+ textAlign: 'center',
42
+ },
43
+ title: {
44
+ color: colors.text,
45
+ },
46
+ subtitle: {
47
+ color: colors.textMuted,
48
+ },
49
+ right: {
50
+ minWidth: scaling.scaleVertical(34) + sideGap,
51
+ minHeight: scaling.scaleVertical(34),
52
+ alignSelf: 'flex-end',
53
+ paddingLeft: sideGap,
54
+ alignItems: 'flex-end',
55
+ justifyContent: 'center',
56
+ },
57
+ });
58
+ };
@@ -0,0 +1,8 @@
1
+ export { Input } from './ui/Input';
2
+ export type {
3
+ InputPasswordToggle,
4
+ InputPasswordToggleAction,
5
+ InputProps,
6
+ InputRef,
7
+ InputTextProps,
8
+ } from './types/types';
@@ -0,0 +1,104 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import type { TextInputProps } from 'react-native';
3
+ import { useUITheme } from '../../../theme';
4
+ import type { InputProps } from '../types/types';
5
+
6
+ type UseInputProps = Pick<
7
+ InputProps,
8
+ | 'accessibilityHint'
9
+ | 'accessibilityLabel'
10
+ | 'accessibilityState'
11
+ | 'disabled'
12
+ | 'editable'
13
+ | 'errorText'
14
+ | 'errorTextProps'
15
+ | 'helperText'
16
+ | 'helperTextProps'
17
+ | 'invalid'
18
+ | 'label'
19
+ | 'labelProps'
20
+ | 'onBlur'
21
+ | 'onFocus'
22
+ | 'passwordToggle'
23
+ | 'placeholderTextColor'
24
+ | 'secureTextEntry'
25
+ >;
26
+
27
+ export const useInput = ({
28
+ accessibilityHint,
29
+ accessibilityLabel,
30
+ accessibilityState,
31
+ disabled = false,
32
+ editable,
33
+ errorText,
34
+ errorTextProps,
35
+ helperText,
36
+ helperTextProps,
37
+ invalid = false,
38
+ label,
39
+ labelProps,
40
+ onBlur,
41
+ onFocus,
42
+ passwordToggle,
43
+ placeholderTextColor,
44
+ secureTextEntry,
45
+ }: UseInputProps) => {
46
+ const { colors, input } = useUITheme();
47
+ const canTogglePassword = Boolean(secureTextEntry && passwordToggle);
48
+ const [visibility, setVisibility] = useState({ enabled: canTogglePassword, visible: false });
49
+ const [isFocused, setFocused] = useState(false);
50
+
51
+ useEffect(() => {
52
+ setVisibility(current => current.enabled === canTogglePassword
53
+ ? current
54
+ : { enabled: canTogglePassword, visible: false });
55
+ }, [canTogglePassword]);
56
+
57
+ const isPasswordVisible = canTogglePassword && visibility.enabled === canTogglePassword && visibility.visible;
58
+ const isInvalid = invalid || Boolean(errorText);
59
+
60
+ const onInputFocus = useCallback<NonNullable<TextInputProps['onFocus']>>(
61
+ event => {
62
+ setFocused(true);
63
+ onFocus?.(event);
64
+ },
65
+ [onFocus],
66
+ );
67
+ const onInputBlur = useCallback<NonNullable<TextInputProps['onBlur']>>(
68
+ event => {
69
+ setFocused(false);
70
+ onBlur?.(event);
71
+ },
72
+ [onBlur],
73
+ );
74
+ const onPasswordVisibilityToggle = useCallback(() => {
75
+ setVisibility(current => ({
76
+ enabled: canTogglePassword,
77
+ visible: !(current.enabled === canTogglePassword && current.visible),
78
+ }));
79
+ }, [canTogglePassword]);
80
+ const passwordAction = isPasswordVisible ? passwordToggle?.hide : passwordToggle?.show;
81
+
82
+ return {
83
+ canTogglePassword,
84
+ errorColor: errorTextProps?.color ?? input.colors.error,
85
+ errorVariant: errorTextProps?.variant ?? input.typography.errorVariant,
86
+ helperColor: helperTextProps?.color ?? input.colors.helper,
87
+ helperVariant: helperTextProps?.variant ?? input.typography.helperVariant,
88
+ labelColor: labelProps?.color ?? input.colors.label,
89
+ labelVariant: labelProps?.variant ?? input.typography.labelVariant,
90
+ isFocused,
91
+ isInvalid,
92
+ onInputBlur,
93
+ onInputFocus,
94
+ onPasswordVisibilityToggle,
95
+ passwordAction,
96
+ resolvedAccessibilityHint: accessibilityHint ?? (errorText || undefined),
97
+ resolvedAccessibilityLabel: accessibilityLabel ?? label,
98
+ resolvedAccessibilityState: disabled ? { ...accessibilityState, disabled: true } : accessibilityState,
99
+ resolvedEditable: disabled ? false : editable,
100
+ resolvedPlaceholderTextColor: placeholderTextColor ?? colors[input.colors.placeholder],
101
+ resolvedSecureTextEntry: secureTextEntry ? !isPasswordVisible : secureTextEntry,
102
+ showHelperText: !isInvalid && Boolean(helperText),
103
+ };
104
+ };
@@ -0,0 +1,36 @@
1
+ import type { ComponentRef, ReactNode } from 'react';
2
+ import type { StyleProp, TextInput, TextInputProps, ViewStyle } from 'react-native';
3
+ import type { TypographyProps } from '../../Typography';
4
+
5
+ export type InputRef = ComponentRef<typeof TextInput>;
6
+
7
+ export type InputTextProps = Pick<
8
+ TypographyProps,
9
+ 'variant' | 'weight' | 'color' | 'style' | 'numberOfLines'
10
+ >;
11
+
12
+ export interface InputPasswordToggleAction {
13
+ label: string;
14
+ icon: ReactNode;
15
+ }
16
+
17
+ export interface InputPasswordToggle {
18
+ show: InputPasswordToggleAction;
19
+ hide: InputPasswordToggleAction;
20
+ }
21
+
22
+ export interface InputProps extends Omit<TextInputProps, 'ref'> {
23
+ label?: string;
24
+ helperText?: string;
25
+ errorText?: string;
26
+ invalid?: boolean;
27
+ disabled?: boolean;
28
+ startAccessory?: ReactNode;
29
+ endAccessory?: ReactNode;
30
+ passwordToggle?: InputPasswordToggle;
31
+ containerStyle?: StyleProp<ViewStyle>;
32
+ inputContainerStyle?: StyleProp<ViewStyle>;
33
+ labelProps?: InputTextProps;
34
+ helperTextProps?: InputTextProps;
35
+ errorTextProps?: InputTextProps;
36
+ }