@sudobility/building_blocks_rn 0.0.31 → 0.0.33

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.
@@ -1,18 +1,24 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { View, Text, ScrollView } from 'react-native';
3
3
  import { createThemedStyles } from '../../utils/styles';
4
+ import { useIsRTL } from '../../hooks/useRTL';
4
5
  function TextSectionView({ section, level = 2, }) {
5
6
  const styles = useStyles();
6
- return (_jsxs(View, { style: styles.section, children: [_jsx(Text, { style: level === 2 ? styles.sectionTitle : styles.subsectionTitle, children: section.title }), section.content && (_jsx(Text, { style: styles.sectionContent, children: section.content })), section.items && section.items.length > 0 && (_jsx(View, { style: styles.list, children: section.items.map((item, index) => (_jsxs(View, { style: styles.listItem, children: [_jsx(Text, { style: styles.bullet, children: '\u2022' }), _jsx(Text, { style: styles.listItemText, children: item })] }, index))) })), section.subsections?.map((sub, index) => (_jsx(TextSectionView, { section: sub, level: 3 }, index)))] }));
7
+ const isRTL = useIsRTL();
8
+ return (_jsxs(View, { style: styles.section, children: [_jsx(Text, { style: [
9
+ level === 2 ? styles.sectionTitle : styles.subsectionTitle,
10
+ isRTL && styles.rtlText,
11
+ ], children: section.title }), section.content && (_jsx(Text, { style: [styles.sectionContent, isRTL && styles.rtlText], children: section.content })), section.items && section.items.length > 0 && (_jsx(View, { style: styles.list, children: section.items.map((item, index) => (_jsxs(View, { style: styles.listItem, children: [_jsx(Text, { style: styles.bullet, children: '\u2022' }), _jsx(Text, { style: [styles.listItemText, isRTL && styles.rtlText], children: item })] }, index))) })), section.subsections?.map((sub, index) => (_jsx(TextSectionView, { section: sub, level: 3 }, index)))] }));
7
12
  }
8
13
  export function AppTextScreen({ text, lastUpdatedDate, ScreenWrapper, style, }) {
9
14
  const styles = useStyles();
15
+ const isRTL = useIsRTL();
10
16
  const lastUpdated = text.lastUpdated
11
17
  ? text.lastUpdated.replace('{{date}}', lastUpdatedDate ?? '')
12
18
  : lastUpdatedDate
13
19
  ? `Last updated: ${lastUpdatedDate}`
14
20
  : undefined;
15
- const content = (_jsxs(View, { style: [styles.container, style], children: [_jsx(Text, { style: styles.title, children: text.title }), lastUpdated && _jsx(Text, { style: styles.lastUpdated, children: lastUpdated }), text.sections.map((section, index) => (_jsx(TextSectionView, { section: section }, index))), text.contact && (_jsxs(View, { style: styles.contactSection, children: [_jsx(Text, { style: styles.sectionTitle, children: text.contact.title }), _jsx(Text, { style: styles.sectionContent, children: text.contact.description }), _jsx(Text, { style: styles.contactInfo, children: text.contact.info }), text.contact.gdprNotice && (_jsx(Text, { style: styles.gdprNotice, children: text.contact.gdprNotice }))] }))] }));
21
+ const content = (_jsxs(View, { style: [styles.container, style], children: [_jsx(Text, { style: [styles.title, isRTL && styles.rtlText], children: text.title }), lastUpdated && (_jsx(Text, { style: [styles.lastUpdated, isRTL && styles.rtlText], children: lastUpdated })), text.sections.map((section, index) => (_jsx(TextSectionView, { section: section }, index))), text.contact && (_jsxs(View, { style: styles.contactSection, children: [_jsx(Text, { style: [styles.sectionTitle, isRTL && styles.rtlText], children: text.contact.title }), _jsx(Text, { style: [styles.sectionContent, isRTL && styles.rtlText], children: text.contact.description }), _jsx(Text, { style: [styles.contactInfo, isRTL && styles.rtlText], children: text.contact.info }), text.contact.gdprNotice && (_jsx(Text, { style: [styles.gdprNotice, isRTL && styles.rtlText], children: text.contact.gdprNotice }))] }))] }));
16
22
  if (ScreenWrapper) {
17
23
  return _jsx(ScreenWrapper, { children: content });
18
24
  }
@@ -30,13 +36,11 @@ const useStyles = createThemedStyles(colors => ({
30
36
  fontWeight: '700',
31
37
  color: colors.text,
32
38
  marginBottom: 8,
33
- textAlign: 'auto',
34
39
  },
35
40
  lastUpdated: {
36
41
  fontSize: 14,
37
42
  color: colors.textMuted,
38
43
  marginBottom: 24,
39
- textAlign: 'auto',
40
44
  },
41
45
  section: {
42
46
  marginBottom: 24,
@@ -46,20 +50,17 @@ const useStyles = createThemedStyles(colors => ({
46
50
  fontWeight: '600',
47
51
  color: colors.text,
48
52
  marginBottom: 8,
49
- textAlign: 'auto',
50
53
  },
51
54
  subsectionTitle: {
52
55
  fontSize: 17,
53
56
  fontWeight: '600',
54
57
  color: colors.text,
55
58
  marginBottom: 6,
56
- textAlign: 'auto',
57
59
  },
58
60
  sectionContent: {
59
61
  fontSize: 15,
60
62
  lineHeight: 22,
61
63
  color: colors.textSecondary,
62
- textAlign: 'auto',
63
64
  },
64
65
  list: {
65
66
  marginTop: 8,
@@ -80,7 +81,6 @@ const useStyles = createThemedStyles(colors => ({
80
81
  lineHeight: 22,
81
82
  color: colors.textSecondary,
82
83
  flex: 1,
83
- textAlign: 'auto',
84
84
  },
85
85
  contactSection: {
86
86
  marginTop: 16,
@@ -92,13 +92,14 @@ const useStyles = createThemedStyles(colors => ({
92
92
  fontSize: 15,
93
93
  color: colors.primary,
94
94
  marginTop: 8,
95
- textAlign: 'auto',
96
95
  },
97
96
  gdprNotice: {
98
97
  fontSize: 13,
99
98
  color: colors.textMuted,
100
99
  marginTop: 12,
101
100
  fontStyle: 'italic',
102
- textAlign: 'auto',
101
+ },
102
+ rtlText: {
103
+ writingDirection: 'rtl',
103
104
  },
104
105
  }));
@@ -14,24 +14,26 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
14
  */
15
15
  import { useState } from 'react';
16
16
  import { View, Text, Pressable, Modal, FlatList, SafeAreaView, } from 'react-native';
17
+ import { useRTLTextStyle } from '../../hooks/useRTL';
17
18
  import { DEFAULT_LANGUAGES } from '../../constants/languages';
18
19
  import { createThemedStyles } from '../../utils/styles';
19
20
  export function LanguagePicker({ languages = DEFAULT_LANGUAGES, currentLanguage = 'en', onLanguageChange, label, style, }) {
20
21
  const styles = useStyles();
22
+ const rtlText = useRTLTextStyle();
21
23
  const [modalVisible, setModalVisible] = useState(false);
22
24
  const currentLang = languages.find(l => l.code === currentLanguage);
23
25
  const handleSelect = (code) => {
24
26
  onLanguageChange?.(code);
25
27
  setModalVisible(false);
26
28
  };
27
- return (_jsxs(View, { style: style, children: [label && _jsx(Text, { style: styles.label, children: label }), _jsxs(Pressable, { style: styles.trigger, onPress: () => setModalVisible(true), accessibilityRole: 'button', accessibilityLabel: `${label ?? 'Language'}: ${currentLang?.name ?? currentLanguage}. Tap to change.`, children: [_jsx(Text, { style: styles.triggerText, children: currentLang
29
+ return (_jsxs(View, { style: style, children: [label && _jsx(Text, { style: [styles.label, rtlText], children: label }), _jsxs(Pressable, { style: styles.trigger, onPress: () => setModalVisible(true), accessibilityRole: 'button', accessibilityLabel: `${label ?? 'Language'}: ${currentLang?.name ?? currentLanguage}. Tap to change.`, children: [_jsx(Text, { style: [styles.triggerText, rtlText], children: currentLang
28
30
  ? `${currentLang.flag} ${currentLang.name}`
29
- : currentLanguage }), _jsx(Text, { style: styles.chevron, children: '\u25BC' })] }), _jsx(Modal, { visible: modalVisible, animationType: 'slide', presentationStyle: 'pageSheet', onRequestClose: () => setModalVisible(false), children: _jsxs(SafeAreaView, { style: styles.modal, children: [_jsxs(View, { style: styles.modalHeader, children: [_jsx(Text, { style: styles.modalTitle, accessibilityRole: 'header', children: label ?? 'Select Language' }), _jsx(Pressable, { onPress: () => setModalVisible(false), accessibilityRole: 'button', accessibilityLabel: 'Done, close language picker', children: _jsx(Text, { style: styles.closeButton, children: "Done" }) })] }), _jsx(FlatList, { data: languages, keyExtractor: item => item.code, renderItem: ({ item }) => (_jsxs(Pressable, { style: [
31
+ : currentLanguage }), _jsx(Text, { style: styles.chevron, children: '\u25BC' })] }), _jsx(Modal, { visible: modalVisible, animationType: 'slide', presentationStyle: 'pageSheet', onRequestClose: () => setModalVisible(false), children: _jsxs(SafeAreaView, { style: styles.modal, children: [_jsxs(View, { style: styles.modalHeader, children: [_jsx(Text, { style: [styles.modalTitle, rtlText], accessibilityRole: 'header', children: label ?? 'Select Language' }), _jsx(Pressable, { onPress: () => setModalVisible(false), accessibilityRole: 'button', accessibilityLabel: 'Done, close language picker', children: _jsx(Text, { style: styles.closeButton, children: "Done" }) })] }), _jsx(FlatList, { data: languages, keyExtractor: item => item.code, renderItem: ({ item }) => (_jsxs(Pressable, { style: [
30
32
  styles.languageRow,
31
33
  item.code === currentLanguage && styles.languageRowActive,
32
34
  ], onPress: () => handleSelect(item.code), accessibilityRole: 'radio', accessibilityState: {
33
35
  selected: item.code === currentLanguage,
34
- }, accessibilityLabel: `${item.name}${item.code === currentLanguage ? ', selected' : ''}`, children: [_jsx(Text, { style: styles.flag, children: item.flag }), _jsx(Text, { style: styles.languageName, children: item.name }), item.code === currentLanguage && (_jsx(Text, { style: styles.checkmark, children: '\u2713' }))] })), ItemSeparatorComponent: () => _jsx(View, { style: styles.separator }) })] }) })] }));
36
+ }, accessibilityLabel: `${item.name}${item.code === currentLanguage ? ', selected' : ''}`, children: [_jsx(Text, { style: styles.flag, children: item.flag }), _jsx(Text, { style: [styles.languageName, rtlText], children: item.name }), item.code === currentLanguage && (_jsx(Text, { style: styles.checkmark, children: '\u2713' }))] })), ItemSeparatorComponent: () => _jsx(View, { style: styles.separator }) })] }) })] }));
35
37
  }
36
38
  const useStyles = createThemedStyles(colors => ({
37
39
  label: {
@@ -39,7 +41,6 @@ const useStyles = createThemedStyles(colors => ({
39
41
  fontWeight: '500',
40
42
  color: colors.textSecondary,
41
43
  marginBottom: 8,
42
- textAlign: 'auto',
43
44
  },
44
45
  trigger: {
45
46
  flexDirection: 'row',
@@ -56,7 +57,6 @@ const useStyles = createThemedStyles(colors => ({
56
57
  triggerText: {
57
58
  fontSize: 16,
58
59
  color: colors.text,
59
- textAlign: 'auto',
60
60
  },
61
61
  chevron: {
62
62
  fontSize: 10,
@@ -103,7 +103,6 @@ const useStyles = createThemedStyles(colors => ({
103
103
  fontSize: 16,
104
104
  color: colors.text,
105
105
  flex: 1,
106
- textAlign: 'auto',
107
106
  },
108
107
  checkmark: {
109
108
  fontSize: 18,
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { View, Text, Pressable, ScrollView } from 'react-native';
3
3
  import { createThemedStyles } from '../../utils/styles';
4
+ import { useRTLTextStyle } from '../../hooks/useRTL';
4
5
  export function SettingsListScreen({ sections, onSectionPress, title = 'Settings', style, onTrack, }) {
5
6
  const styles = useStyles();
7
+ const rtlText = useRTLTextStyle();
6
8
  const handlePress = (section) => {
7
9
  onTrack?.({
8
10
  eventType: 'navigation',
@@ -12,12 +14,12 @@ export function SettingsListScreen({ sections, onSectionPress, title = 'Settings
12
14
  });
13
15
  onSectionPress(section.id);
14
16
  };
15
- return (_jsxs(ScrollView, { style: [styles.container, style], contentContainerStyle: styles.content, children: [_jsx(Text, { style: styles.title, accessibilityRole: 'header', children: title }), _jsx(View, { style: styles.sectionList, accessibilityRole: 'list', children: sections.map((section, index) => {
17
+ return (_jsxs(ScrollView, { style: [styles.container, style], contentContainerStyle: styles.content, children: [_jsx(Text, { style: [styles.title, rtlText], accessibilityRole: 'header', children: title }), _jsx(View, { style: styles.sectionList, accessibilityRole: 'list', children: sections.map((section, index) => {
16
18
  const IconComponent = section.icon;
17
19
  return (_jsxs(Pressable, { style: [
18
20
  styles.sectionRow,
19
21
  index < sections.length - 1 && styles.sectionRowBorder,
20
- ], onPress: () => handlePress(section), accessibilityRole: 'button', accessibilityLabel: `${section.label}${section.description ? `, ${section.description}` : ''}`, accessibilityHint: `Opens ${section.label} settings`, children: [IconComponent && (_jsx(View, { style: styles.iconContainer, children: _jsx(IconComponent, { size: 20, color: styles.iconColor.color }) })), _jsxs(View, { style: styles.sectionInfo, children: [_jsx(Text, { style: styles.sectionLabel, children: section.label }), section.description && (_jsx(Text, { style: styles.sectionDescription, children: section.description }))] }), _jsx(Text, { style: styles.chevron, children: '\u203A' })] }, section.id));
22
+ ], onPress: () => handlePress(section), accessibilityRole: 'button', accessibilityLabel: `${section.label}${section.description ? `, ${section.description}` : ''}`, accessibilityHint: `Opens ${section.label} settings`, children: [IconComponent && (_jsx(View, { style: styles.iconContainer, children: _jsx(IconComponent, { size: 20, color: styles.iconColor.color }) })), _jsxs(View, { style: styles.sectionInfo, children: [_jsx(Text, { style: [styles.sectionLabel, rtlText], children: section.label }), section.description && (_jsx(Text, { style: [styles.sectionDescription, rtlText], children: section.description }))] }), _jsx(Text, { style: styles.chevron, children: '\u203A' })] }, section.id));
21
23
  }) })] }));
22
24
  }
23
25
  const useStyles = createThemedStyles(colors => ({
@@ -33,7 +35,6 @@ const useStyles = createThemedStyles(colors => ({
33
35
  fontWeight: '700',
34
36
  color: colors.text,
35
37
  marginBottom: 20,
36
- textAlign: 'auto',
37
38
  },
38
39
  sectionList: {
39
40
  backgroundColor: colors.card,
@@ -70,13 +71,11 @@ const useStyles = createThemedStyles(colors => ({
70
71
  fontSize: 16,
71
72
  fontWeight: '500',
72
73
  color: colors.text,
73
- textAlign: 'auto',
74
74
  },
75
75
  sectionDescription: {
76
76
  fontSize: 13,
77
77
  color: colors.textMuted,
78
78
  marginTop: 2,
79
- textAlign: 'auto',
80
79
  },
81
80
  chevron: {
82
81
  fontSize: 22,
@@ -1,2 +1,3 @@
1
1
  export { useResponsive } from './useResponsive';
2
2
  export type { ResponsiveInfo } from './useResponsive';
3
+ export { useIsRTL, useRTLTextStyle } from './useRTL';
@@ -1 +1,2 @@
1
1
  export { useResponsive } from './useResponsive';
2
+ export { useIsRTL, useRTLTextStyle } from './useRTL';
@@ -0,0 +1,12 @@
1
+ import { type TextStyle } from 'react-native';
2
+ /**
3
+ * Returns whether the current language is RTL.
4
+ * Checks both I18nManager (native layout) and the current i18n language
5
+ * (covers the session before a restart applies forceRTL).
6
+ */
7
+ export declare function useIsRTL(): boolean;
8
+ /**
9
+ * Returns a text style object that sets the correct text alignment
10
+ * for the current language direction.
11
+ */
12
+ export declare function useRTLTextStyle(): TextStyle;
@@ -0,0 +1,24 @@
1
+ import { useMemo } from 'react';
2
+ import { I18nManager } from 'react-native';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { isRTL as isRTLLanguage } from '../constants/languages';
5
+ /**
6
+ * Returns whether the current language is RTL.
7
+ * Checks both I18nManager (native layout) and the current i18n language
8
+ * (covers the session before a restart applies forceRTL).
9
+ */
10
+ export function useIsRTL() {
11
+ const { i18n } = useTranslation();
12
+ return I18nManager.isRTL || isRTLLanguage(i18n.language);
13
+ }
14
+ /**
15
+ * Returns a text style object that sets the correct text alignment
16
+ * for the current language direction.
17
+ */
18
+ export function useRTLTextStyle() {
19
+ const isRTL = useIsRTL();
20
+ return useMemo(() => ({
21
+ textAlign: isRTL ? 'right' : 'left',
22
+ writingDirection: isRTL ? 'rtl' : 'ltr',
23
+ }), [isRTL]);
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sudobility/building_blocks_rn",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "description": "Higher-level shared UI building blocks for Sudobility React Native apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",