@webority-technologies/mobile 0.0.6 → 0.0.8
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.
- package/README.md +3 -463
- package/lib/commonjs/components/Badge/Badge.js +23 -13
- package/lib/commonjs/components/BottomSheet/BottomSheet.js +39 -5
- package/lib/commonjs/components/Button/Button.js +25 -6
- package/lib/commonjs/components/Card/Card.js +13 -1
- package/lib/commonjs/components/Checkbox/Checkbox.js +5 -3
- package/lib/commonjs/components/Chip/Chip.js +12 -3
- package/lib/commonjs/components/Dialog/Dialog.js +15 -8
- package/lib/commonjs/components/EmptyState/EmptyState.js +32 -26
- package/lib/commonjs/components/FormField/FormField.js +3 -3
- package/lib/commonjs/components/Input/Input.js +13 -5
- package/lib/commonjs/components/ListItem/ListItem.js +33 -27
- package/lib/commonjs/components/OTPInput/OTPInput.js +6 -3
- package/lib/commonjs/components/Radio/Radio.js +7 -6
- package/lib/commonjs/components/SearchBar/SearchBar.js +9 -5
- package/lib/commonjs/components/Skeleton/Skeleton.js +20 -12
- package/lib/commonjs/components/Skeleton/SkeletonContent.js +25 -9
- package/lib/commonjs/components/Skeleton/SkeletonList.js +7 -2
- package/lib/commonjs/components/Skeleton/SkeletonProvider.js +48 -0
- package/lib/commonjs/components/Skeleton/SkeletonSkip.js +37 -0
- package/lib/commonjs/components/Skeleton/index.js +20 -0
- package/lib/commonjs/components/Switch/Switch.js +31 -2
- package/lib/commonjs/components/Toast/Toast.js +16 -11
- package/lib/commonjs/components/index.js +18 -0
- package/lib/commonjs/theme/Gradient.js +57 -0
- package/lib/commonjs/theme/index.js +20 -0
- package/lib/commonjs/theme/textStyle.js +37 -0
- package/lib/commonjs/theme/tokens.js +260 -2
- package/lib/module/components/Badge/Badge.js +24 -14
- package/lib/module/components/BottomSheet/BottomSheet.js +40 -6
- package/lib/module/components/Button/Button.js +26 -7
- package/lib/module/components/Card/Card.js +14 -2
- package/lib/module/components/Checkbox/Checkbox.js +5 -3
- package/lib/module/components/Chip/Chip.js +13 -4
- package/lib/module/components/Dialog/Dialog.js +16 -9
- package/lib/module/components/EmptyState/EmptyState.js +33 -27
- package/lib/module/components/FormField/FormField.js +4 -4
- package/lib/module/components/Input/Input.js +14 -6
- package/lib/module/components/ListItem/ListItem.js +34 -28
- package/lib/module/components/OTPInput/OTPInput.js +7 -4
- package/lib/module/components/Radio/Radio.js +7 -6
- package/lib/module/components/SearchBar/SearchBar.js +10 -6
- package/lib/module/components/Skeleton/Skeleton.js +20 -12
- package/lib/module/components/Skeleton/SkeletonContent.js +25 -9
- package/lib/module/components/Skeleton/SkeletonList.js +7 -2
- package/lib/module/components/Skeleton/SkeletonProvider.js +41 -0
- package/lib/module/components/Skeleton/SkeletonSkip.js +31 -0
- package/lib/module/components/Skeleton/index.js +2 -0
- package/lib/module/components/Switch/Switch.js +31 -2
- package/lib/module/components/Toast/Toast.js +17 -12
- package/lib/module/components/index.js +1 -1
- package/lib/module/theme/Gradient.js +50 -0
- package/lib/module/theme/index.js +2 -0
- package/lib/module/theme/textStyle.js +32 -0
- package/lib/module/theme/tokens.js +260 -2
- package/lib/typescript/commonjs/components/BottomSheet/BottomSheet.d.ts +10 -0
- package/lib/typescript/commonjs/components/Button/Button.d.ts +8 -0
- package/lib/typescript/commonjs/components/Card/Card.d.ts +8 -0
- package/lib/typescript/commonjs/components/Dialog/Dialog.d.ts +5 -0
- package/lib/typescript/commonjs/components/Input/Input.d.ts +12 -0
- package/lib/typescript/commonjs/components/Skeleton/Skeleton.d.ts +9 -0
- package/lib/typescript/commonjs/components/Skeleton/SkeletonContent.d.ts +6 -0
- package/lib/typescript/commonjs/components/Skeleton/SkeletonList.d.ts +3 -0
- package/lib/typescript/commonjs/components/Skeleton/SkeletonProvider.d.ts +32 -0
- package/lib/typescript/commonjs/components/Skeleton/SkeletonSkip.d.ts +25 -0
- package/lib/typescript/commonjs/components/Skeleton/index.d.ts +4 -0
- package/lib/typescript/commonjs/components/Switch/Switch.d.ts +5 -0
- package/lib/typescript/commonjs/components/Toast/Toast.d.ts +5 -0
- package/lib/typescript/commonjs/components/index.d.ts +2 -2
- package/lib/typescript/commonjs/theme/Gradient.d.ts +11 -0
- package/lib/typescript/commonjs/theme/index.d.ts +5 -1
- package/lib/typescript/commonjs/theme/textStyle.d.ts +18 -0
- package/lib/typescript/commonjs/theme/types.d.ts +178 -0
- package/lib/typescript/module/components/BottomSheet/BottomSheet.d.ts +10 -0
- package/lib/typescript/module/components/Button/Button.d.ts +8 -0
- package/lib/typescript/module/components/Card/Card.d.ts +8 -0
- package/lib/typescript/module/components/Dialog/Dialog.d.ts +5 -0
- package/lib/typescript/module/components/Input/Input.d.ts +12 -0
- package/lib/typescript/module/components/Skeleton/Skeleton.d.ts +9 -0
- package/lib/typescript/module/components/Skeleton/SkeletonContent.d.ts +6 -0
- package/lib/typescript/module/components/Skeleton/SkeletonList.d.ts +3 -0
- package/lib/typescript/module/components/Skeleton/SkeletonProvider.d.ts +32 -0
- package/lib/typescript/module/components/Skeleton/SkeletonSkip.d.ts +25 -0
- package/lib/typescript/module/components/Skeleton/index.d.ts +4 -0
- package/lib/typescript/module/components/Switch/Switch.d.ts +5 -0
- package/lib/typescript/module/components/Toast/Toast.d.ts +5 -0
- package/lib/typescript/module/components/index.d.ts +2 -2
- package/lib/typescript/module/theme/Gradient.d.ts +11 -0
- package/lib/typescript/module/theme/index.d.ts +5 -1
- package/lib/typescript/module/theme/textStyle.d.ts +18 -0
- package/lib/typescript/module/theme/types.d.ts +178 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,471 +1,11 @@
|
|
|
1
1
|
# @webority-technologies/mobile
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
`v0.0.1` · MIT · React Native 0.81+ · React 19 · TypeScript strict
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Install
|
|
3
|
+
Internal React Native foundation for Webority projects — UI components, theme, API client, auth, logging, storage, formatters, validators, network, permissions, and version-check in one package.
|
|
10
4
|
|
|
11
5
|
```bash
|
|
12
6
|
npm install @webority-technologies/mobile
|
|
13
7
|
```
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
`react` 19.1 and `react-native` 0.81+ are required. Everything else is **optional** — install only what the modules you use need:
|
|
18
|
-
|
|
19
|
-
| Peer dep | Needed for |
|
|
20
|
-
| ------------------------------------------------- | ----------------------------------------------------------------- |
|
|
21
|
-
| `react-native-reanimated` (3.x) | `BottomSheet`, `Drawer`, `Swipeable` (others use native Animated) |
|
|
22
|
-
| `react-native-gesture-handler` (2.x) | `BottomSheet`, swipe-to-dismiss interactions |
|
|
23
|
-
| `react-native-safe-area-context` (5.x) | Layout components, modals, toast positioning |
|
|
24
|
-
| `react-native-vector-icons` (10.x) | Icon-bearing components (`Button`, `Avatar`, etc.) |
|
|
25
|
-
| `@react-native-async-storage/async-storage` (2.x) | `Storage` module + token persistence |
|
|
26
|
-
| `react-native-keychain` (9.x) | Secure credential storage (`storeToken` / `getToken`) |
|
|
27
|
-
| `react-native-device-info` (14.x) | `versionCheck` helpers |
|
|
28
|
-
| `@react-native-community/netinfo` (11.x) | `useNetworkStatus`, `NetworkStatusBanner` |
|
|
29
|
-
| `react-native-permissions` (4.x) | `Permissions` module |
|
|
30
|
-
| `react-native-compressor` (1.10+) | `compressImage` utility |
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
npm install react-native-reanimated \
|
|
34
|
-
react-native-gesture-handler \
|
|
35
|
-
react-native-safe-area-context \
|
|
36
|
-
react-native-vector-icons \
|
|
37
|
-
@react-native-async-storage/async-storage \
|
|
38
|
-
react-native-keychain \
|
|
39
|
-
react-native-device-info \
|
|
40
|
-
@react-native-community/netinfo \
|
|
41
|
-
react-native-permissions \
|
|
42
|
-
react-native-compressor
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Follow each library's native-setup steps (Reanimated babel plugin, Gesture Handler import at the top of the entry file, vector-icons font linking, etc.).
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## Setup
|
|
50
|
-
|
|
51
|
-
Wrap the app with `GestureHandlerRootView`, `SafeAreaProvider`, `ThemeProvider`, and `ToastProvider`. Initialize the SDK once at module scope.
|
|
52
|
-
|
|
53
|
-
```tsx
|
|
54
|
-
// App.tsx
|
|
55
|
-
import 'react-native-gesture-handler';
|
|
56
|
-
import React from 'react';
|
|
57
|
-
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
58
|
-
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
59
|
-
import {
|
|
60
|
-
initWebority,
|
|
61
|
-
initUserAuth,
|
|
62
|
-
ThemeProvider,
|
|
63
|
-
ToastProvider,
|
|
64
|
-
NetworkStatusBanner
|
|
65
|
-
} from '@webority-technologies/mobile';
|
|
66
|
-
import store from './store';
|
|
67
|
-
import { logout, refreshToken } from './store/auth';
|
|
68
|
-
|
|
69
|
-
initWebority({
|
|
70
|
-
baseUrl: 'https://api.example.com',
|
|
71
|
-
environment: __DEV__ ? 'Development' : 'Production',
|
|
72
|
-
country: 'in' // optional, defaults to 'in'
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
initUserAuth(store, { logout, refreshToken });
|
|
76
|
-
|
|
77
|
-
export default function App(): JSX.Element {
|
|
78
|
-
return (
|
|
79
|
-
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
80
|
-
<SafeAreaProvider>
|
|
81
|
-
<ThemeProvider>
|
|
82
|
-
<ToastProvider>
|
|
83
|
-
<NetworkStatusBanner />
|
|
84
|
-
{/* your screens */}
|
|
85
|
-
</ToastProvider>
|
|
86
|
-
</ThemeProvider>
|
|
87
|
-
</SafeAreaProvider>
|
|
88
|
-
</GestureHandlerRootView>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
`initUI` is also exported for advanced setups that need to register UI-only globals (theme overrides, default color mode) without the API/auth side of the SDK.
|
|
94
|
-
|
|
95
|
-
---
|
|
96
|
-
|
|
97
|
-
## API surface
|
|
98
|
-
|
|
99
|
-
```ts
|
|
100
|
-
import {
|
|
101
|
-
// bootstrap
|
|
102
|
-
initWebority,
|
|
103
|
-
initUserAuth,
|
|
104
|
-
initUI,
|
|
105
|
-
|
|
106
|
-
// configuration
|
|
107
|
-
setConfig,
|
|
108
|
-
getConfig,
|
|
109
|
-
getConfigValue,
|
|
110
|
-
resetConfig,
|
|
111
|
-
|
|
112
|
-
// theme
|
|
113
|
-
ThemeProvider,
|
|
114
|
-
useTheme,
|
|
115
|
-
useThemeMode,
|
|
116
|
-
mergeTheme,
|
|
117
|
-
lightTheme,
|
|
118
|
-
darkTheme,
|
|
119
|
-
|
|
120
|
-
// hooks
|
|
121
|
-
useToggle,
|
|
122
|
-
useDebounce,
|
|
123
|
-
usePressAnimation,
|
|
124
|
-
|
|
125
|
-
// http
|
|
126
|
-
publicClient,
|
|
127
|
-
userClient,
|
|
128
|
-
|
|
129
|
-
// auth
|
|
130
|
-
storeToken,
|
|
131
|
-
getToken,
|
|
132
|
-
removeToken,
|
|
133
|
-
decodeJWT,
|
|
134
|
-
isJWTExpired,
|
|
135
|
-
getJWTExpiry,
|
|
136
|
-
|
|
137
|
-
// storage
|
|
138
|
-
Storage,
|
|
139
|
-
setStorageImplementation,
|
|
140
|
-
|
|
141
|
-
// logger + remote sink (Crashlytics / Sentry adapter)
|
|
142
|
-
Logger,
|
|
143
|
-
setRemoteLogger,
|
|
144
|
-
|
|
145
|
-
// version + force update
|
|
146
|
-
VersionCheck,
|
|
147
|
-
useVersionCheck,
|
|
148
|
-
ForceUpdateDialog,
|
|
149
|
-
|
|
150
|
-
// network
|
|
151
|
-
useNetworkStatus,
|
|
152
|
-
getNetworkStatus,
|
|
153
|
-
NetworkStatusBanner,
|
|
154
|
-
|
|
155
|
-
// permissions
|
|
156
|
-
Permissions,
|
|
157
|
-
|
|
158
|
-
// formatters
|
|
159
|
-
formatDate,
|
|
160
|
-
formatTime,
|
|
161
|
-
formatRelativeTime,
|
|
162
|
-
formatPhone,
|
|
163
|
-
formatCurrency,
|
|
164
|
-
formatNumber,
|
|
165
|
-
formatPercent,
|
|
166
|
-
formatCompactNumber,
|
|
167
|
-
getInitials,
|
|
168
|
-
|
|
169
|
-
// validators
|
|
170
|
-
isEmail,
|
|
171
|
-
isIndianMobile,
|
|
172
|
-
isPhone,
|
|
173
|
-
isUrl,
|
|
174
|
-
isStrongPassword,
|
|
175
|
-
isPan,
|
|
176
|
-
isGstin,
|
|
177
|
-
isAadhaar,
|
|
178
|
-
isIfsc,
|
|
179
|
-
isIndianPincode,
|
|
180
|
-
isHexColor,
|
|
181
|
-
isNumeric,
|
|
182
|
-
isInteger,
|
|
183
|
-
isAlphanumeric,
|
|
184
|
-
isBetween,
|
|
185
|
-
minLength,
|
|
186
|
-
maxLength,
|
|
187
|
-
|
|
188
|
-
// utilities
|
|
189
|
-
Responsive,
|
|
190
|
-
Spacing,
|
|
191
|
-
Padding,
|
|
192
|
-
Margin,
|
|
193
|
-
isTablet,
|
|
194
|
-
shadowStyle,
|
|
195
|
-
triggerHaptic,
|
|
196
|
-
compressImage,
|
|
197
|
-
|
|
198
|
-
// toast
|
|
199
|
-
ToastProvider,
|
|
200
|
-
toast,
|
|
201
|
-
useToast
|
|
202
|
-
|
|
203
|
-
// …41 UI components
|
|
204
|
-
} from '@webority-technologies/mobile';
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
|
-
## HTTP clients
|
|
210
|
-
|
|
211
|
-
### `publicClient`
|
|
212
|
-
|
|
213
|
-
Axios instance for unauthenticated calls. Handles retries (network errors, 429, 503), 30s timeout, environment-aware base URL, and standard headers (including gzip `Accept-Encoding`).
|
|
214
|
-
|
|
215
|
-
### `userClient`
|
|
216
|
-
|
|
217
|
-
Axios instance for authenticated calls. Injects a Bearer token from Keychain, queues concurrent 401s while a single refresh is in flight, and replays them on success. Wires into the host app's Redux store via `initUserAuth(store, { logout, refreshToken })`.
|
|
218
|
-
|
|
219
|
-
---
|
|
220
|
-
|
|
221
|
-
## Auth & tokens
|
|
222
|
-
|
|
223
|
-
```ts
|
|
224
|
-
import {
|
|
225
|
-
storeToken,
|
|
226
|
-
getToken,
|
|
227
|
-
removeToken,
|
|
228
|
-
decodeJWT,
|
|
229
|
-
isJWTExpired
|
|
230
|
-
} from '@webority-technologies/mobile';
|
|
231
|
-
|
|
232
|
-
await storeToken('accessToken', token, expiry);
|
|
233
|
-
const stored = await getToken('accessToken'); // { token, expiry } | null
|
|
234
|
-
if (stored && isJWTExpired(stored.token)) {
|
|
235
|
-
/* refresh */
|
|
236
|
-
}
|
|
237
|
-
const claims = decodeJWT<{ sub: string; role: string }>(stored.token);
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
Tokens are persisted via `react-native-keychain` with biometry-friendly defaults and graceful recovery from "Key permanently invalidated" errors.
|
|
241
|
-
|
|
242
|
-
---
|
|
243
|
-
|
|
244
|
-
## Storage
|
|
245
|
-
|
|
246
|
-
Typed `AsyncStorage` wrapper with safe JSON helpers. All operations swallow errors (logging them) and return falsy results so storage failures never crash the app.
|
|
247
|
-
|
|
248
|
-
```ts
|
|
249
|
-
import { Storage } from '@webority-technologies/mobile';
|
|
250
|
-
|
|
251
|
-
await Storage.setItem('lang', 'en');
|
|
252
|
-
const lang = await Storage.getItem('lang'); // string | null
|
|
253
|
-
|
|
254
|
-
await Storage.setJson('user', { id: '1', name: 'Navneet' });
|
|
255
|
-
const user = await Storage.getJson<User>('user'); // User | null
|
|
256
|
-
|
|
257
|
-
await Storage.multiSet({ pushToken: '…', deviceId: '…' });
|
|
258
|
-
await Storage.multiRemove(['pushToken']);
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
Override the backend (e.g. with MMKV) via `setStorageImplementation()`.
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
|
-
## Logger & crash reporting
|
|
266
|
-
|
|
267
|
-
`Logger.info / warn / error` write to the console. `Logger.recordError`, `Logger.breadcrumb`, `Logger.setUser`, and `Logger.setAttribute` forward to an optional **remote sink** that you wire from your app — keeping the library Firebase- and Sentry-agnostic.
|
|
268
|
-
|
|
269
|
-
```ts
|
|
270
|
-
import { Logger, setRemoteLogger } from '@webority-technologies/mobile';
|
|
271
|
-
import crashlytics from '@react-native-firebase/crashlytics';
|
|
272
|
-
|
|
273
|
-
setRemoteLogger({
|
|
274
|
-
log: (level, message) => crashlytics().log(`[${level}] ${message}`),
|
|
275
|
-
recordError: (error, ctx) => {
|
|
276
|
-
if (ctx) Object.entries(ctx).forEach(([k, v]) => crashlytics().setAttribute(k, String(v)));
|
|
277
|
-
crashlytics().recordError(error instanceof Error ? error : new Error(String(error)));
|
|
278
|
-
},
|
|
279
|
-
breadcrumb: (msg, data) => crashlytics().log(data ? `${msg} ${JSON.stringify(data)}` : msg),
|
|
280
|
-
setUser: (id) => crashlytics().setUserId(id ?? ''),
|
|
281
|
-
setAttribute: (k, v) => crashlytics().setAttribute(k, v == null ? '' : String(v))
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
Logger.recordError(new Error('boom'), { route: '/checkout', orderId: 42 });
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
The same shape works for Sentry, Bugsnag, Datadog, or any custom backend — implement only the methods you need.
|
|
288
|
-
|
|
289
|
-
---
|
|
290
|
-
|
|
291
|
-
## Version check & force update
|
|
292
|
-
|
|
293
|
-
```tsx
|
|
294
|
-
import { useVersionCheck, ForceUpdateDialog } from '@webority-technologies/mobile';
|
|
295
|
-
|
|
296
|
-
const { isUpdateRequired, updateUrl } = useVersionCheck(isAppReady);
|
|
297
|
-
return <ForceUpdateDialog visible={isUpdateRequired} mandatory updateUrl={updateUrl} />;
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
`useVersionCheck` only runs in `'Production'`. Country code (used for App Store / Play Store lookups) is configurable via `initWebority({ country: 'us' })`; defaults to `'in'`.
|
|
301
|
-
|
|
302
|
-
---
|
|
303
|
-
|
|
304
|
-
## Network status
|
|
305
|
-
|
|
306
|
-
```tsx
|
|
307
|
-
import { useNetworkStatus, NetworkStatusBanner } from '@webority-technologies/mobile';
|
|
308
|
-
|
|
309
|
-
// Mount once near the app root
|
|
310
|
-
<NetworkStatusBanner />;
|
|
311
|
-
|
|
312
|
-
// Or read it inside a screen
|
|
313
|
-
const { isConnected, isInternetReachable, type } = useNetworkStatus();
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
Falls back to an optimistic "online" status when `@react-native-community/netinfo` is not installed.
|
|
317
|
-
|
|
318
|
-
---
|
|
319
|
-
|
|
320
|
-
## Permissions
|
|
321
|
-
|
|
322
|
-
```ts
|
|
323
|
-
import { Permissions } from '@webority-technologies/mobile';
|
|
324
|
-
|
|
325
|
-
await Permissions.isAuthorized('camera'); // boolean
|
|
326
|
-
const status = await Permissions.ensure('photos'); // 'granted' | 'denied' | 'limited' | 'blocked' | 'unavailable'
|
|
327
|
-
if (status === 'blocked') await Permissions.openSettings();
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
Maps an abstract `PermissionKind` (camera, microphone, photos, location, contacts, calendar, notifications, bluetooth, …) to the right iOS / Android permission string under the hood.
|
|
331
|
-
|
|
332
|
-
---
|
|
333
|
-
|
|
334
|
-
## Formatters
|
|
335
|
-
|
|
336
|
-
```ts
|
|
337
|
-
formatDate(new Date(), 'medium'); // '8 Apr 2026'
|
|
338
|
-
formatRelativeTime(then, now); // '5 minutes ago'
|
|
339
|
-
formatPhone('9876512345'); // '+91 98765 12345'
|
|
340
|
-
formatCurrency(150000); // '₹1,50,000'
|
|
341
|
-
formatCurrency(99, { currency: 'USD', locale: 'en-US' }); // '$99.00'
|
|
342
|
-
formatCompactNumber(1500000); // '15L' (en-IN)
|
|
343
|
-
getInitials('Navneet Singh'); // 'NS'
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
All formatters default to `en-IN` locale (Indian numbering / DD-MMM-YYYY style) and accept overrides.
|
|
347
|
-
|
|
348
|
-
---
|
|
349
|
-
|
|
350
|
-
## Validators
|
|
351
|
-
|
|
352
|
-
Pragmatic regex-based validators that match common Indian-app needs:
|
|
353
|
-
|
|
354
|
-
```ts
|
|
355
|
-
isEmail('a@b.co'); // true
|
|
356
|
-
isIndianMobile('+919876512345'); // true
|
|
357
|
-
isPan('ABCDE1234F'); // true
|
|
358
|
-
isGstin('27AAPFU0939F1ZV'); // true
|
|
359
|
-
isIfsc('HDFC0001234'); // true
|
|
360
|
-
isIndianPincode('110001'); // true
|
|
361
|
-
isStrongPassword('Aa1!@#$%', { requireSymbol: true }); // true
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
Plus generic helpers: `isUrl`, `isNumeric`, `isInteger`, `isAlphanumeric`, `isHexColor`, `isBetween`, `minLength`, `maxLength`.
|
|
365
|
-
|
|
366
|
-
---
|
|
367
|
-
|
|
368
|
-
## Theme
|
|
369
|
-
|
|
370
|
-
`ThemeProvider` exposes the design-token surface (colors, typography, spacing, radius, shadows, motion) and tracks the system color-scheme automatically. Read it via `useTheme()`. Every shipped component reads from the active theme — no hard-coded colors anywhere. Customize with `mergeTheme` or `initUI({ light, dark })`.
|
|
371
|
-
|
|
372
|
-
---
|
|
373
|
-
|
|
374
|
-
## Toast
|
|
375
|
-
|
|
376
|
-
Imperative `toast.success(...)` / `toast.error(...)` from anywhere in the tree, or `useToast().show(...)` inside components.
|
|
377
|
-
|
|
378
|
-
```ts
|
|
379
|
-
toast.success('Saved.');
|
|
380
|
-
toast.error('Network is down', { description: 'Check your connection.' });
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
Mount `<ToastProvider>` once near the app root.
|
|
384
|
-
|
|
385
|
-
---
|
|
386
|
-
|
|
387
|
-
## Components
|
|
388
|
-
|
|
389
|
-
41 components across Layout, Forms & Inputs, Feedback & Status, Overlay, Navigation, and Data Display.
|
|
390
|
-
|
|
391
|
-
For the full catalog with props tables and minimal usage examples, see [`docs/COMPONENTS.md`](../../docs/COMPONENTS.md).
|
|
392
|
-
|
|
393
|
-
For runnable usage of every component, see [`example/`](../../example/).
|
|
394
|
-
|
|
395
|
-
---
|
|
396
|
-
|
|
397
|
-
## Designing skeleton-aware components
|
|
398
|
-
|
|
399
|
-
Presentational primitives (`Card`, `ListItem`, `Avatar`, `Badge`, `Banner`, `Chip`,
|
|
400
|
-
`Rating`, `Stepper`, `ImageGallery`) accept an optional
|
|
401
|
-
`loading?: boolean` prop. When `true`, the component renders its normal layout wrapped
|
|
402
|
-
in `<SkeletonContent loading mode="auto">` — the walker replaces every `<Text>`,
|
|
403
|
-
`<Image>`, and sized leaf `<View>` with a shimmer block matching the element's
|
|
404
|
-
dimensions. When `loading` is `false` or omitted, behaviour is unchanged.
|
|
405
|
-
|
|
406
|
-
Conventions for components that opt into this pattern:
|
|
407
|
-
|
|
408
|
-
- **Render gracefully with undefined data.** Use optional chaining and string defaults
|
|
409
|
-
so the auto walker has leaves to skeletonize even before data arrives:
|
|
410
|
-
`{item?.title ?? ' '}`, `{item?.subtitle ?? ' '}`. Never let a `<Text>` render `undefined`.
|
|
411
|
-
- **Use explicit dimensions on visual elements.** Set `width`/`height` on `<Image>` and
|
|
412
|
-
`<View>` leaves, and `fontSize` on `<Text>`, so the walker can size each shimmer
|
|
413
|
-
block correctly. Inline styles, `StyleSheet.create`, and arrays all work — they get
|
|
414
|
-
flattened.
|
|
415
|
-
- **Wrap once at the top.** Don't sprinkle `<SkeletonContent>` inside subtrees; wrap the
|
|
416
|
-
component's final return so the entire layout becomes a single coherent placeholder.
|
|
417
|
-
- **For data-driven lists, prefer `<SkeletonList>`.** It owns the "show N placeholder
|
|
418
|
-
rows while loading, then real data" branching so callers don't hand-roll it.
|
|
419
|
-
|
|
420
|
-
Before / after with `Card`:
|
|
421
|
-
|
|
422
|
-
```tsx
|
|
423
|
-
// Before — caller hand-rolls a placeholder branch
|
|
424
|
-
{
|
|
425
|
-
loading ? (
|
|
426
|
-
<SkeletonCard />
|
|
427
|
-
) : (
|
|
428
|
-
<Card>
|
|
429
|
-
<Text>{site.name}</Text>
|
|
430
|
-
<Text>{site.address}</Text>
|
|
431
|
-
</Card>
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// After — Card itself is skeleton-aware
|
|
436
|
-
<Card loading={loading}>
|
|
437
|
-
<Text>{site?.name ?? ' '}</Text>
|
|
438
|
-
<Text>{site?.address ?? ' '}</Text>
|
|
439
|
-
</Card>;
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
### Two flavours of `loading`
|
|
443
|
-
|
|
444
|
-
Same prop name, different semantic by category:
|
|
445
|
-
|
|
446
|
-
- **Display primitives** (`Card`, `ListItem`, `Avatar`, `Badge`, `Banner`, `Chip`, `Rating`,
|
|
447
|
-
`Stepper`, `ImageGallery`) — `loading={true}` renders the component as a skeleton placeholder
|
|
448
|
-
via `SkeletonContent`'s auto walker. Use when the data driving the component is still being
|
|
449
|
-
fetched.
|
|
450
|
-
- **Interactive triggers** (`Button`) — `loading={true}` swaps the label for an inline `Spinner`,
|
|
451
|
-
hides leading/trailing icons, and disables press handling. The button keeps its footprint and
|
|
452
|
-
visual style. Use when an action is in flight (submit, save, delete) — pending action, not
|
|
453
|
-
data fetch.
|
|
454
|
-
|
|
455
|
-
Before / after with `Button`:
|
|
456
|
-
|
|
457
|
-
```tsx
|
|
458
|
-
// Before — caller swaps a separate spinner in
|
|
459
|
-
{
|
|
460
|
-
submitting ? <Spinner size="small" /> : <Button title="Save" onPress={save} />;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// After — Button itself signals the in-flight action
|
|
464
|
-
<Button title="Save" loading={submitting} onPress={save} />;
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
---
|
|
468
|
-
|
|
469
|
-
## License
|
|
9
|
+
This package is published for use inside Webority repositories. Documentation is maintained internally.
|
|
470
10
|
|
|
471
|
-
MIT
|
|
11
|
+
MIT © Webority Technologies.
|
|
@@ -61,28 +61,29 @@ const sizeMap = {
|
|
|
61
61
|
dot: 10
|
|
62
62
|
}
|
|
63
63
|
};
|
|
64
|
-
const anchorPosition = anchor => {
|
|
64
|
+
const anchorPosition = (anchor, offset) => {
|
|
65
|
+
const o = -offset;
|
|
65
66
|
switch (anchor) {
|
|
66
67
|
case 'topLeft':
|
|
67
68
|
return {
|
|
68
|
-
top:
|
|
69
|
-
left:
|
|
69
|
+
top: o,
|
|
70
|
+
left: o
|
|
70
71
|
};
|
|
71
72
|
case 'bottomRight':
|
|
72
73
|
return {
|
|
73
|
-
bottom:
|
|
74
|
-
right:
|
|
74
|
+
bottom: o,
|
|
75
|
+
right: o
|
|
75
76
|
};
|
|
76
77
|
case 'bottomLeft':
|
|
77
78
|
return {
|
|
78
|
-
bottom:
|
|
79
|
-
left:
|
|
79
|
+
bottom: o,
|
|
80
|
+
left: o
|
|
80
81
|
};
|
|
81
82
|
case 'topRight':
|
|
82
83
|
default:
|
|
83
84
|
return {
|
|
84
|
-
top:
|
|
85
|
-
right:
|
|
85
|
+
top: o,
|
|
86
|
+
right: o
|
|
86
87
|
};
|
|
87
88
|
}
|
|
88
89
|
};
|
|
@@ -115,7 +116,16 @@ const Badge = exports.Badge = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =
|
|
|
115
116
|
} = props;
|
|
116
117
|
const theme = (0, _index.useTheme)();
|
|
117
118
|
const tones = (0, _react.useMemo)(() => toneFor(theme, tone), [theme, tone]);
|
|
118
|
-
const
|
|
119
|
+
const overrides = theme.components.badge?.[size];
|
|
120
|
+
const sz = {
|
|
121
|
+
fontSize: overrides?.fontSize ?? sizeMap[size].fontSize,
|
|
122
|
+
minWidth: overrides?.minWidth ?? sizeMap[size].minWidth,
|
|
123
|
+
height: overrides?.height ?? sizeMap[size].height,
|
|
124
|
+
paddingH: overrides?.paddingHorizontal ?? sizeMap[size].paddingH,
|
|
125
|
+
dot: overrides?.dotSize ?? sizeMap[size].dot
|
|
126
|
+
};
|
|
127
|
+
const badgeBorderWidth = theme.components.badge?.borderWidth ?? 1.5;
|
|
128
|
+
const badgeAnchorOffset = theme.components.badge?.anchorOffset ?? 4;
|
|
119
129
|
const formatted = variant === 'dot' ? null : formatValue(value, max);
|
|
120
130
|
const shouldRender = !invisible && (variant === 'dot' || formatted !== null);
|
|
121
131
|
const pulseScale = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current;
|
|
@@ -161,9 +171,9 @@ const Badge = exports.Badge = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =
|
|
|
161
171
|
}]
|
|
162
172
|
}, children ? {
|
|
163
173
|
position: 'absolute',
|
|
164
|
-
borderWidth:
|
|
174
|
+
borderWidth: badgeBorderWidth,
|
|
165
175
|
borderColor: theme.colors.background.elevated,
|
|
166
|
-
...anchorPosition(anchor)
|
|
176
|
+
...anchorPosition(anchor, badgeAnchorOffset)
|
|
167
177
|
} : null, badgeStyle],
|
|
168
178
|
accessible: true,
|
|
169
179
|
accessibilityRole: a11yRole,
|
|
@@ -172,7 +182,7 @@ const Badge = exports.Badge = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =
|
|
|
172
182
|
style: [styles.text, {
|
|
173
183
|
color: tones.text,
|
|
174
184
|
fontSize: sz.fontSize,
|
|
175
|
-
|
|
185
|
+
...(0, _index.fontFor)(theme, 'bold')
|
|
176
186
|
}, textStyle],
|
|
177
187
|
numberOfLines: 1,
|
|
178
188
|
allowFontScaling: false,
|
|
@@ -48,6 +48,7 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
|
|
|
48
48
|
enablePanDownToClose = true,
|
|
49
49
|
enableBackdropPress = true,
|
|
50
50
|
backdropOpacity = 0.5,
|
|
51
|
+
keyboardBehavior = 'none',
|
|
51
52
|
handleIndicatorStyle,
|
|
52
53
|
containerStyle,
|
|
53
54
|
children,
|
|
@@ -240,12 +241,45 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
|
|
|
240
241
|
});
|
|
241
242
|
}, [closedY, dragStartY, enablePanDownToClose, handleSnapEnd, minTopY, snapTargets, translateY]);
|
|
242
243
|
|
|
244
|
+
// ───────── Keyboard tracking ─────────
|
|
245
|
+
// keyboardOffset is added to translateY in sheetStyle so we don't disturb
|
|
246
|
+
// snap-point math. iOS gets keyboardWillShow/Hide (smoother), Android only
|
|
247
|
+
// emits keyboardDidShow/Hide.
|
|
248
|
+
const keyboardOffset = (0, _reactNativeReanimated.useSharedValue)(0);
|
|
249
|
+
(0, _react.useEffect)(() => {
|
|
250
|
+
if (keyboardBehavior === 'none') return;
|
|
251
|
+
const showEvt = _reactNative.Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
|
|
252
|
+
const hideEvt = _reactNative.Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
|
|
253
|
+
const onShow = e => {
|
|
254
|
+
const h = e.endCoordinates?.height ?? 0;
|
|
255
|
+
keyboardOffset.value = (0, _reactNativeReanimated.withTiming)(-h, {
|
|
256
|
+
duration: 220
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
const onHide = () => {
|
|
260
|
+
keyboardOffset.value = (0, _reactNativeReanimated.withTiming)(0, {
|
|
261
|
+
duration: 220
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
const showSub = _reactNative.Keyboard.addListener(showEvt, onShow);
|
|
265
|
+
const hideSub = _reactNative.Keyboard.addListener(hideEvt, onHide);
|
|
266
|
+
return () => {
|
|
267
|
+
showSub.remove();
|
|
268
|
+
hideSub.remove();
|
|
269
|
+
};
|
|
270
|
+
}, [keyboardBehavior, keyboardOffset]);
|
|
271
|
+
|
|
243
272
|
// ───────── Animated styles ─────────
|
|
244
|
-
const sheetStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() =>
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
273
|
+
const sheetStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
|
|
274
|
+
// Don't push past the screen top — keyboard offset is clamped to minTopY.
|
|
275
|
+
const yWithKb = translateY.value + keyboardOffset.value;
|
|
276
|
+
const clamped = yWithKb < minTopY ? minTopY : yWithKb;
|
|
277
|
+
return {
|
|
278
|
+
transform: [{
|
|
279
|
+
translateY: clamped
|
|
280
|
+
}]
|
|
281
|
+
};
|
|
282
|
+
});
|
|
249
283
|
const backdropStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
|
|
250
284
|
// 0 opacity when sheet is closed, full backdropOpacity at minSnap height (and above).
|
|
251
285
|
const closedAt = screenHeight;
|
|
@@ -26,6 +26,7 @@ const Button = exports.Button = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
|
|
|
26
26
|
rightIcon,
|
|
27
27
|
haptic = 'selection',
|
|
28
28
|
rounded = false,
|
|
29
|
+
gradient,
|
|
29
30
|
style,
|
|
30
31
|
textStyle,
|
|
31
32
|
pressAnimation = true,
|
|
@@ -45,9 +46,22 @@ const Button = exports.Button = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
|
|
|
45
46
|
enabled: pressAnimation && isInteractive
|
|
46
47
|
});
|
|
47
48
|
const styles = (0, _react.useMemo)(() => buildStyles(theme), [theme]);
|
|
48
|
-
const sizeStyles =
|
|
49
|
+
const sizeStyles = {
|
|
50
|
+
...sizeMap[size],
|
|
51
|
+
...(theme.components.button?.[size] ?? {})
|
|
52
|
+
};
|
|
49
53
|
const toneColors = (0, _react.useMemo)(() => toneFor(theme, tone), [theme, tone]);
|
|
50
54
|
const variantStyles = (0, _react.useMemo)(() => variantFor(theme, variant, toneColors, disabled), [theme, variant, toneColors, disabled]);
|
|
55
|
+
|
|
56
|
+
// Resolve gradient: string token → theme.gradients[token]; literal passes through.
|
|
57
|
+
// Skipped when disabled (parity with disabled flat-color treatment).
|
|
58
|
+
const resolvedGradient = (0, _react.useMemo)(() => {
|
|
59
|
+
if (!gradient || disabled || variant !== 'solid') return null;
|
|
60
|
+
if (typeof gradient === 'string') {
|
|
61
|
+
return theme.gradients[gradient] ?? null;
|
|
62
|
+
}
|
|
63
|
+
return gradient;
|
|
64
|
+
}, [gradient, disabled, variant, theme.gradients]);
|
|
51
65
|
const handlePress = event => {
|
|
52
66
|
if (!isInteractive) return;
|
|
53
67
|
if (haptic !== false) (0, _hapticUtils.triggerHaptic)(haptic);
|
|
@@ -69,7 +83,7 @@ const Button = exports.Button = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
|
|
|
69
83
|
style: [styles.label, {
|
|
70
84
|
color: variantStyles.textColor,
|
|
71
85
|
fontSize: sizeStyles.fontSize,
|
|
72
|
-
|
|
86
|
+
...(0, _index.fontFor)(theme, 'semibold')
|
|
73
87
|
}, textStyle],
|
|
74
88
|
numberOfLines: 1,
|
|
75
89
|
children: title
|
|
@@ -85,7 +99,7 @@ const Button = exports.Button = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
|
|
|
85
99
|
scale
|
|
86
100
|
}]
|
|
87
101
|
}, fullWidth ? styles.fullWidth : null],
|
|
88
|
-
children: /*#__PURE__*/(0, _jsxRuntime.
|
|
102
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
|
|
89
103
|
ref: ref,
|
|
90
104
|
onPress: handlePress,
|
|
91
105
|
onPressIn: pressIn,
|
|
@@ -113,15 +127,20 @@ const Button = exports.Button = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
|
|
|
113
127
|
paddingVertical: sizeStyles.paddingVertical,
|
|
114
128
|
minHeight: sizeStyles.minHeight,
|
|
115
129
|
borderRadius: rounded ? theme.radius.full : sizeStyles.borderRadius,
|
|
116
|
-
|
|
130
|
+
// Hide solid bg when a gradient layer covers it; preserve outline borders.
|
|
131
|
+
backgroundColor: resolvedGradient ? 'transparent' : variantStyles.backgroundColor,
|
|
117
132
|
borderWidth: variantStyles.borderWidth,
|
|
118
133
|
borderColor: variantStyles.borderColor,
|
|
119
|
-
opacity: !isInteractive ? 0.55 : 1
|
|
134
|
+
opacity: !isInteractive ? 0.55 : 1,
|
|
135
|
+
overflow: resolvedGradient ? 'hidden' : undefined
|
|
120
136
|
}, pressed && variant !== 'solid' ? {
|
|
121
137
|
backgroundColor: theme.colors.surface.pressed
|
|
122
138
|
} : null, fullWidth ? styles.fullWidth : null, style],
|
|
123
139
|
...rest,
|
|
124
|
-
children:
|
|
140
|
+
children: [resolvedGradient ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.Gradient, {
|
|
141
|
+
gradient: resolvedGradient,
|
|
142
|
+
style: _reactNative.StyleSheet.absoluteFill
|
|
143
|
+
}) : null, content]
|
|
125
144
|
})
|
|
126
145
|
});
|
|
127
146
|
});
|