@nexub/react-native 0.0.0-stage → 2.1.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 (124) hide show
  1. package/README.md +111 -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 +292 -0
  5. package/lib/module/components/containers.js.map +1 -0
  6. package/lib/module/components/display.js +266 -0
  7. package/lib/module/components/display.js.map +1 -0
  8. package/lib/module/components/feedback.js +464 -0
  9. package/lib/module/components/feedback.js.map +1 -0
  10. package/lib/module/components/inputs.js +746 -0
  11. package/lib/module/components/inputs.js.map +1 -0
  12. package/lib/module/components/mobile.js +811 -0
  13. package/lib/module/components/mobile.js.map +1 -0
  14. package/lib/module/components/overlays.js +418 -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 +30 -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/index.js +7 -0
  28. package/lib/module/patterns/index.js.map +1 -0
  29. package/lib/module/patterns/money.js +419 -0
  30. package/lib/module/patterns/money.js.map +1 -0
  31. package/lib/module/primitives/Icon.js +17 -0
  32. package/lib/module/primitives/Icon.js.map +1 -0
  33. package/lib/module/primitives/Layout.js +79 -0
  34. package/lib/module/primitives/Layout.js.map +1 -0
  35. package/lib/module/primitives/Pressable.js +77 -0
  36. package/lib/module/primitives/Pressable.js.map +1 -0
  37. package/lib/module/primitives/ScreenContainer.js +58 -0
  38. package/lib/module/primitives/ScreenContainer.js.map +1 -0
  39. package/lib/module/primitives/Surface.js +57 -0
  40. package/lib/module/primitives/Surface.js.map +1 -0
  41. package/lib/module/primitives/Text.js +70 -0
  42. package/lib/module/primitives/Text.js.map +1 -0
  43. package/lib/module/theme/ThemeProvider.js +127 -0
  44. package/lib/module/theme/ThemeProvider.js.map +1 -0
  45. package/lib/module/theme/fonts.js +56 -0
  46. package/lib/module/theme/fonts.js.map +1 -0
  47. package/lib/module/theme/haptics.js +56 -0
  48. package/lib/module/theme/haptics.js.map +1 -0
  49. package/lib/module/theme/navigation.js +99 -0
  50. package/lib/module/theme/navigation.js.map +1 -0
  51. package/lib/typescript/components/actions.d.ts +53 -0
  52. package/lib/typescript/components/actions.d.ts.map +1 -0
  53. package/lib/typescript/components/containers.d.ts +59 -0
  54. package/lib/typescript/components/containers.d.ts.map +1 -0
  55. package/lib/typescript/components/display.d.ts +62 -0
  56. package/lib/typescript/components/display.d.ts.map +1 -0
  57. package/lib/typescript/components/feedback.d.ts +93 -0
  58. package/lib/typescript/components/feedback.d.ts.map +1 -0
  59. package/lib/typescript/components/inputs.d.ts +141 -0
  60. package/lib/typescript/components/inputs.d.ts.map +1 -0
  61. package/lib/typescript/components/mobile.d.ts +173 -0
  62. package/lib/typescript/components/mobile.d.ts.map +1 -0
  63. package/lib/typescript/components/overlays.d.ts +74 -0
  64. package/lib/typescript/components/overlays.d.ts.map +1 -0
  65. package/lib/typescript/components/shared.d.ts +20 -0
  66. package/lib/typescript/components/shared.d.ts.map +1 -0
  67. package/lib/typescript/hooks/useControllableState.d.ts +6 -0
  68. package/lib/typescript/hooks/useControllableState.d.ts.map +1 -0
  69. package/lib/typescript/index.d.ts +37 -0
  70. package/lib/typescript/index.d.ts.map +1 -0
  71. package/lib/typescript/package.json +1 -0
  72. package/lib/typescript/patterns/app.d.ts +139 -0
  73. package/lib/typescript/patterns/app.d.ts.map +1 -0
  74. package/lib/typescript/patterns/auth.d.ts +54 -0
  75. package/lib/typescript/patterns/auth.d.ts.map +1 -0
  76. package/lib/typescript/patterns/index.d.ts +7 -0
  77. package/lib/typescript/patterns/index.d.ts.map +1 -0
  78. package/lib/typescript/patterns/money.d.ts +106 -0
  79. package/lib/typescript/patterns/money.d.ts.map +1 -0
  80. package/lib/typescript/primitives/Icon.d.ts +12 -0
  81. package/lib/typescript/primitives/Icon.d.ts.map +1 -0
  82. package/lib/typescript/primitives/Layout.d.ts +26 -0
  83. package/lib/typescript/primitives/Layout.d.ts.map +1 -0
  84. package/lib/typescript/primitives/Pressable.d.ts +21 -0
  85. package/lib/typescript/primitives/Pressable.d.ts.map +1 -0
  86. package/lib/typescript/primitives/ScreenContainer.d.ts +25 -0
  87. package/lib/typescript/primitives/ScreenContainer.d.ts.map +1 -0
  88. package/lib/typescript/primitives/Surface.d.ts +23 -0
  89. package/lib/typescript/primitives/Surface.d.ts.map +1 -0
  90. package/lib/typescript/primitives/Text.d.ts +21 -0
  91. package/lib/typescript/primitives/Text.d.ts.map +1 -0
  92. package/lib/typescript/theme/ThemeProvider.d.ts +69 -0
  93. package/lib/typescript/theme/ThemeProvider.d.ts.map +1 -0
  94. package/lib/typescript/theme/fonts.d.ts +25 -0
  95. package/lib/typescript/theme/fonts.d.ts.map +1 -0
  96. package/lib/typescript/theme/haptics.d.ts +3 -0
  97. package/lib/typescript/theme/haptics.d.ts.map +1 -0
  98. package/lib/typescript/theme/navigation.d.ts +158 -0
  99. package/lib/typescript/theme/navigation.d.ts.map +1 -0
  100. package/package.json +138 -3
  101. package/src/components/actions.tsx +259 -0
  102. package/src/components/containers.tsx +341 -0
  103. package/src/components/display.tsx +314 -0
  104. package/src/components/feedback.tsx +522 -0
  105. package/src/components/inputs.tsx +851 -0
  106. package/src/components/mobile.tsx +879 -0
  107. package/src/components/overlays.tsx +485 -0
  108. package/src/components/shared.ts +54 -0
  109. package/src/hooks/useControllableState.ts +27 -0
  110. package/src/index.ts +175 -0
  111. package/src/patterns/app.tsx +715 -0
  112. package/src/patterns/auth.tsx +266 -0
  113. package/src/patterns/index.ts +57 -0
  114. package/src/patterns/money.tsx +458 -0
  115. package/src/primitives/Icon.tsx +15 -0
  116. package/src/primitives/Layout.tsx +71 -0
  117. package/src/primitives/Pressable.tsx +94 -0
  118. package/src/primitives/ScreenContainer.tsx +65 -0
  119. package/src/primitives/Surface.tsx +75 -0
  120. package/src/primitives/Text.tsx +79 -0
  121. package/src/theme/ThemeProvider.tsx +210 -0
  122. package/src/theme/fonts.ts +52 -0
  123. package/src/theme/haptics.ts +57 -0
  124. package/src/theme/navigation.ts +68 -0
@@ -0,0 +1,69 @@
1
+ import * as React from 'react';
2
+ import { native, type NativeRoles } from '@nexub/tokens/native';
3
+ import { type ContrastLevel, type Direction, type GlassMode, type NexubMessages, type Product } from '@nexub/core';
4
+ import { type HapticToken } from './haptics.js';
5
+ import type { FontSource } from './fonts.js';
6
+ export type Scheme = 'light' | 'dark';
7
+ export type Roles = {
8
+ -readonly [K in keyof NativeRoles]: string;
9
+ };
10
+ export type RoleName = keyof Roles;
11
+ export type ElevationLevel = 0 | 1 | 2 | 3;
12
+ export interface NexubTheme {
13
+ scheme: Scheme;
14
+ contrast: ContrastLevel;
15
+ product: Product | undefined;
16
+ /** Colour roles, already resolved for scheme, contrast, product and seed. */
17
+ roles: Roles;
18
+ /** Every native token (spacing, shape, size, type, state, motion). */
19
+ tokens: typeof native;
20
+ /** `boxShadow` for an elevation level, undefined for 0. */
21
+ elevation: (level: ElevationLevel) => string | undefined;
22
+ /** Effective glass: 'off' when the user has Reduce Transparency on. */
23
+ glass: GlassMode;
24
+ reducedMotion: boolean;
25
+ haptics: boolean;
26
+ fonts: FontSource;
27
+ locale: string;
28
+ dir: Direction;
29
+ messages: NexubMessages;
30
+ }
31
+ export interface NexubNativeProviderProps {
32
+ /** @default 'system' */
33
+ scheme?: Scheme | 'system';
34
+ /** 'system' = iOS Increase Contrast / Android high-contrast text → 'high'. @default 'system' */
35
+ contrast?: ContrastLevel | 'system';
36
+ /** Product brand: remaps the tertiary role family. */
37
+ product?: Product;
38
+ /** White-label seed colour (#rgb / #rrggbb); regenerates the `seedTarget` family. */
39
+ seed?: string;
40
+ /** @default 'primary' */
41
+ seedTarget?: 'primary' | 'tertiary';
42
+ /** Glass intensity (the app persists the user's choice). @default 'balanced' */
43
+ glass?: GlassMode;
44
+ /** Haptics on/off (user setting). @default true */
45
+ haptics?: boolean;
46
+ /** 'nexub' needs `nexubFonts` loaded; 'system' uses the platform font. @default 'nexub' */
47
+ fonts?: FontSource;
48
+ /** BCP 47 locale for Intl formatting. @default 'en' */
49
+ locale?: string;
50
+ /** @default derived from `locale` */
51
+ dir?: Direction;
52
+ /** Overrides for the default English strings. */
53
+ messages?: Partial<NexubMessages>;
54
+ children?: React.ReactNode;
55
+ }
56
+ /**
57
+ * Theme, accessibility preferences, locale and strings for every
58
+ * @nexub/react-native component. Wrap the app once, inside the safe-area provider.
59
+ */
60
+ export declare function NexubNativeProvider({ scheme: schemeProp, contrast: contrastProp, product, seed, seedTarget, glass, haptics, fonts, locale, dir, messages, children, }: NexubNativeProviderProps): React.JSX.Element;
61
+ /** The active theme. Throws outside NexubNativeProvider. */
62
+ export declare function useTheme(): NexubTheme;
63
+ /** Shorthand for `useTheme().roles`. */
64
+ export declare const useRoles: () => Roles;
65
+ /** The active strings: defaults merged with the provider's overrides. */
66
+ export declare const useNexubMessages: () => NexubMessages;
67
+ /** Plays a semantic haptic, unless the user turned haptics off. */
68
+ export declare function useHaptics(): (token: HapticToken) => void;
69
+ //# sourceMappingURL=ThemeProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../../src/theme/ThemeProvider.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ export declare const nexubFonts: {
2
+ Inter_400Regular: number;
3
+ Inter_500Medium: number;
4
+ Inter_600SemiBold: number;
5
+ Inter_700Bold: number;
6
+ PlusJakartaSans_600SemiBold: number;
7
+ PlusJakartaSans_700Bold: number;
8
+ PlusJakartaSans_800ExtraBold: number;
9
+ GeistMono_400Regular: number;
10
+ };
11
+ export type FontFamily = 'sans' | 'display' | 'mono';
12
+ export type FontSource = 'nexub' | 'system';
13
+ /**
14
+ * Style for a family + weight. With static files the weight lives in the file
15
+ * name, so `fontWeight` is only set for the system font (Android picks the
16
+ * wrong file when both are set).
17
+ */
18
+ export declare function fontStyle(family: FontFamily, weight: number, source: FontSource): {
19
+ fontWeight: "400";
20
+ fontFamily?: undefined;
21
+ } | {
22
+ fontFamily: string | undefined;
23
+ fontWeight?: undefined;
24
+ };
25
+ //# sourceMappingURL=fonts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fonts.d.ts","sourceRoot":"","sources":["../../../src/theme/fonts.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,UAAU;;;;;;;;;CAStB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;AACrD,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;AAiB5C;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU;gBACd,KAAK;;;;;EAGtE"}
@@ -0,0 +1,3 @@
1
+ export type HapticToken = 'selection' | 'toggleOn' | 'toggleOff' | 'confirm' | 'reject' | 'warning' | 'snap';
2
+ export declare function playHaptic(token: HapticToken): void;
3
+ //# sourceMappingURL=haptics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"haptics.d.ts","sourceRoot":"","sources":["../../../src/theme/haptics.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GACrB,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAqBrF,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CA6BnD"}
@@ -0,0 +1,158 @@
1
+ import { type NexubTheme } from './ThemeProvider.js';
2
+ /** Native stack `screenOptions`: large titles on a surface-coloured native header, Nexub type and tint. */
3
+ export declare function nexubStackOptions(theme: NexubTheme): {
4
+ headerLargeTitle: boolean;
5
+ headerTransparent: boolean;
6
+ headerShadowVisible: boolean;
7
+ headerLargeTitleShadowVisible: boolean;
8
+ headerTintColor: string;
9
+ headerStyle: {
10
+ backgroundColor: string;
11
+ };
12
+ headerTitleStyle: {
13
+ fontWeight: "400";
14
+ fontFamily?: undefined;
15
+ color: string;
16
+ } | {
17
+ fontFamily: string | undefined;
18
+ fontWeight?: undefined;
19
+ color: string;
20
+ };
21
+ headerLargeTitleStyle: {
22
+ fontWeight: "400";
23
+ fontFamily?: undefined;
24
+ color: string;
25
+ } | {
26
+ fontFamily: string | undefined;
27
+ fontWeight?: undefined;
28
+ color: string;
29
+ };
30
+ contentStyle: {
31
+ backgroundColor: string;
32
+ };
33
+ };
34
+ /** React Navigation `theme` (also Expo Router's ThemeProvider). */
35
+ export declare function nexubNavigationTheme(theme: NexubTheme): {
36
+ dark: boolean;
37
+ colors: {
38
+ primary: string;
39
+ background: string;
40
+ card: string;
41
+ text: string;
42
+ border: string;
43
+ notification: string;
44
+ };
45
+ fonts: {
46
+ regular: {
47
+ fontFamily: string;
48
+ fontWeight: "400";
49
+ };
50
+ medium: {
51
+ fontFamily: string;
52
+ fontWeight: "400";
53
+ };
54
+ bold: {
55
+ fontFamily: string;
56
+ fontWeight: "400";
57
+ };
58
+ heavy: {
59
+ fontFamily: string;
60
+ fontWeight: "400";
61
+ };
62
+ };
63
+ };
64
+ /** Colours for native tab bars (Expo Router NativeTabs / react-native-bottom-tabs). */
65
+ export declare function nexubTabsOptions(theme: NexubTheme): {
66
+ tintColor: string;
67
+ inactiveTintColor: string;
68
+ backgroundColor: string;
69
+ badgeBackgroundColor: string;
70
+ labelStyle: {
71
+ fontSize: number;
72
+ fontWeight: "400";
73
+ fontFamily?: undefined;
74
+ } | {
75
+ fontSize: number;
76
+ fontFamily: string | undefined;
77
+ fontWeight?: undefined;
78
+ };
79
+ };
80
+ /** All three for the current theme: `const nav = useNexubNavigation()`. */
81
+ export declare function useNexubNavigation(): {
82
+ stack: {
83
+ headerLargeTitle: boolean;
84
+ headerTransparent: boolean;
85
+ headerShadowVisible: boolean;
86
+ headerLargeTitleShadowVisible: boolean;
87
+ headerTintColor: string;
88
+ headerStyle: {
89
+ backgroundColor: string;
90
+ };
91
+ headerTitleStyle: {
92
+ fontWeight: "400";
93
+ fontFamily?: undefined;
94
+ color: string;
95
+ } | {
96
+ fontFamily: string | undefined;
97
+ fontWeight?: undefined;
98
+ color: string;
99
+ };
100
+ headerLargeTitleStyle: {
101
+ fontWeight: "400";
102
+ fontFamily?: undefined;
103
+ color: string;
104
+ } | {
105
+ fontFamily: string | undefined;
106
+ fontWeight?: undefined;
107
+ color: string;
108
+ };
109
+ contentStyle: {
110
+ backgroundColor: string;
111
+ };
112
+ };
113
+ theme: {
114
+ dark: boolean;
115
+ colors: {
116
+ primary: string;
117
+ background: string;
118
+ card: string;
119
+ text: string;
120
+ border: string;
121
+ notification: string;
122
+ };
123
+ fonts: {
124
+ regular: {
125
+ fontFamily: string;
126
+ fontWeight: "400";
127
+ };
128
+ medium: {
129
+ fontFamily: string;
130
+ fontWeight: "400";
131
+ };
132
+ bold: {
133
+ fontFamily: string;
134
+ fontWeight: "400";
135
+ };
136
+ heavy: {
137
+ fontFamily: string;
138
+ fontWeight: "400";
139
+ };
140
+ };
141
+ };
142
+ tabs: {
143
+ tintColor: string;
144
+ inactiveTintColor: string;
145
+ backgroundColor: string;
146
+ badgeBackgroundColor: string;
147
+ labelStyle: {
148
+ fontSize: number;
149
+ fontWeight: "400";
150
+ fontFamily?: undefined;
151
+ } | {
152
+ fontSize: number;
153
+ fontFamily: string | undefined;
154
+ fontWeight?: undefined;
155
+ };
156
+ };
157
+ };
158
+ //# sourceMappingURL=navigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../../src/theme/navigation.ts"],"names":[],"mappings":"AAGA,OAAO,EAAY,KAAK,UAAU,EAAE,MAAM,oBAAiB,CAAC;AAE5D,2GAA2G;AAC3G,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAelD;AAED,mEAAmE;AACnE,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU;;;;;;;;;;;;;wBAMlB,KAAK;;;;wBAAL,KAAK;;;;wBAAL,KAAK;;;;wBAAL,KAAK;;;EAexC;AAED,uFAAuF;AACvF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU;;;;;;;;;;;;;;EASjD;AAED,2EAA2E;AAC3E,wBAAgB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BA9BE,KAAK;;;;4BAAL,KAAK;;;;4BAAL,KAAK;;;;4BAAL,KAAK;;;;;;;;;;;;;;;;;;;EAqCxC"}
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.1.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.1.0",
67
+ "@nexub/icons": "2.1.0",
68
+ "@nexub/tokens": "2.1.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
+ }