@nexub/react-native 0.0.0-stage → 2.2.0

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 (134) hide show
  1. package/README.md +113 -2
  2. package/lib/module/components/actions.js +222 -0
  3. package/lib/module/components/actions.js.map +1 -0
  4. package/lib/module/components/containers.js +300 -0
  5. package/lib/module/components/containers.js.map +1 -0
  6. package/lib/module/components/display.js +299 -0
  7. package/lib/module/components/display.js.map +1 -0
  8. package/lib/module/components/feedback.js +484 -0
  9. package/lib/module/components/feedback.js.map +1 -0
  10. package/lib/module/components/inputs.js +792 -0
  11. package/lib/module/components/inputs.js.map +1 -0
  12. package/lib/module/components/mobile.js +818 -0
  13. package/lib/module/components/mobile.js.map +1 -0
  14. package/lib/module/components/overlays.js +421 -0
  15. package/lib/module/components/overlays.js.map +1 -0
  16. package/lib/module/components/shared.js +57 -0
  17. package/lib/module/components/shared.js.map +1 -0
  18. package/lib/module/hooks/useControllableState.js +23 -0
  19. package/lib/module/hooks/useControllableState.js.map +1 -0
  20. package/lib/module/index.js +31 -0
  21. package/lib/module/index.js.map +1 -0
  22. package/lib/module/package.json +1 -0
  23. package/lib/module/patterns/app.js +709 -0
  24. package/lib/module/patterns/app.js.map +1 -0
  25. package/lib/module/patterns/auth.js +224 -0
  26. package/lib/module/patterns/auth.js.map +1 -0
  27. package/lib/module/patterns/home.js +201 -0
  28. package/lib/module/patterns/home.js.map +1 -0
  29. package/lib/module/patterns/index.js +8 -0
  30. package/lib/module/patterns/index.js.map +1 -0
  31. package/lib/module/patterns/money.js +419 -0
  32. package/lib/module/patterns/money.js.map +1 -0
  33. package/lib/module/primitives/Icon.js +17 -0
  34. package/lib/module/primitives/Icon.js.map +1 -0
  35. package/lib/module/primitives/Layout.js +79 -0
  36. package/lib/module/primitives/Layout.js.map +1 -0
  37. package/lib/module/primitives/Pressable.js +79 -0
  38. package/lib/module/primitives/Pressable.js.map +1 -0
  39. package/lib/module/primitives/ScreenContainer.js +58 -0
  40. package/lib/module/primitives/ScreenContainer.js.map +1 -0
  41. package/lib/module/primitives/Surface.js +57 -0
  42. package/lib/module/primitives/Surface.js.map +1 -0
  43. package/lib/module/primitives/Text.js +70 -0
  44. package/lib/module/primitives/Text.js.map +1 -0
  45. package/lib/module/theme/ThemeProvider.js +127 -0
  46. package/lib/module/theme/ThemeProvider.js.map +1 -0
  47. package/lib/module/theme/fonts.js +56 -0
  48. package/lib/module/theme/fonts.js.map +1 -0
  49. package/lib/module/theme/haptics.js +56 -0
  50. package/lib/module/theme/haptics.js.map +1 -0
  51. package/lib/module/theme/motion.js +89 -0
  52. package/lib/module/theme/motion.js.map +1 -0
  53. package/lib/module/theme/navigation.js +99 -0
  54. package/lib/module/theme/navigation.js.map +1 -0
  55. package/lib/typescript/components/actions.d.ts +53 -0
  56. package/lib/typescript/components/actions.d.ts.map +1 -0
  57. package/lib/typescript/components/containers.d.ts +59 -0
  58. package/lib/typescript/components/containers.d.ts.map +1 -0
  59. package/lib/typescript/components/display.d.ts +68 -0
  60. package/lib/typescript/components/display.d.ts.map +1 -0
  61. package/lib/typescript/components/feedback.d.ts +96 -0
  62. package/lib/typescript/components/feedback.d.ts.map +1 -0
  63. package/lib/typescript/components/inputs.d.ts +141 -0
  64. package/lib/typescript/components/inputs.d.ts.map +1 -0
  65. package/lib/typescript/components/mobile.d.ts +173 -0
  66. package/lib/typescript/components/mobile.d.ts.map +1 -0
  67. package/lib/typescript/components/overlays.d.ts +74 -0
  68. package/lib/typescript/components/overlays.d.ts.map +1 -0
  69. package/lib/typescript/components/shared.d.ts +20 -0
  70. package/lib/typescript/components/shared.d.ts.map +1 -0
  71. package/lib/typescript/hooks/useControllableState.d.ts +6 -0
  72. package/lib/typescript/hooks/useControllableState.d.ts.map +1 -0
  73. package/lib/typescript/index.d.ts +39 -0
  74. package/lib/typescript/index.d.ts.map +1 -0
  75. package/lib/typescript/package.json +1 -0
  76. package/lib/typescript/patterns/app.d.ts +139 -0
  77. package/lib/typescript/patterns/app.d.ts.map +1 -0
  78. package/lib/typescript/patterns/auth.d.ts +54 -0
  79. package/lib/typescript/patterns/auth.d.ts.map +1 -0
  80. package/lib/typescript/patterns/home.d.ts +50 -0
  81. package/lib/typescript/patterns/home.d.ts.map +1 -0
  82. package/lib/typescript/patterns/index.d.ts +9 -0
  83. package/lib/typescript/patterns/index.d.ts.map +1 -0
  84. package/lib/typescript/patterns/money.d.ts +106 -0
  85. package/lib/typescript/patterns/money.d.ts.map +1 -0
  86. package/lib/typescript/primitives/Icon.d.ts +12 -0
  87. package/lib/typescript/primitives/Icon.d.ts.map +1 -0
  88. package/lib/typescript/primitives/Layout.d.ts +26 -0
  89. package/lib/typescript/primitives/Layout.d.ts.map +1 -0
  90. package/lib/typescript/primitives/Pressable.d.ts +21 -0
  91. package/lib/typescript/primitives/Pressable.d.ts.map +1 -0
  92. package/lib/typescript/primitives/ScreenContainer.d.ts +25 -0
  93. package/lib/typescript/primitives/ScreenContainer.d.ts.map +1 -0
  94. package/lib/typescript/primitives/Surface.d.ts +23 -0
  95. package/lib/typescript/primitives/Surface.d.ts.map +1 -0
  96. package/lib/typescript/primitives/Text.d.ts +21 -0
  97. package/lib/typescript/primitives/Text.d.ts.map +1 -0
  98. package/lib/typescript/theme/ThemeProvider.d.ts +69 -0
  99. package/lib/typescript/theme/ThemeProvider.d.ts.map +1 -0
  100. package/lib/typescript/theme/fonts.d.ts +25 -0
  101. package/lib/typescript/theme/fonts.d.ts.map +1 -0
  102. package/lib/typescript/theme/haptics.d.ts +3 -0
  103. package/lib/typescript/theme/haptics.d.ts.map +1 -0
  104. package/lib/typescript/theme/motion.d.ts +79 -0
  105. package/lib/typescript/theme/motion.d.ts.map +1 -0
  106. package/lib/typescript/theme/navigation.d.ts +158 -0
  107. package/lib/typescript/theme/navigation.d.ts.map +1 -0
  108. package/package.json +138 -3
  109. package/src/components/actions.tsx +259 -0
  110. package/src/components/containers.tsx +352 -0
  111. package/src/components/display.tsx +345 -0
  112. package/src/components/feedback.tsx +541 -0
  113. package/src/components/inputs.tsx +899 -0
  114. package/src/components/mobile.tsx +883 -0
  115. package/src/components/overlays.tsx +488 -0
  116. package/src/components/shared.ts +54 -0
  117. package/src/hooks/useControllableState.ts +27 -0
  118. package/src/index.ts +177 -0
  119. package/src/patterns/app.tsx +715 -0
  120. package/src/patterns/auth.tsx +266 -0
  121. package/src/patterns/home.tsx +211 -0
  122. package/src/patterns/index.ts +59 -0
  123. package/src/patterns/money.tsx +458 -0
  124. package/src/primitives/Icon.tsx +15 -0
  125. package/src/primitives/Layout.tsx +71 -0
  126. package/src/primitives/Pressable.tsx +97 -0
  127. package/src/primitives/ScreenContainer.tsx +65 -0
  128. package/src/primitives/Surface.tsx +75 -0
  129. package/src/primitives/Text.tsx +79 -0
  130. package/src/theme/ThemeProvider.tsx +210 -0
  131. package/src/theme/fonts.ts +52 -0
  132. package/src/theme/haptics.ts +57 -0
  133. package/src/theme/motion.ts +105 -0
  134. package/src/theme/navigation.ts +68 -0
package/package.json CHANGED
@@ -1,6 +1,141 @@
1
1
  {
2
2
  "name": "@nexub/react-native",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "2.2.0",
4
+ "description": "React Native components for Nexub Design System — native chrome, Nexub content. Same tokens, brands and prop names as @nexub/react, for Expo and bare React Native apps.",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/Nexub-AI/nexub-ds#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/Nexub-AI/nexub-ds.git",
10
+ "directory": "packages/react-native"
11
+ },
12
+ "keywords": [
13
+ "react-native",
14
+ "expo",
15
+ "components",
16
+ "design-system",
17
+ "nexub"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "files": [
23
+ "src",
24
+ "lib",
25
+ "!src/__tests__"
26
+ ],
27
+ "sideEffects": false,
28
+ "main": "./lib/module/index.js",
29
+ "types": "./lib/typescript/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "source": "./src/index.ts",
33
+ "types": "./lib/typescript/index.d.ts",
34
+ "default": "./lib/module/index.js"
35
+ },
36
+ "./patterns": {
37
+ "source": "./src/patterns/index.ts",
38
+ "types": "./lib/typescript/patterns/index.d.ts",
39
+ "default": "./lib/module/patterns/index.js"
40
+ },
41
+ "./package.json": "./package.json"
42
+ },
43
+ "react-native-builder-bob": {
44
+ "source": "src",
45
+ "output": "lib",
46
+ "targets": [
47
+ [
48
+ "module",
49
+ {
50
+ "esm": true
51
+ }
52
+ ],
53
+ [
54
+ "typescript",
55
+ {
56
+ "project": "tsconfig.build.json"
57
+ }
58
+ ]
59
+ ]
60
+ },
61
+ "dependencies": {
62
+ "@expo-google-fonts/geist-mono": "^0.4.3",
63
+ "@expo-google-fonts/inter": "^0.4.2",
64
+ "@expo-google-fonts/plus-jakarta-sans": "^0.4.2",
65
+ "lucide-react-native": "^1.64.0",
66
+ "@nexub/core": "2.2.0",
67
+ "@nexub/icons": "2.2.0",
68
+ "@nexub/tokens": "2.2.0"
69
+ },
70
+ "peerDependencies": {
71
+ "expo-glass-effect": ">=57.0.0",
72
+ "expo-haptics": ">=57.0.0",
73
+ "react": ">=19.1.0",
74
+ "react-native": ">=0.86.0",
75
+ "react-native-reanimated": ">=4.5.0",
76
+ "react-native-safe-area-context": ">=5.6.0",
77
+ "react-native-svg": ">=15.0.0",
78
+ "@react-native-community/datetimepicker": ">=8.4.0",
79
+ "expo-screen-capture": ">=57.0.0"
80
+ },
81
+ "peerDependenciesMeta": {
82
+ "expo-glass-effect": {
83
+ "optional": true
84
+ },
85
+ "expo-haptics": {
86
+ "optional": true
87
+ },
88
+ "@react-native-community/datetimepicker": {
89
+ "optional": true
90
+ },
91
+ "expo-screen-capture": {
92
+ "optional": true
93
+ }
94
+ },
95
+ "devDependencies": {
96
+ "@testing-library/react-native": "^14.1.0",
97
+ "@types/jest": "^30.0.0",
98
+ "@types/react": "~19.3.0",
99
+ "expo": "~58.0.21",
100
+ "expo-glass-effect": "~58.0.5",
101
+ "expo-haptics": "~58.0.7",
102
+ "jest": "^30.2.0",
103
+ "jest-expo": "~58.0.13",
104
+ "react": "19.3.0",
105
+ "react-native": "0.88.1",
106
+ "react-native-builder-bob": "^0.44.1",
107
+ "react-native-reanimated": "4.7.1",
108
+ "react-native-safe-area-context": "~5.10.1",
109
+ "react-native-svg": "15.16.0",
110
+ "react-native-worklets": "0.13.0",
111
+ "typescript": "^5.9.3",
112
+ "@react-native-community/datetimepicker": "9.2.1",
113
+ "expo-screen-capture": "~58.0.6"
114
+ },
115
+ "jest": {
116
+ "preset": "jest-expo",
117
+ "roots": [
118
+ "<rootDir>/src"
119
+ ],
120
+ "setupFiles": [
121
+ "<rootDir>/jest.setup.js"
122
+ ],
123
+ "transformIgnorePatterns": [
124
+ "node_modules/(?!(\\.pnpm|((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|react-native-svg|lucide-react-native|@nexub/.*|@material/.*))"
125
+ ],
126
+ "transform": {
127
+ "^.+\\.mjs$": "babel-jest"
128
+ },
129
+ "moduleNameMapper": {
130
+ "^react$": "<rootDir>/node_modules/react",
131
+ "^react/(.*)$": "<rootDir>/node_modules/react/$1"
132
+ }
133
+ },
134
+ "scripts": {
135
+ "build": "bob build",
136
+ "clean": "rm -rf lib",
137
+ "typecheck": "tsc --noEmit",
138
+ "lint": "eslint src",
139
+ "test": "jest"
140
+ }
6
141
  }
@@ -0,0 +1,259 @@
1
+ import * as React from 'react';
2
+ import { ActivityIndicator, View, type StyleProp, type ViewStyle } from 'react-native';
3
+ import type { ControlSize, ToneValue } from '@nexub/core';
4
+ import type { NativeIconProps } from '@nexub/icons/native';
5
+ import { Pressable, type PressableProps } from '../primitives/Pressable';
6
+ import { Surface } from '../primitives/Surface';
7
+ import { Text } from '../primitives/Text';
8
+ import { useTheme, type RoleName } from '../theme/ThemeProvider';
9
+ import type { HapticToken } from '../theme/haptics';
10
+ import { TONE_ROLES, controlHeight, disabledOpacity, hitSlopFor } from './shared';
11
+
12
+ export type ButtonVariant = 'filled' | 'tonal' | 'outlined' | 'text' | 'glass';
13
+
14
+ // [container role | undefined, content role]
15
+ const VARIANT: Record<ButtonVariant, [RoleName | undefined, RoleName]> = {
16
+ filled: ['primary', 'on-primary'],
17
+ tonal: ['secondary-container', 'on-secondary-container'],
18
+ outlined: [undefined, 'on-surface'],
19
+ text: [undefined, 'primary'],
20
+ glass: [undefined, 'on-surface'],
21
+ };
22
+
23
+ const LABEL: Record<ControlSize, 'label-md' | 'label-lg' | 'title-md'> = {
24
+ xs: 'label-md',
25
+ sm: 'label-md',
26
+ md: 'label-lg',
27
+ lg: 'label-lg',
28
+ xl: 'title-md',
29
+ };
30
+ const PAD: Record<ControlSize, number> = { xs: 10, sm: 14, md: 20, lg: 24, xl: 28 };
31
+ const ICON: Record<ControlSize, NativeIconProps['size']> = {
32
+ xs: 'xs',
33
+ sm: 'sm',
34
+ md: 'sm',
35
+ lg: 'md',
36
+ xl: 'md',
37
+ };
38
+
39
+ export interface ButtonProps extends Omit<PressableProps, 'children'> {
40
+ children: React.ReactNode;
41
+ /** @default 'filled' */
42
+ variant?: ButtonVariant;
43
+ /** Solid semantic or product colour (overrides the variant colour). */
44
+ tone?: ToneValue;
45
+ /** The control ladder. Primary actions on mobile use 'lg' (48). @default 'md' */
46
+ size?: ControlSize;
47
+ /** Shows a spinner, blocks presses and announces `loadingLabel`. */
48
+ loading?: boolean;
49
+ loadingLabel?: string;
50
+ fullWidth?: boolean;
51
+ leadingIcon?: React.ComponentType<NativeIconProps>;
52
+ trailingIcon?: React.ComponentType<NativeIconProps>;
53
+ }
54
+
55
+ /** Capsule button. One `filled` (or toned) action per screen; the rest tonal/outlined/text. */
56
+ export function Button({
57
+ children,
58
+ variant = 'filled',
59
+ tone,
60
+ size = 'md',
61
+ loading,
62
+ loadingLabel,
63
+ fullWidth,
64
+ leadingIcon: Leading,
65
+ trailingIcon: Trailing,
66
+ disabled,
67
+ style,
68
+ accessibilityLabel,
69
+ ...rest
70
+ }: ButtonProps) {
71
+ const theme = useTheme();
72
+ const { roles } = theme;
73
+ const [bgRole, fgRole] = tone ? TONE_ROLES[tone] : VARIANT[variant];
74
+ const height = controlHeight(theme, size);
75
+ const fg = roles[fgRole];
76
+ const inactive = disabled || loading;
77
+ const label = typeof children === 'string' ? children : undefined;
78
+
79
+ const body = (
80
+ <View
81
+ style={{
82
+ flexDirection: 'row',
83
+ alignItems: 'center',
84
+ justifyContent: 'center',
85
+ gap: 8,
86
+ opacity: disabled ? disabledOpacity(theme) : 1,
87
+ }}
88
+ >
89
+ {loading ? (
90
+ <ActivityIndicator size="small" color={fg} />
91
+ ) : (
92
+ Leading && <Leading size={ICON[size]} color={fg} />
93
+ )}
94
+ {typeof children === 'string' ? (
95
+ <Text variant={LABEL[size]} style={{ color: fg }} numberOfLines={1}>
96
+ {children}
97
+ </Text>
98
+ ) : (
99
+ children
100
+ )}
101
+ {!loading && Trailing && <Trailing size={ICON[size]} color={fg} />}
102
+ </View>
103
+ );
104
+
105
+ const shape: StyleProp<ViewStyle> = [
106
+ {
107
+ height,
108
+ paddingHorizontal: PAD[size],
109
+ borderRadius: theme.tokens.shape.control,
110
+ justifyContent: 'center',
111
+ alignSelf: fullWidth ? 'stretch' : 'flex-start',
112
+ backgroundColor: bgRole ? roles[bgRole] : 'transparent',
113
+ },
114
+ variant === 'outlined' && !tone && { borderWidth: 1, borderColor: roles['outline-variant'] },
115
+ disabled && bgRole && { backgroundColor: roles['on-surface'] + '1f' }, // disabled-container 12%
116
+ style,
117
+ ];
118
+
119
+ return (
120
+ <Pressable
121
+ accessibilityLabel={accessibilityLabel ?? (loading && loadingLabel ? loadingLabel : label)}
122
+ accessibilityState={{ disabled: !!inactive, busy: !!loading }}
123
+ disabled={inactive}
124
+ hitSlop={hitSlopFor(height)}
125
+ stateLayer={fgRole}
126
+ style={
127
+ variant === 'glass' && !tone ? { alignSelf: fullWidth ? 'stretch' : 'flex-start' } : shape
128
+ }
129
+ {...rest}
130
+ >
131
+ {variant === 'glass' && !tone ? (
132
+ <Surface
133
+ material="glass"
134
+ radius="control"
135
+ style={[{ height, paddingHorizontal: PAD[size], justifyContent: 'center' }, style]}
136
+ >
137
+ {body}
138
+ </Surface>
139
+ ) : (
140
+ body
141
+ )}
142
+ </Pressable>
143
+ );
144
+ }
145
+
146
+ export interface IconButtonProps extends Omit<PressableProps, 'children'> {
147
+ icon: React.ComponentType<NativeIconProps>;
148
+ /** Required: the accessible name (what it does, not "button"). */
149
+ label: string;
150
+ /** @default 'text' */
151
+ variant?: ButtonVariant;
152
+ tone?: ToneValue;
153
+ /** @default 'md' */
154
+ size?: ControlSize;
155
+ /** Toggle state for icon toggles (favourite, mute). */
156
+ selected?: boolean;
157
+ haptic?: HapticToken;
158
+ }
159
+
160
+ /** Circular icon-only button; always 44pt+ to touch. */
161
+ export function IconButton({
162
+ icon: Glyph,
163
+ label,
164
+ variant = 'text',
165
+ tone,
166
+ size = 'md',
167
+ selected,
168
+ disabled,
169
+ style,
170
+ ...rest
171
+ }: IconButtonProps) {
172
+ const theme = useTheme();
173
+ const [bgRole, fgRole] = tone ? TONE_ROLES[tone] : VARIANT[variant];
174
+ const fgSelected: RoleName = selected
175
+ ? 'primary'
176
+ : variant === 'text' && !tone
177
+ ? 'on-surface-variant'
178
+ : fgRole;
179
+ const height = controlHeight(theme, size);
180
+ return (
181
+ <Pressable
182
+ accessibilityLabel={label}
183
+ accessibilityState={{ disabled: !!disabled, selected }}
184
+ disabled={disabled}
185
+ hitSlop={hitSlopFor(height)}
186
+ stateLayer={fgRole}
187
+ style={[
188
+ {
189
+ width: height,
190
+ height,
191
+ borderRadius: height / 2,
192
+ alignItems: 'center',
193
+ justifyContent: 'center',
194
+ backgroundColor: bgRole ? theme.roles[bgRole] : 'transparent',
195
+ opacity: disabled ? disabledOpacity(theme) : 1,
196
+ },
197
+ variant === 'outlined' && { borderWidth: 1, borderColor: theme.roles['outline-variant'] },
198
+ style,
199
+ ]}
200
+ {...rest}
201
+ >
202
+ <Glyph
203
+ size={ICON[size] === 'xs' ? 'sm' : size === 'xl' ? 'lg' : 'md'}
204
+ color={theme.roles[fgSelected]}
205
+ />
206
+ </Pressable>
207
+ );
208
+ }
209
+
210
+ export interface ButtonGroupProps {
211
+ children: React.ReactNode;
212
+ /** 'stack' puts actions full-width in a column (bottom of forms). @default 'row' */
213
+ direction?: 'row' | 'stack';
214
+ style?: StyleProp<ViewStyle>;
215
+ }
216
+
217
+ /** Related actions together. In a stack, put the primary action first. */
218
+ export function ButtonGroup({ children, direction = 'row', style }: ButtonGroupProps) {
219
+ const { tokens } = useTheme();
220
+ return (
221
+ <View
222
+ style={[
223
+ direction === 'row'
224
+ ? { flexDirection: 'row', flexWrap: 'wrap', gap: tokens.spacing[2] }
225
+ : { gap: tokens.spacing[3], alignItems: 'stretch' },
226
+ style,
227
+ ]}
228
+ >
229
+ {direction === 'stack'
230
+ ? React.Children.map(children, (c) =>
231
+ React.isValidElement<ButtonProps>(c) ? React.cloneElement(c, { fullWidth: true }) : c,
232
+ )
233
+ : children}
234
+ </View>
235
+ );
236
+ }
237
+
238
+ export interface LinkProps extends Omit<PressableProps, 'children'> {
239
+ children: string;
240
+ }
241
+
242
+ /** Inline text action. Opens nothing by itself: pass `onPress` (e.g. router.push / Linking.openURL). */
243
+ export function Link({ children, style, ...rest }: LinkProps) {
244
+ return (
245
+ <Pressable
246
+ accessibilityRole="link"
247
+ accessibilityLabel={children}
248
+ stateLayer={false}
249
+ pressScale={false}
250
+ hitSlop={8}
251
+ style={style}
252
+ {...rest}
253
+ >
254
+ <Text variant="label-lg" color="primary" style={{ textDecorationLine: 'underline' }}>
255
+ {children}
256
+ </Text>
257
+ </Pressable>
258
+ );
259
+ }