@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,121 @@
1
+ import { StyleSheet } from "react-native";
2
+ import type { UIColors, UIRadius, UISpacing } from "../../../theme";
3
+ import type { ScalingFunctions } from "../../../utils";
4
+
5
+ export const getStyles = (
6
+ colors: UIColors,
7
+ spacing: UISpacing,
8
+ radius: UIRadius,
9
+ scaling: ScalingFunctions
10
+ ) => {
11
+ return StyleSheet.create({
12
+ launcher: {
13
+ position: "absolute",
14
+ right: scaling.scaleHorizontal(spacing.lg),
15
+ bottom: scaling.scaleVertical(spacing.lg * 7),
16
+ zIndex: 10,
17
+ padding: scaling.scaleVertical(spacing.sm),
18
+ borderRadius: scaling.scaleHorizontal(radius.lg),
19
+ borderWidth: scaling.scaleHorizontal(1),
20
+ borderColor: colors.border,
21
+ backgroundColor: colors.background,
22
+ },
23
+ container: {
24
+ flex: 1,
25
+ backgroundColor: colors.background,
26
+ },
27
+ header: {
28
+ flexDirection: "row",
29
+ alignItems: "center",
30
+ paddingHorizontal: scaling.scaleHorizontal(spacing.lg),
31
+ paddingVertical: scaling.scaleVertical(spacing.sm),
32
+ gap: scaling.scaleHorizontal(spacing.sm),
33
+ },
34
+ headerTitle: {
35
+ flex: 1,
36
+ },
37
+ headerAction: {
38
+ paddingHorizontal: scaling.scaleHorizontal(spacing.sm),
39
+ paddingVertical: scaling.scaleVertical(spacing.sm),
40
+ },
41
+ actionText: { color: colors.primary },
42
+ list: {
43
+ flex: 1,
44
+ borderTopWidth: scaling.scaleVertical(1),
45
+ borderTopColor: colors.border,
46
+ },
47
+ listContent: {
48
+ flexGrow: 1,
49
+ paddingHorizontal: scaling.scaleHorizontal(spacing.lg),
50
+ paddingBottom: scaling.scaleVertical(spacing.lg),
51
+ },
52
+ empty: {
53
+ flex: 1,
54
+ alignItems: "center",
55
+ justifyContent: "center",
56
+ padding: scaling.scaleHorizontal(spacing.lg),
57
+ },
58
+ card: {
59
+ marginTop: scaling.scaleVertical(spacing.md),
60
+ paddingHorizontal: scaling.scaleHorizontal(spacing.md),
61
+ paddingVertical: scaling.scaleVertical(spacing.sm),
62
+ borderRadius: scaling.scaleHorizontal(radius.sm),
63
+ borderWidth: scaling.scaleHorizontal(1),
64
+ borderColor: colors.border,
65
+ backgroundColor: colors.surface,
66
+ },
67
+ cardTitle: {
68
+ marginBottom: scaling.scaleVertical(spacing.xs),
69
+ },
70
+ section: {
71
+ borderLeftWidth: scaling.scaleHorizontal(3),
72
+ paddingLeft: scaling.scaleHorizontal(spacing.sm),
73
+ },
74
+ divider: {
75
+ height: scaling.scaleVertical(1),
76
+ backgroundColor: colors.border,
77
+ marginVertical: scaling.scaleVertical(spacing.sm),
78
+ },
79
+ sectionButton: {
80
+ flex: 1,
81
+ minHeight: scaling.scaleVertical(36),
82
+ flexDirection: "row",
83
+ alignItems: "center",
84
+ gap: scaling.scaleHorizontal(spacing.sm),
85
+ },
86
+ sectionRow: {
87
+ flexDirection: "row",
88
+ alignItems: "center",
89
+ },
90
+ sectionTitle: {
91
+ flex: 1,
92
+ },
93
+ paramsRow: {
94
+ flexDirection: "row",
95
+ alignItems: "flex-start",
96
+ },
97
+ paramsText: {
98
+ flex: 1,
99
+ minWidth: 0,
100
+ },
101
+ copyButton: {
102
+ minHeight: scaling.scaleVertical(36),
103
+ justifyContent: "center",
104
+ paddingHorizontal: scaling.scaleHorizontal(spacing.sm),
105
+ },
106
+ copyButtonTop: {
107
+ minHeight: scaling.scaleVertical(36),
108
+ justifyContent: "flex-start",
109
+ paddingTop: scaling.scaleVertical(spacing.xs),
110
+ paddingHorizontal: scaling.scaleHorizontal(spacing.sm),
111
+ },
112
+ emptySection: {
113
+ height: scaling.scaleVertical(spacing.sm),
114
+ },
115
+ message: {
116
+ color: colors.textMuted,
117
+ marginTop: scaling.scaleVertical(spacing.xs),
118
+ marginBottom: scaling.scaleVertical(spacing.sm),
119
+ },
120
+ });
121
+ };
@@ -0,0 +1,40 @@
1
+ import type { LoggerEntry } from '../types/types';
2
+
3
+ export type LoggerPresentationItem =
4
+ | { kind: 'entry'; key: string; entry: LoggerEntry }
5
+ | { kind: 'exchange'; key: string; request?: LoggerEntry; response?: LoggerEntry };
6
+
7
+ export const groupLoggerEntries = (logs: readonly LoggerEntry[]): LoggerPresentationItem[] => {
8
+ const items: LoggerPresentationItem[] = [];
9
+ const exchanges = new Map<string, Extract<LoggerPresentationItem, { kind: 'exchange' }>>();
10
+
11
+ for (const entry of logs) {
12
+ const { correlationId, phase } = entry.http ?? {};
13
+ if (!correlationId || !phase) {
14
+ items.push({ kind: 'entry', key: `entry:${entry.id}`, entry });
15
+ continue;
16
+ }
17
+
18
+ let exchange = exchanges.get(correlationId);
19
+ if (!exchange) {
20
+ exchange = { kind: 'exchange', key: `exchange:${correlationId}` };
21
+ exchanges.set(correlationId, exchange);
22
+ items.push(exchange);
23
+ }
24
+
25
+ if (exchange[phase]) {
26
+ items.push({ kind: 'entry', key: `entry:${entry.id}`, entry });
27
+ } else {
28
+ exchange[phase] = entry;
29
+ }
30
+ }
31
+
32
+ return items;
33
+ };
34
+
35
+ export const toggleExpandedEntryId = (previous: ReadonlySet<string>, id: string): Set<string> => {
36
+ const next = new Set(previous);
37
+ if (next.has(id)) next.delete(id);
38
+ else next.add(id);
39
+ return next;
40
+ };
@@ -0,0 +1,69 @@
1
+ import type { LoggerEntry, LoggerLabels } from '../types/types';
2
+ import type { UIColorKey } from '../../../theme';
3
+
4
+ const nonBlank = (value: string | undefined): string | undefined => value?.trim() ? value : undefined;
5
+
6
+ const isRequest = (entry: LoggerEntry): boolean => entry.http?.phase === 'request' || entry.type === 'request';
7
+
8
+ export const getLoggerSectionContext = (entry: LoggerEntry, labels: LoggerLabels): string => {
9
+ const phase = entry.http?.phase ?? (entry.type === 'request' || entry.type === 'response' ? entry.type : undefined);
10
+ return phase === 'request' ? labels.request : phase === 'response' ? labels.response : labels.typeLabels?.[entry.type] ?? entry.type;
11
+ };
12
+
13
+ export const getLoggerPayload = (entry: LoggerEntry): string | undefined => {
14
+ const message = entry.message.trim();
15
+ if (!message || (entry.http && (message === 'undefined' || message === 'null'))) return undefined;
16
+ return entry.message;
17
+ };
18
+
19
+ export const getRequestParams = (entry: LoggerEntry, labels: LoggerLabels): string | undefined => {
20
+ if (!isRequest(entry)) return undefined;
21
+ const params = nonBlank(entry.requestData?.params);
22
+ if (!params) return undefined;
23
+ return nonBlank(entry.requestData?.body) ? `${labels.params}\n${params}` : params;
24
+ };
25
+
26
+ export const getExpandablePayload = (entry: LoggerEntry, labels: LoggerLabels): string | undefined => {
27
+ if (!isRequest(entry)) return getLoggerPayload(entry);
28
+ const body = nonBlank(entry.requestData?.body) ?? getLoggerPayload(entry);
29
+ if (!body) return undefined;
30
+ return nonBlank(entry.requestData?.params) ? `${labels.body}\n${body}` : body;
31
+ };
32
+
33
+ export const getCopyableText = (entry: LoggerEntry, labels: LoggerLabels): string | undefined => {
34
+ if (!isRequest(entry)) return getLoggerPayload(entry);
35
+ const params = nonBlank(entry.requestData?.params);
36
+ const body = nonBlank(entry.requestData?.body) ?? getLoggerPayload(entry);
37
+ if (params && body) return `${labels.params}\n${params}\n\n${labels.body}\n${body}`;
38
+ return params ?? body;
39
+ };
40
+
41
+ export const shouldRenderLoggerSection = (entry: LoggerEntry, labels: LoggerLabels): boolean =>
42
+ !isRequest(entry) || getRequestParams(entry, labels) !== undefined || getExpandablePayload(entry, labels) !== undefined;
43
+
44
+ export const getResponseSummary = (entry: LoggerEntry, labels: LoggerLabels): string => {
45
+ if (entry.http?.phase !== 'response') return '';
46
+ return [
47
+ entry.http.status === undefined ? undefined : `${labels.status}: ${entry.http.status}`,
48
+ entry.http.durationMs === undefined ? undefined : `${entry.http.durationMs} ms`,
49
+ ].filter(value => value !== undefined).join(' · ');
50
+ };
51
+
52
+ export const getResponseDurationSuffix = (entry: LoggerEntry): string =>
53
+ entry.http?.phase === 'response' && entry.http.durationMs !== undefined
54
+ ? ` · ${entry.http.durationMs} ms`
55
+ : '';
56
+
57
+ export const getStatusColorKey = (status: number): UIColorKey => {
58
+ if (status >= 200 && status < 300) return 'success';
59
+ if (status >= 300 && status < 400) return 'icon';
60
+ if (status >= 400 && status < 500) return 'warning';
61
+ if (status >= 500 && status < 600) return 'error';
62
+ return 'text';
63
+ };
64
+
65
+ export const getVisibleSectionText = (entry: LoggerEntry, labels: LoggerLabels): string => {
66
+ if (isRequest(entry)) return getExpandablePayload(entry, labels) ? labels.payload : '';
67
+ if (entry.http || entry.type === 'response') return getResponseSummary(entry, labels);
68
+ return getLoggerSectionContext(entry, labels);
69
+ };
@@ -0,0 +1,2 @@
1
+ export { ScreenContainer } from './ui/ScreenContainer';
2
+ export type { ScreenContainerGradient, ScreenContainerProps } from './types/types';
@@ -0,0 +1,12 @@
1
+ import { useCallback } from 'react';
2
+ import { Keyboard } from 'react-native';
3
+
4
+ export const useScreenContainer = () => {
5
+ const onContainerStartShouldSetResponder = useCallback(() => false, []);
6
+ const onContainerResponderRelease = useCallback(() => Keyboard.dismiss(), []);
7
+
8
+ return {
9
+ onContainerStartShouldSetResponder,
10
+ onContainerResponderRelease,
11
+ };
12
+ };
@@ -0,0 +1,19 @@
1
+ import type { ReactNode, Ref } from 'react';
2
+ import type { ScrollViewProps, StyleProp, ViewStyle } from 'react-native';
3
+ import type { KeyboardAwareScrollViewRef } from 'react-native-keyboard-controller';
4
+ import type { LinearGradientProps } from 'react-native-linear-gradient';
5
+ import type { Edge } from 'react-native-safe-area-context';
6
+
7
+ export type ScreenContainerGradient = Pick<LinearGradientProps, 'colors' | 'locations' | 'start' | 'end'>;
8
+
9
+ export interface ScreenContainerProps extends Omit<ScrollViewProps, 'children' | 'style' | 'contentContainerStyle'> {
10
+ children?: ReactNode;
11
+ edges?: Edge[];
12
+ isKeyboardAvoiding?: boolean;
13
+ containerStyle?: StyleProp<ViewStyle>;
14
+ contentContainerStyle?: StyleProp<ViewStyle>;
15
+ headerComponent?: ReactNode;
16
+ footerComponent?: ReactNode;
17
+ keyboardScrollViewRef?: Ref<KeyboardAwareScrollViewRef>;
18
+ gradient?: ScreenContainerGradient;
19
+ }
@@ -0,0 +1,83 @@
1
+ import React, { useMemo } from 'react';
2
+ import { ScrollView, View } from 'react-native';
3
+ import { KeyboardAwareScrollView, KeyboardAvoidingView } from 'react-native-keyboard-controller';
4
+ import { LinearGradient } from 'react-native-linear-gradient';
5
+ import { SafeAreaView } from 'react-native-safe-area-context';
6
+ import { useUITheme } from '../../../theme';
7
+ import { useScaling } from '../../../utils';
8
+ import { getBottomOffset, getStyles } from './styles';
9
+ import type { ScreenContainerProps } from '../types/types';
10
+ import { useScreenContainer } from '../presenters/useScreenContainer';
11
+
12
+ export const ScreenContainer = ({
13
+ children,
14
+ edges = ['top', 'bottom'],
15
+ scrollEnabled = false,
16
+ isKeyboardAvoiding = false,
17
+ containerStyle,
18
+ contentContainerStyle,
19
+ headerComponent,
20
+ footerComponent,
21
+ scrollViewRef,
22
+ keyboardScrollViewRef,
23
+ keyboardShouldPersistTaps = 'handled',
24
+ showsVerticalScrollIndicator = false,
25
+ gradient,
26
+ ...scrollProps
27
+ }: ScreenContainerProps) => {
28
+ const { colors, spacing } = useUITheme();
29
+ const scaling = useScaling();
30
+ const styles = useMemo(() => getStyles(colors), [colors]);
31
+ const bottomOffset = getBottomOffset(spacing, scaling);
32
+ const { onContainerStartShouldSetResponder, onContainerResponderRelease } = useScreenContainer();
33
+
34
+ return (
35
+ <View style={styles.root}>
36
+ {gradient ? <LinearGradient {...gradient} pointerEvents="none" style={styles.gradient} /> : null}
37
+ <SafeAreaView edges={edges} style={styles.safeArea}>
38
+ {headerComponent}
39
+
40
+ {scrollEnabled ? (
41
+ isKeyboardAvoiding ? (
42
+ <KeyboardAwareScrollView
43
+ {...scrollProps}
44
+ ref={keyboardScrollViewRef}
45
+ bottomOffset={bottomOffset}
46
+ style={[styles.container, containerStyle]}
47
+ contentContainerStyle={[styles.content, contentContainerStyle]}
48
+ keyboardShouldPersistTaps={keyboardShouldPersistTaps}
49
+ showsVerticalScrollIndicator={showsVerticalScrollIndicator}
50
+ >
51
+ {children}
52
+ </KeyboardAwareScrollView>
53
+ ) : (
54
+ <ScrollView
55
+ {...scrollProps}
56
+ ref={scrollViewRef}
57
+ style={[styles.container, containerStyle]}
58
+ contentContainerStyle={[styles.content, contentContainerStyle]}
59
+ keyboardShouldPersistTaps={keyboardShouldPersistTaps}
60
+ showsVerticalScrollIndicator={showsVerticalScrollIndicator}
61
+ >
62
+ {children}
63
+ </ScrollView>
64
+ )
65
+ ) : isKeyboardAvoiding ? (
66
+ <KeyboardAvoidingView behavior="padding" style={[styles.container, containerStyle]}>
67
+ <View
68
+ style={styles.container}
69
+ onStartShouldSetResponder={onContainerStartShouldSetResponder}
70
+ onResponderRelease={onContainerResponderRelease}
71
+ >
72
+ {children}
73
+ </View>
74
+ </KeyboardAvoidingView>
75
+ ) : (
76
+ <View style={[styles.container, containerStyle]}>{children}</View>
77
+ )}
78
+
79
+ {footerComponent}
80
+ </SafeAreaView>
81
+ </View>
82
+ );
83
+ };
@@ -0,0 +1,31 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import type { UIColors } from '../../../theme';
3
+ import type { UISpacing } from '../../../theme';
4
+ import type { ScalingFunctions } from '../../../utils';
5
+
6
+ export const getStyles = (colors: UIColors) => {
7
+ const styles = StyleSheet.create({
8
+ root: {
9
+ flex: 1,
10
+ backgroundColor: colors.background,
11
+ },
12
+ gradient: {
13
+ ...StyleSheet.absoluteFill,
14
+ },
15
+ safeArea: {
16
+ flex: 1,
17
+ backgroundColor: colors.transparent,
18
+ },
19
+ container: {
20
+ flex: 1,
21
+ backgroundColor: colors.transparent,
22
+ },
23
+ content: {
24
+ flexGrow: 1,
25
+ },
26
+ });
27
+
28
+ return styles;
29
+ };
30
+
31
+ export const getBottomOffset = (spacing: UISpacing, scaling: ScalingFunctions) => scaling.scaleVertical(spacing.xl);
@@ -0,0 +1,2 @@
1
+ export * from './ui/Typography';
2
+ export * from './types/types';
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { TextProps } from 'react-native';
3
+ import type { UIColorKey, UIFontKey, TypographyVariant } from '../../../theme';
4
+
5
+ export type TypographyProps = Omit<TextProps, 'children'> & {
6
+ variant?: TypographyVariant;
7
+ weight?: UIFontKey;
8
+ color?: UIColorKey;
9
+ size?: number;
10
+ lineHeight?: number;
11
+ } & (
12
+ | { text: string | number; children?: never }
13
+ | { text?: never; children?: ReactNode }
14
+ );
@@ -0,0 +1,37 @@
1
+ import React, { forwardRef, memo, useMemo } from 'react';
2
+ import { Text } from 'react-native';
3
+ import type { TextInstance } from 'react-native';
4
+ import { useUITheme } from '../../../theme';
5
+ import { useScaling } from '../../../utils';
6
+ import { getTypographyStyle } from './styles';
7
+ import type { TypographyProps } from '../types/types';
8
+
9
+ const TypographyComponent = forwardRef<TextInstance, TypographyProps>(
10
+ (
11
+ {
12
+ text,
13
+ children,
14
+ variant,
15
+ weight,
16
+ color,
17
+ size,
18
+ lineHeight,
19
+ style,
20
+ ...props
21
+ },
22
+ ref,
23
+ ) => {
24
+ const theme = useUITheme();
25
+ const scaling = useScaling();
26
+ const resolvedStyle = useMemo(
27
+ () => getTypographyStyle(theme, scaling, { variant, weight, color, size, lineHeight }),
28
+ [theme, scaling, variant, weight, color, size, lineHeight],
29
+ );
30
+
31
+ return <Text ref={ref} {...props} style={[resolvedStyle, style]}>{text ?? children}</Text>;
32
+ },
33
+ );
34
+
35
+ TypographyComponent.displayName = 'Typography';
36
+
37
+ export const Typography = memo(TypographyComponent);
@@ -0,0 +1,26 @@
1
+ import type { TextStyle } from 'react-native';
2
+ import type { UITheme, UITypographyToken } from '../../../theme';
3
+ import type { ScalingFunctions } from '../../../utils';
4
+ import type { TypographyProps } from '../types/types';
5
+
6
+ type TypographyStyleProps = Pick<TypographyProps, 'variant' | 'weight' | 'color' | 'size' | 'lineHeight'>;
7
+
8
+ export const getTypographyStyle = (
9
+ { colors, fonts, typography }: UITheme,
10
+ scaling: ScalingFunctions,
11
+ { variant = 'body', weight, color = 'text', size, lineHeight }: TypographyStyleProps,
12
+ ): TextStyle => {
13
+ const token = typography[variant] as UITypographyToken;
14
+ const font = fonts[weight ?? token.font];
15
+ const resolvedLineHeight = lineHeight ?? token.lineHeight;
16
+
17
+ return {
18
+ ...font,
19
+ color: colors[color],
20
+ fontSize: scaling.scaleFontSize(size ?? token.fontSize),
21
+ lineHeight: resolvedLineHeight === undefined ? undefined : scaling.scaleLineHeight(resolvedLineHeight),
22
+ letterSpacing: token.letterSpacing,
23
+ includeFontPadding: token.includeFontPadding,
24
+ flexShrink: token.flexShrink,
25
+ };
26
+ };
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ export * from './components/Button';
2
+ export * from './components/BottomModal';
3
+ export * from './components/ConnectionContainer';
4
+ export * from './components/CustomAlert';
5
+ export * from './components/HeaderWithBackButton';
6
+ export * from './components/Input';
7
+ export * from './components/Loader';
8
+ export * from './components/Logger';
9
+ export * from './components/ScreenContainer';
10
+ export * from './components/Typography';
11
+ export * from './provider';
12
+ export * from './theme';
13
+ export * from './utils';
@@ -0,0 +1,14 @@
1
+ import { createContext, useContext } from 'react';
2
+ import type { UILocalizationAdapter } from './types';
3
+
4
+ export const UILocalizationContext = createContext<UILocalizationAdapter | undefined>(undefined);
5
+
6
+ export const useUILocalization = (): UILocalizationAdapter => {
7
+ const localization = useContext(UILocalizationContext);
8
+
9
+ if (!localization) {
10
+ throw new Error('useUILocalization requires the localization prop on UIProvider.');
11
+ }
12
+
13
+ return localization;
14
+ };
@@ -0,0 +1,9 @@
1
+ import { createContext, useContext } from 'react';
2
+ import { defaultTheme } from '../theme/defaultTheme';
3
+ import type { UIModeContextValue } from './types';
4
+
5
+ export const UIModeContext = createContext<UIModeContextValue>({
6
+ mode: defaultTheme.mode,
7
+ });
8
+
9
+ export const useUIMode = (): UIModeContextValue => useContext(UIModeContext);
@@ -0,0 +1,12 @@
1
+ export { useUILocalization } from './LocalizationContext';
2
+ export { useUIMode } from './ModeContext';
3
+ export type {
4
+ ChangeLocale,
5
+ ChangeMode,
6
+ MaybePromise,
7
+ Translate,
8
+ TranslationParams,
9
+ UILocalizationAdapter,
10
+ UIModeContextValue,
11
+ UIProviderProps,
12
+ } from './types';
@@ -0,0 +1,31 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ThemeMode, UIThemeOverride } from '../theme/types';
3
+
4
+ export type MaybePromise<T> = T | Promise<T>;
5
+
6
+ export type ChangeMode = (mode: ThemeMode) => MaybePromise<void>;
7
+
8
+ export interface UIModeContextValue {
9
+ mode: ThemeMode;
10
+ changeMode?: ChangeMode;
11
+ }
12
+
13
+ export type TranslationParams = Readonly<Record<string, unknown>>;
14
+
15
+ export type Translate = (key: string, params?: TranslationParams) => string;
16
+
17
+ export type ChangeLocale = (locale: string) => MaybePromise<void>;
18
+
19
+ export interface UILocalizationAdapter {
20
+ locale: string;
21
+ t: Translate;
22
+ changeLocale?: ChangeLocale;
23
+ direction?: 'ltr' | 'rtl';
24
+ }
25
+
26
+ export interface UIProviderProps {
27
+ children: ReactNode;
28
+ theme?: UIThemeOverride;
29
+ onModeChange?: ChangeMode;
30
+ localization?: UILocalizationAdapter;
31
+ }
@@ -0,0 +1,7 @@
1
+ export {
2
+ UIProvider,
3
+ UIKitThemeProvider,
4
+ useUITheme,
5
+ useUIKitTheme,
6
+ } from './UIProvider';
7
+ export type { UIProviderProps } from './UIProvider';
@@ -0,0 +1,60 @@
1
+ import React, { createContext, useContext, useMemo } from 'react';
2
+ import { UILocalizationContext } from '../provider/LocalizationContext';
3
+ import { UIModeContext } from '../provider/ModeContext';
4
+ import type {
5
+ UILocalizationAdapter,
6
+ UIModeContextValue,
7
+ UIProviderProps,
8
+ } from '../provider/types';
9
+ import { createTheme } from './createTheme';
10
+ import { defaultTheme } from './defaultTheme';
11
+ import type { UITheme } from './types';
12
+
13
+ const UIThemeContext = createContext<UITheme>(defaultTheme as UITheme);
14
+
15
+ export type { UIProviderProps } from '../provider/types';
16
+
17
+ export const UIProvider = ({ children, theme, onModeChange, localization }: UIProviderProps) => {
18
+ const locale = localization?.locale;
19
+ const translate = localization?.t;
20
+ const changeLocale = localization?.changeLocale;
21
+ const direction = localization?.direction;
22
+ const value = useMemo<UITheme>(
23
+ () => (theme ? createTheme(theme) : (defaultTheme as UITheme)),
24
+ [theme],
25
+ );
26
+ const modeValue = useMemo<UIModeContextValue>(
27
+ () => ({ mode: value.mode, changeMode: onModeChange }),
28
+ [onModeChange, value.mode],
29
+ );
30
+ const localizationValue = useMemo<UILocalizationAdapter | undefined>(
31
+ () =>
32
+ locale !== undefined && translate !== undefined
33
+ ? {
34
+ locale,
35
+ t: translate,
36
+ changeLocale,
37
+ direction,
38
+ }
39
+ : undefined,
40
+ [changeLocale, direction, locale, translate],
41
+ );
42
+
43
+ return (
44
+ <UIThemeContext.Provider value={value}>
45
+ <UIModeContext.Provider value={modeValue}>
46
+ <UILocalizationContext.Provider value={localizationValue}>
47
+ {children}
48
+ </UILocalizationContext.Provider>
49
+ </UIModeContext.Provider>
50
+ </UIThemeContext.Provider>
51
+ );
52
+ };
53
+
54
+ export const useUITheme = (): UITheme => useContext(UIThemeContext);
55
+
56
+ /** @deprecated Use UIProvider. */
57
+ export const UIKitThemeProvider = UIProvider;
58
+
59
+ /** @deprecated Use useUITheme. */
60
+ export const useUIKitTheme = useUITheme;