@temboplus/frontend-react-core 0.1.3-beta.9 → 0.1.3

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 (103) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +80 -334
  3. package/dist/index.cjs.js +15 -1
  4. package/dist/index.cjs.js.map +1 -1
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.esm.js +4 -1
  8. package/dist/index.esm.js.map +1 -1
  9. package/dist/timezone/index.cjs.js +16 -0
  10. package/dist/{notifications → timezone}/index.cjs.js.map +1 -1
  11. package/dist/timezone/index.d.ts +4 -0
  12. package/dist/timezone/index.d.ts.map +1 -0
  13. package/dist/timezone/index.js +5 -0
  14. package/dist/{notifications → timezone}/index.js.map +1 -1
  15. package/dist/timezone/use-format-date.d.ts +23 -0
  16. package/dist/timezone/use-format-date.d.ts.map +1 -0
  17. package/dist/timezone/use-timezone-date.d.ts +18 -0
  18. package/dist/timezone/use-timezone-date.d.ts.map +1 -0
  19. package/dist/timezone/use-timezone.d.ts +20 -0
  20. package/dist/timezone/use-timezone.d.ts.map +1 -0
  21. package/dist/use-timezone-date-B-P03WD_.js +81 -0
  22. package/dist/use-timezone-date-B-P03WD_.js.map +1 -0
  23. package/dist/use-timezone-date-s1Ru9Fw6.js +88 -0
  24. package/dist/use-timezone-date-s1Ru9Fw6.js.map +1 -0
  25. package/package.json +58 -63
  26. package/dist/InfoCircleOutlined-B7d2aRfV.js +0 -7
  27. package/dist/InfoCircleOutlined-B7d2aRfV.js.map +0 -1
  28. package/dist/InfoCircleOutlined-DYs90hdV.js +0 -7
  29. package/dist/InfoCircleOutlined-DYs90hdV.js.map +0 -1
  30. package/dist/ZoomOutOutlined-CW-jqBMI.js +0 -2
  31. package/dist/ZoomOutOutlined-CW-jqBMI.js.map +0 -1
  32. package/dist/ZoomOutOutlined-Pw8hpWWK.js +0 -2
  33. package/dist/ZoomOutOutlined-Pw8hpWWK.js.map +0 -1
  34. package/dist/alerts/index.cjs.js +0 -2
  35. package/dist/alerts/index.cjs.js.map +0 -1
  36. package/dist/alerts/index.d.ts +0 -1
  37. package/dist/alerts/index.js +0 -2
  38. package/dist/alerts/index.js.map +0 -1
  39. package/dist/dialogs/index.cjs.js +0 -2
  40. package/dist/dialogs/index.cjs.js.map +0 -1
  41. package/dist/dialogs/index.d.ts +0 -1
  42. package/dist/dialogs/index.js +0 -2
  43. package/dist/dialogs/index.js.map +0 -1
  44. package/dist/features/alerts/alert.d.ts +0 -12
  45. package/dist/features/alerts/alert.js +0 -95
  46. package/dist/features/alerts/index.d.ts +0 -1
  47. package/dist/features/alerts/index.js +0 -1
  48. package/dist/features/dialogs/index.d.ts +0 -1
  49. package/dist/features/dialogs/index.js +0 -1
  50. package/dist/features/dialogs/modal-provider.d.ts +0 -3
  51. package/dist/features/dialogs/modal-provider.js +0 -6
  52. package/dist/features/dialogs/tembo-confirm.d.ts +0 -63
  53. package/dist/features/dialogs/tembo-confirm.js +0 -111
  54. package/dist/features/input-validation/account-name-validator.d.ts +0 -13
  55. package/dist/features/input-validation/account-name-validator.js +0 -28
  56. package/dist/features/input-validation/account-number-validator.d.ts +0 -13
  57. package/dist/features/input-validation/account-number-validator.js +0 -65
  58. package/dist/features/input-validation/amount-validator.d.ts +0 -78
  59. package/dist/features/input-validation/amount-validator.js +0 -100
  60. package/dist/features/input-validation/index.d.ts +0 -5
  61. package/dist/features/input-validation/index.js +0 -5
  62. package/dist/features/input-validation/phone-number-validator.d.ts +0 -25
  63. package/dist/features/input-validation/phone-number-validator.js +0 -79
  64. package/dist/features/input-validation/swift-code-validator.d.ts +0 -13
  65. package/dist/features/input-validation/swift-code-validator.js +0 -38
  66. package/dist/features/notifications/index.d.ts +0 -3
  67. package/dist/features/notifications/index.js +0 -3
  68. package/dist/features/notifications/tembo-notify.d.ts +0 -50
  69. package/dist/features/notifications/tembo-notify.js +0 -140
  70. package/dist/features/notifications/toast-config.d.ts +0 -12
  71. package/dist/features/notifications/toast-config.js +0 -60
  72. package/dist/features/notifications/toast-container.d.ts +0 -19
  73. package/dist/features/notifications/toast-container.js +0 -89
  74. package/dist/index.js +0 -1
  75. package/dist/notifications/index.cjs.js +0 -2
  76. package/dist/notifications/index.d.ts +0 -1
  77. package/dist/notifications/index.js +0 -2
  78. package/dist/providers.d.ts +0 -37
  79. package/dist/providers.js +0 -32
  80. package/dist/tembo-notify-Bp14qngd.js +0 -2
  81. package/dist/tembo-notify-Bp14qngd.js.map +0 -1
  82. package/dist/tembo-notify-h5Xn66oA.js +0 -2
  83. package/dist/tembo-notify-h5Xn66oA.js.map +0 -1
  84. package/dist/theme/colors.d.ts +0 -278
  85. package/dist/theme/colors.js +0 -212
  86. package/dist/theme/constants.d.ts +0 -143
  87. package/dist/theme/constants.js +0 -82
  88. package/dist/theme/index.cjs.js +0 -2
  89. package/dist/theme/index.cjs.js.map +0 -1
  90. package/dist/theme/index.d.ts +0 -3
  91. package/dist/theme/index.js +0 -2
  92. package/dist/theme/index.js.map +0 -1
  93. package/dist/theme/theme-provider.d.ts +0 -99
  94. package/dist/theme/theme-provider.js +0 -404
  95. package/dist/theme-provider-Ca4P0Hcp.js +0 -11
  96. package/dist/theme-provider-Ca4P0Hcp.js.map +0 -1
  97. package/dist/theme-provider-RhAw3jw_.js +0 -11
  98. package/dist/theme-provider-RhAw3jw_.js.map +0 -1
  99. package/dist/validation/index.cjs.js +0 -2
  100. package/dist/validation/index.cjs.js.map +0 -1
  101. package/dist/validation/index.d.ts +0 -1
  102. package/dist/validation/index.js +0 -2
  103. package/dist/validation/index.js.map +0 -1
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2024 TemboPlus
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -1,385 +1,131 @@
1
1
  # @temboplus/frontend-react-core
2
2
 
3
- A React UI library for TemboPlus applications, providing reusable components, validators, and utilities for building consistent react applications across the platform.
3
+ React hooks that wrap pure helpers from [`@temboplus/frontend-core`](https://github.com/TemboPlus-Frontend/frontend-core-js) for use in React applications across the TemboPlus platform.
4
4
 
5
- ## 📦 Installation
5
+ Today the package ships one feature area — timezone — with more to be added as React-flavored counterparts to frontend-core utilities are needed.
6
+
7
+ ## Installation
6
8
 
7
9
  ```bash
8
- npm install @temboplus/frontend-react-core
9
- # or
10
- yarn add @temboplus/frontend-react-core
10
+ bun add @temboplus/frontend-react-core
11
11
  # or
12
- pnpm add @temboplus/frontend-react-core
12
+ npm install @temboplus/frontend-react-core
13
13
  ```
14
14
 
15
- ## 🏗️ Dependencies
16
-
17
- This package requires the following peer dependencies:
15
+ Peer dependency:
18
16
 
19
17
  ```bash
20
- npm install @temboplus/frontend-core
21
- ```
22
-
23
- ## 🚀 Quick Start
24
-
25
- ```typescript
26
- import { PHONE_NUMBER_VALIDATOR, AMOUNT_VALIDATOR } from '@temboplus/frontend-react-core';
27
-
28
- // Use in your Ant Design form
29
- const formRules = {
30
- phoneNumber: [{ required: true, validator: PHONE_NUMBER_VALIDATOR('TZ') }],
31
- amount: [{ required: true, validator: AMOUNT_VALIDATOR({ currencyCode: 'TZS' }) }]
32
- };
18
+ bun add react
33
19
  ```
34
20
 
35
- ## 📚 Features
36
-
37
- ### ✅ Form Validators (Available Now)
38
-
39
- Type-safe, country-aware form validators for Ant Design forms with built-in internationalization and error messaging.
40
-
41
- ### 🔄 Coming Soon
21
+ Runtime dependency (auto-installed):
42
22
 
43
- - **Reusable React Components** - Pre-built UI components for financial applications
44
- - **Hooks Library** - Custom React hooks for common patterns
45
- - **Context Providers** - Application-wide state management
46
- - **Theme System** - Consistent design tokens and styling
47
- - **Utility Functions** - Helper functions for React applications
23
+ - `@temboplus/frontend-core` the pure utilities and domain entities the hooks wrap.
48
24
 
49
- ## 📋 API Reference
25
+ ## Subpath imports
50
26
 
51
- ### Form Validators
27
+ Import the focused surface or the root entry; both work:
52
28
 
53
- All validators follow the Ant Design validator pattern and return Promise-based validation results.
29
+ ```ts
30
+ // Focused (recommended — keeps your bundles honest):
31
+ import { useTimezone } from "@temboplus/frontend-react-core/timezone";
54
32
 
55
- #### `PHONE_NUMBER_VALIDATOR(countryCode?: ISO2CountryCode)`
56
-
57
- Validates general phone number format using libphonenumber-js.
58
-
59
- ```typescript
60
- import { PHONE_NUMBER_VALIDATOR } from '@temboplus/frontend-react-core';
61
-
62
- // Basic usage
63
- const rules = [{ required: true, validator: PHONE_NUMBER_VALIDATOR('TZ') }];
64
-
65
- // Supported countries: TZ, KE, and all libphonenumber-js supported countries
33
+ // Or the root:
34
+ import { useTimezone } from "@temboplus/frontend-react-core";
66
35
  ```
67
36
 
68
- **Features:**
69
- - Validates phone number format for any country
70
- - Returns normalized E.164 format on success
71
- - Country-specific error messages
72
- - Supports international and local number formats
73
-
74
- #### `MOBILE_PHONE_VALIDATOR(countryCode?: ISO2CountryCode)`
75
-
76
- Validates mobile phone numbers eligible for payout operations (stricter validation).
37
+ ## Timezone hooks
77
38
 
78
- ```typescript
79
- import { MOBILE_PHONE_VALIDATOR } from '@temboplus/frontend-react-core';
39
+ Each app passes its own `storageKey` so timezone preferences don't collide across products sharing a browser origin. Wrap once in your app and use the bare hook everywhere:
80
40
 
81
- const rules = [{ required: true, validator: MOBILE_PHONE_VALIDATOR('TZ') }];
82
- ```
83
-
84
- **Features:**
85
- - Validates mobile-specific number formats
86
- - Checks payout eligibility (supports TZ and KE)
87
- - MNO (Mobile Network Operator) validation
88
- - Returns normalized E.164 format
89
-
90
- #### `ACCOUNT_NAME_VALIDATOR`
91
-
92
- Validates bank account holder names.
41
+ ```ts
42
+ // src/shared/lib/timezone.ts (or similar)
43
+ import {
44
+ useTimezone as useTimezoneBase,
45
+ useTimezoneDate as useTimezoneDateBase,
46
+ useFormatDate as useFormatDateBase,
47
+ useFormatDateTime as useFormatDateTimeBase,
48
+ useFormatTime as useFormatTimeBase,
49
+ } from "@temboplus/frontend-react-core/timezone";
93
50
 
94
- ```typescript
95
- import { ACCOUNT_NAME_VALIDATOR } from '@temboplus/frontend-react-core';
51
+ const STORAGE_KEY = "myapp.timezone";
96
52
 
97
- const rules = [{ required: true, validator: ACCOUNT_NAME_VALIDATOR }];
53
+ export const useTimezone = () => useTimezoneBase({ storageKey: STORAGE_KEY });
54
+ export const useTimezoneDate = () => useTimezoneDateBase({ storageKey: STORAGE_KEY });
55
+ export const useFormatDate = () => useFormatDateBase({ storageKey: STORAGE_KEY });
56
+ export const useFormatDateTime = () => useFormatDateTimeBase({ storageKey: STORAGE_KEY });
57
+ export const useFormatTime = () => useFormatTimeBase({ storageKey: STORAGE_KEY });
98
58
  ```
99
59
 
100
- **Validation Rules:**
101
- - 3-50 characters length
102
- - Only letters, spaces, hyphens, apostrophes, commas
103
- - At least two words (first name + last name)
104
- - Not all uppercase
105
- - No numbers or special characters
60
+ ### `useTimezone()`
106
61
 
107
- #### `SWIFT_CODE_VALIDATOR(countryCode?: ISO2CountryCode)`
108
-
109
- Validates SWIFT/BIC codes for banks.
110
-
111
- ```typescript
112
- import { SWIFT_CODE_VALIDATOR } from '@temboplus/frontend-react-core';
113
-
114
- // Country-specific validation
115
- const rules = [{ required: true, validator: SWIFT_CODE_VALIDATOR('TZ') }];
116
-
117
- // Multi-country validation
118
- const rules = [{ required: true, validator: SWIFT_CODE_VALIDATOR() }];
62
+ ```ts
63
+ const [timezone, setTimezone] = useTimezone();
119
64
  ```
120
65
 
121
- **Features:**
122
- - Validates against known bank SWIFT codes
123
- - Country-specific validation (TZ, KE)
124
- - Automatic uppercase conversion
125
- - Real bank verification
126
-
127
- #### `ACCOUNT_NUMBER_VALIDATOR(countryCode?: ISO2CountryCode)`
128
-
129
- Validates bank account numbers with country-specific formats.
66
+ - Reads from `localStorage[storageKey]` on mount (after first paint, to avoid SSR mismatch).
67
+ - Falls back to `DEFAULT_TIMEZONE` (`"Africa/Nairobi"`) when nothing's stored.
68
+ - `setTimezone(next)` validates against `Intl.supportedValuesOf("timeZone")` plus TemboPlus operating/anchor zones, then persists.
130
69
 
131
- ```typescript
132
- import { ACCOUNT_NUMBER_VALIDATOR } from '@temboplus/frontend-react-core';
70
+ ### `useTimezoneDate()`
133
71
 
134
- const rules = [{ required: true, validator: ACCOUNT_NUMBER_VALIDATOR('KE') }];
135
- ```
72
+ Boundary helpers bound to the current timezone preference. Re-memoises when the preference changes.
136
73
 
137
- **Country-Specific Rules:**
138
- - **Kenya (KE)**: 10, 12, or 15 alphanumeric characters
139
- - **Tanzania (TZ)**: 9-19 characters
140
- - Automatic space removal and normalization
141
-
142
- #### `AMOUNT_VALIDATOR(options?: AmountValidatorOptions)`
143
-
144
- Validates monetary amounts with currency-specific constraints.
145
-
146
- ```typescript
147
- import { AMOUNT_VALIDATOR } from '@temboplus/frontend-react-core';
148
-
149
- // Basic usage with defaults
150
- const rules = [{ required: true, validator: AMOUNT_VALIDATOR() }];
151
-
152
- // Custom configuration
153
- const rules = [{
154
- required: true,
155
- validator: AMOUNT_VALIDATOR({
156
- currencyCode: 'KES',
157
- min: 100,
158
- max: 50000,
159
- invalidFormatMessage: 'Please enter a valid amount',
160
- minMessage: 'Minimum amount is 100 KES',
161
- maxMessage: 'Maximum amount is 50,000 KES'
162
- })
163
- }];
164
-
165
- // Disable constraints
166
- const rules = [{
167
- required: true,
168
- validator: AMOUNT_VALIDATOR({
169
- currencyCode: 'USD',
170
- min: null, // No minimum
171
- max: null, // No maximum
172
- })
173
- }];
174
- ```
74
+ ```ts
75
+ const { tz, startOfDay, endOfDay, parse, sameDay, toUtc } = useTimezoneDate();
175
76
 
176
- **Default Constraints:**
177
- - **TZS**: Min: 1,000, Max: 1,000,000
178
- - **KES**: Min: 40, Max: 40,000,000
179
- - **Other**: Min: 1, Max: 1,000,000
180
-
181
- **Options:**
182
- ```typescript
183
- interface AmountValidatorOptions {
184
- currencyCode?: CurrencyCode; // Default: 'TZS'
185
- min?: number | null; // Default: currency-specific
186
- max?: number | null; // Default: currency-specific
187
- invalidFormatMessage?: string; // Custom error message
188
- minMessage?: string; // Custom minimum error
189
- maxMessage?: string; // Custom maximum error
190
- }
77
+ const utcStart = startOfDay(pickerDate); // UTC instant of midnight in `tz`
78
+ const utcEnd = endOfDay(pickerDate);
79
+ const zoned = parse("2026-05-15T00:00:00Z"); // TZDate; getDate() reports `tz` wall clock
191
80
  ```
192
81
 
193
- **Features:**
194
- - Supports all currencies from @temboplus/frontend-core
195
- - Handles comma-separated numbers (1,000.50)
196
- - Rejects negative amounts
197
- - Currency-specific formatting
198
- - Returns normalized numeric format
199
-
200
- ## 🎯 Usage Examples
82
+ ### `useFormatDate*()`
201
83
 
202
- ### Complete Form Example
203
-
204
- ```typescript
205
- import React from 'react';
206
- import { Form, Input, Button } from 'antd';
207
- import {
208
- PHONE_NUMBER_VALIDATOR,
209
- AMOUNT_VALIDATOR,
210
- ACCOUNT_NAME_VALIDATOR,
211
- SWIFT_CODE_VALIDATOR,
212
- ACCOUNT_NUMBER_VALIDATOR,
213
- } from '@temboplus/frontend-react-core';
214
-
215
- const PayoutForm: React.FC = () => {
216
- const [form] = Form.useForm();
217
-
218
- const onFinish = (values: any) => {
219
- console.log('Validated values:', values);
220
- };
221
-
222
- return (
223
- <Form form={form} onFinish={onFinish} layout="vertical">
224
- <Form.Item
225
- label="Phone Number"
226
- name="phoneNumber"
227
- rules={[{ required: true, validator: PHONE_NUMBER_VALIDATOR('TZ') }]}
228
- >
229
- <Input placeholder="+255 712 345 678" />
230
- </Form.Item>
231
-
232
- <Form.Item
233
- label="Amount"
234
- name="amount"
235
- rules={[{
236
- required: true,
237
- validator: AMOUNT_VALIDATOR({
238
- currencyCode: 'TZS',
239
- min: 1000,
240
- max: 100000
241
- })
242
- }]}
243
- >
244
- <Input placeholder="10,000" />
245
- </Form.Item>
246
-
247
- <Form.Item
248
- label="Account Name"
249
- name="accountName"
250
- rules={[{ required: true, validator: ACCOUNT_NAME_VALIDATOR }]}
251
- >
252
- <Input placeholder="John Smith" />
253
- </Form.Item>
254
-
255
- <Form.Item
256
- label="Bank SWIFT Code"
257
- name="swiftCode"
258
- rules={[{ required: true, validator: SWIFT_CODE_VALIDATOR('TZ') }]}
259
- >
260
- <Input placeholder="CORUTZTZ" />
261
- </Form.Item>
262
-
263
- <Form.Item
264
- label="Account Number"
265
- name="accountNumber"
266
- rules={[{ required: true, validator: ACCOUNT_NUMBER_VALIDATOR('TZ') }]}
267
- >
268
- <Input placeholder="123456789" />
269
- </Form.Item>
270
-
271
- <Form.Item>
272
- <Button type="primary" htmlType="submit">
273
- Submit
274
- </Button>
275
- </Form.Item>
276
- </Form>
277
- );
278
- };
279
-
280
- export default PayoutForm;
281
- ```
84
+ Reactive formatters. Re-render automatically when the user changes their timezone preference.
282
85
 
283
- ### Multi-Country Support
284
-
285
- ```typescript
286
- import { useState } from 'react';
287
- import { PHONE_NUMBER_VALIDATOR, AMOUNT_VALIDATOR } from '@temboplus/frontend-react-core';
288
-
289
- const MultiCountryForm: React.FC = () => {
290
- const [selectedCountry, setSelectedCountry] = useState<'TZ' | 'KE'>('TZ');
291
-
292
- const getValidationRules = () => ({
293
- phoneNumber: [{
294
- required: true,
295
- validator: PHONE_NUMBER_VALIDATOR(selectedCountry)
296
- }],
297
- amount: [{
298
- required: true,
299
- validator: AMOUNT_VALIDATOR({
300
- currencyCode: selectedCountry === 'TZ' ? 'TZS' : 'KES'
301
- })
302
- }]
303
- });
304
-
305
- // Rest of component...
306
- };
86
+ ```ts
87
+ const fmtDateTime = useFormatDateTime();
88
+ return <span>{fmtDateTime(row.createdAt)}</span>; // "25 MAY 2026, 02:30 PM"
307
89
  ```
308
90
 
309
- ## 🌍 Supported Countries
91
+ Available variants:
310
92
 
311
- | Country | Code | Phone | Mobile | Bank | Currency |
312
- | -------- | ---- | ----- | ------ | ---- | --------------- |
313
- | Tanzania | TZ | ✅ | ✅ | | TZS |
314
- | Kenya | KE | ✅ | ✅ | ✅ | KES |
315
- | Global | * | ✅ | ❌ | ❌ | 150+ currencies |
93
+ | Hook | Output |
94
+ |---|---|
95
+ | `useFormatDate` | `25 MAY 2026` |
96
+ | `useFormatDateTime` | `25 MAY 2026, 02:30 PM` |
97
+ | `useFormatDateTimeWithSeconds` | `25 MAY 2026, 02:30:45 PM` |
98
+ | `useFormatTime` | `02:30 PM` |
316
99
 
317
- ## 🔧 Error Handling
100
+ For non-React call sites, the imperative siblings live in [`@temboplus/frontend-core`](https://github.com/TemboPlus-Frontend/frontend-core-js):
318
101
 
319
- All validators provide clear, user-friendly error messages:
102
+ ```ts
103
+ import { formatDateTime } from "@temboplus/frontend-core";
320
104
 
321
- ```typescript
322
- // Phone validation errors
323
- "Invalid Tanzania phone number format. Please enter a valid Tanzania phone number."
324
-
325
- // Amount validation errors
326
- "Amount must be at least TSh 1,000. Please enter a higher amount."
327
- "Negative amounts are not allowed. Please enter a positive amount."
328
-
329
- // Account name errors
330
- "Invalid account name. Please enter a valid full name with at least two words."
331
-
332
- // SWIFT code errors
333
- "Invalid Tanzania SWIFT code. Please enter a valid Tanzania bank SWIFT/BIC code."
105
+ formatDateTime(date, "Africa/Nairobi"); // "25 MAY 2026, 02:30 PM"
334
106
  ```
335
107
 
336
- ## 🏆 Best Practices
337
-
338
- ### 1. Always Specify Country Context
339
- ```typescript
340
- // ✅ Good - specific country validation
341
- validator: PHONE_NUMBER_VALIDATOR('TZ')
342
-
343
- // ❌ Less ideal - generic validation
344
- validator: PHONE_NUMBER_VALIDATOR()
345
- ```
108
+ ## Development
346
109
 
347
- ### 2. Combine with Required Rules
348
- ```typescript
349
- // ✅ Good - explicit required validation
350
- rules: [
351
- { required: true, message: 'Phone number is required' },
352
- { validator: PHONE_NUMBER_VALIDATOR('TZ') }
353
- ]
110
+ Bun for everything, Biome for lint+format, mise for the toolchain pin.
354
111
 
355
- // ✅ Also good - validator handles required
356
- rules: [{ required: true, validator: PHONE_NUMBER_VALIDATOR('TZ') }]
357
- ```
358
-
359
- ### 3. Custom Error Messages
360
- ```typescript
361
- // ✅ Good - custom business-specific messages
362
- validator: AMOUNT_VALIDATOR({
363
- currencyCode: 'TZS',
364
- min: 5000,
365
- minMessage: 'Minimum transfer amount is TSh 5,000 for international transfers'
366
- })
367
- ```
368
-
369
- ### 4. Normalize Return Values
370
- ```typescript
371
- const onFinish = (values: any) => {
372
- // Validators return normalized values
373
- console.log(values.phoneNumber); // "+255712345678" (E.164 format)
374
- console.log(values.amount); // "10000.00" (normalized number)
375
- console.log(values.swiftCode); // "CORUTZTZ" (uppercase)
376
- };
112
+ ```bash
113
+ mise install # one-time: installs pinned Bun
114
+ bun install
115
+ bun run dev # rollup build in watch mode
377
116
  ```
378
117
 
379
- ## 📄 License
118
+ | Script | Does |
119
+ | --- | --- |
120
+ | `bun run dev` | Rollup build in watch mode |
121
+ | `bun run build` | `tsc` declarations + Rollup bundles to `dist/` (CJS + ESM, each with a `"use client"` banner) |
122
+ | `bun run test` | `bun:test` (passes with zero tests; smoke test only — bun:test has no DOM, so real hook tests live in consumer apps) |
123
+ | `bun run lint` | Biome check |
124
+ | `bun run format` | Biome auto-fix |
125
+ | `bun run typecheck` | `tsc --noEmit` |
380
126
 
381
- Private package for TemboPlus applications.
127
+ Tag-driven releases via `.github/workflows/release.yml` — update `CHANGELOG.md`, bump `package.json`, push a `vX.Y.Z` tag.
382
128
 
383
- ## 🔗 Related Packages
129
+ ## License
384
130
 
385
- - **[@temboplus/frontend-core](https://github.com/TemboPlus-Frontend/temboplus-frontend-core)** - Core business logic and models
131
+ [ISC](./LICENSE) © TemboPlus
package/dist/index.cjs.js CHANGED
@@ -1,2 +1,16 @@
1
- "use strict";var n=require("./theme-provider-RhAw3jw_.js"),t=require("@ebay/nice-modal-react"),e=require("tslib"),s=require("react"),o=require("react-toastify");require("lodash");var r=require("./tembo-notify-Bp14qngd.js");function i(n){return n&&n.__esModule?n:{default:n}}require("antd"),require("./InfoCircleOutlined-B7d2aRfV.js"),require("./ZoomOutOutlined-CW-jqBMI.js");var a=i(t);const l=t=>n.jsxRuntimeExports.jsx(a.default.Provider,{children:t.children}),c=t=>{var{showCloseButton:i=!1,showProgressBar:a=!1}=t,l=e.__rest(t,["showCloseButton","showProgressBar"]);const{colors:c,constants:d}=n.useTemboTheme();return s.useEffect(()=>{r.TemboNotify.init(c,d)},[c,d]),n.jsxRuntimeExports.jsxs(n.jsxRuntimeExports.Fragment,{children:[n.jsxRuntimeExports.jsx(o.ToastContainer,Object.assign({closeButton:i,hideProgressBar:!a},l)),n.jsxRuntimeExports.jsx("style",{children:`\n .Toastify__toast {\n font-family: ${d.typography.fontFamily};\n line-height: 1.5;\n display: flex;\n align-items: center; /* center icon + content */\n }\n\n .Toastify__toast-body {\n padding: 0;\n margin: 0;\n display: flex;\n align-items: flex-start;\n }\n\n .Toastify__toast-icon {\n width: 18px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-inline-end: 6px; /* tighter spacing */\n }\n\n .Toastify__progress-bar {\n display: none;\n }\n\n .Toastify__close-button {\n opacity: 0.5;\n transition: opacity 0.2s ease;\n }\n\n .Toastify__close-button:hover {\n opacity: 1;\n }\n\n /* Force progress bar hidden in case someone enables it unintentionally */\n .Toastify__progress-bar {\n display: none;\n }\n\n /* Smooth entrance animation */\n @keyframes toastSlideIn {\n from {\n transform: translateX(110%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n }\n\n .Toastify__toast--top-right {\n animation: toastSlideIn 0.3s ease-out;\n }\n\n /* Optional: full-width mobile behaviour */\n @media (max-width: 480px) {\n .Toastify__toast-container--top-right {\n right: 0;\n left: 0;\n padding: 0;\n width: 100vw;\n }\n\n .Toastify__toast {\n margin-bottom: 0;\n border-radius: 0;\n }\n }\n `})]})};exports.TemboUIProviders=({children:t,colors:e,constants:s,themeOverrides:o})=>n.jsxRuntimeExports.jsx(n.TemboThemeProvider,{colors:e,constants:s,themeOverrides:o,children:n.jsxRuntimeExports.jsxs(l,{children:[t,n.jsxRuntimeExports.jsx(c,{})]})});
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var useTimezoneDate = require('./use-timezone-date-s1Ru9Fw6.js');
5
+ require('@temboplus/frontend-core');
6
+ require('react');
7
+
8
+
9
+
10
+ exports.useFormatDate = useTimezoneDate.useFormatDate;
11
+ exports.useFormatDateTime = useTimezoneDate.useFormatDateTime;
12
+ exports.useFormatDateTimeWithSeconds = useTimezoneDate.useFormatDateTimeWithSeconds;
13
+ exports.useFormatTime = useTimezoneDate.useFormatTime;
14
+ exports.useTimezone = useTimezoneDate.useTimezone;
15
+ exports.useTimezoneDate = useTimezoneDate.useTimezoneDate;
2
16
  //# sourceMappingURL=index.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/features/dialogs/modal-provider.tsx","../src/features/notifications/toast-container.tsx","../src/providers.tsx"],"sourcesContent":[null,null,null],"names":["TemboModalProvider","props","_jsx","NiceModal","Provider","children","TemboToastContainer","_a","showCloseButton","showProgressBar","rest","__rest","colors","constants","useTemboTheme","useEffect","TemboNotify","init","_jsxs","_Fragment","ToastContainer","Object","assign","closeButton","hideProgressBar","typography","fontFamily","themeOverrides","TemboThemeProvider"],"mappings":"kYAGA,MAAMA,EAAmDC,GAC9CC,EAAAA,kBAAAA,IAACC,EAAAA,QAAUC,mBAAUH,EAAMI,WCsBhCC,EAA2DC,IAAA,IAAAC,gBAC7DA,GAAkB,EAAKC,gBACvBA,GAAkB,GAAKF,EACpBG,EAAIC,EAAAA,OAAAJ,EAHsD,CAAA,kBAAA,oBAK7D,MAAMK,OAAEA,EAAMC,UAAEA,GAAcC,kBAM9B,OAJAC,EAAAA,UAAU,KACNC,cAAYC,KAAKL,EAAQC,IAC1B,CAACD,EAAQC,IAGRK,EAAAA,kBAAAA,KAAAC,EAAAA,kBAAAA,SAAA,CAAAd,SAAA,CACIH,EAAAA,kBAAAA,IAACkB,EAAAA,eAAcC,OAAAC,OAAA,CACXC,YAAaf,EACbgB,iBAAkBf,GACdC,IAGRR,EAAAA,kBAAAA,IAAA,QAAA,CAAAG,SAAQ,0EAEeQ,EAAUY,WAAWC,ovECJS,EAC7DrB,WACAO,SACAC,YACAc,oBAGIzB,EAAAA,kBAAAA,IAAC0B,qBAAkB,CACfhB,OAAQA,EACRC,UAAWA,EACXc,eAAgBA,EAActB,SAE9Ba,EAAAA,kBAAAA,KAAClB,EAAkB,CAAAK,SAAA,CACdA,EACDH,EAAAA,kBAAAA,IAACI,EAAmB,CAAA"}
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;"}
package/dist/index.d.ts CHANGED
@@ -1 +1,2 @@
1
- export * from "./providers.js";
1
+ export * from "./timezone/index.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC"}
package/dist/index.esm.js CHANGED
@@ -1,2 +1,5 @@
1
- import{j as n,u as t,T as o}from"./theme-provider-Ca4P0Hcp.js";import i from"@ebay/nice-modal-react";import{__rest as s}from"tslib";import{useEffect as e}from"react";import{ToastContainer as r}from"react-toastify";import"lodash";import{T as a}from"./tembo-notify-h5Xn66oA.js";import"antd";import"./InfoCircleOutlined-DYs90hdV.js";import"./ZoomOutOutlined-Pw8hpWWK.js";const l=t=>n.jsx(i.Provider,{children:t.children}),m=o=>{var{showCloseButton:i=!1,showProgressBar:l=!1}=o,m=s(o,["showCloseButton","showProgressBar"]);const{colors:c,constants:d}=t();return e(()=>{a.init(c,d)},[c,d]),n.jsxs(n.Fragment,{children:[n.jsx(r,Object.assign({closeButton:i,hideProgressBar:!l},m)),n.jsx("style",{children:`\n .Toastify__toast {\n font-family: ${d.typography.fontFamily};\n line-height: 1.5;\n display: flex;\n align-items: center; /* center icon + content */\n }\n\n .Toastify__toast-body {\n padding: 0;\n margin: 0;\n display: flex;\n align-items: flex-start;\n }\n\n .Toastify__toast-icon {\n width: 18px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-inline-end: 6px; /* tighter spacing */\n }\n\n .Toastify__progress-bar {\n display: none;\n }\n\n .Toastify__close-button {\n opacity: 0.5;\n transition: opacity 0.2s ease;\n }\n\n .Toastify__close-button:hover {\n opacity: 1;\n }\n\n /* Force progress bar hidden in case someone enables it unintentionally */\n .Toastify__progress-bar {\n display: none;\n }\n\n /* Smooth entrance animation */\n @keyframes toastSlideIn {\n from {\n transform: translateX(110%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n }\n\n .Toastify__toast--top-right {\n animation: toastSlideIn 0.3s ease-out;\n }\n\n /* Optional: full-width mobile behaviour */\n @media (max-width: 480px) {\n .Toastify__toast-container--top-right {\n right: 0;\n left: 0;\n padding: 0;\n width: 100vw;\n }\n\n .Toastify__toast {\n margin-bottom: 0;\n border-radius: 0;\n }\n }\n `})]})},c=({children:t,colors:i,constants:s,themeOverrides:e})=>n.jsx(o,{colors:i,constants:s,themeOverrides:e,children:n.jsxs(l,{children:[t,n.jsx(m,{})]})});export{c as TemboUIProviders};
1
+ "use client";
2
+ export { u as useFormatDate, a as useFormatDateTime, b as useFormatDateTimeWithSeconds, c as useFormatTime, d as useTimezone, e as useTimezoneDate } from './use-timezone-date-B-P03WD_.js';
3
+ import '@temboplus/frontend-core';
4
+ import 'react';
2
5
  //# sourceMappingURL=index.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../src/features/dialogs/modal-provider.tsx","../src/features/notifications/toast-container.tsx","../src/providers.tsx"],"sourcesContent":[null,null,null],"names":["TemboModalProvider","props","_jsx","NiceModal","Provider","children","TemboToastContainer","_a","showCloseButton","showProgressBar","rest","__rest","colors","constants","useTemboTheme","useEffect","TemboNotify","init","_jsxs","_Fragment","ToastContainer","Object","assign","closeButton","hideProgressBar","typography","fontFamily","TemboUIProviders","themeOverrides","TemboThemeProvider"],"mappings":"gXAGA,MAAMA,EAAmDC,GAC9CC,EAAAA,IAACC,EAAUC,mBAAUH,EAAMI,WCsBhCC,EAA2DC,IAAA,IAAAC,gBAC7DA,GAAkB,EAAKC,gBACvBA,GAAkB,GAAKF,EACpBG,EAAIC,EAAAJ,EAHsD,CAAA,kBAAA,oBAK7D,MAAMK,OAAEA,EAAMC,UAAEA,GAAcC,IAM9B,OAJAC,EAAU,KACNC,EAAYC,KAAKL,EAAQC,IAC1B,CAACD,EAAQC,IAGRK,EAAAA,KAAAC,EAAAA,SAAA,CAAAd,SAAA,CACIH,EAAAA,IAACkB,EAAcC,OAAAC,OAAA,CACXC,YAAaf,EACbgB,iBAAkBf,GACdC,IAGRR,EAAAA,IAAA,QAAA,CAAAG,SAAQ,0EAEeQ,EAAUY,WAAWC,2tECJ3CC,EAAoD,EAC7DtB,WACAO,SACAC,YACAe,oBAGI1B,EAAAA,IAAC2B,EAAkB,CACfjB,OAAQA,EACRC,UAAWA,EACXe,eAAgBA,EAAcvB,SAE9Ba,EAAAA,KAAClB,EAAkB,CAAAK,SAAA,CACdA,EACDH,EAAAA,IAACI,EAAmB,CAAA"}
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var useTimezoneDate = require('../use-timezone-date-s1Ru9Fw6.js');
5
+ require('@temboplus/frontend-core');
6
+ require('react');
7
+
8
+
9
+
10
+ exports.useFormatDate = useTimezoneDate.useFormatDate;
11
+ exports.useFormatDateTime = useTimezoneDate.useFormatDateTime;
12
+ exports.useFormatDateTimeWithSeconds = useTimezoneDate.useFormatDateTimeWithSeconds;
13
+ exports.useFormatTime = useTimezoneDate.useFormatTime;
14
+ exports.useTimezone = useTimezoneDate.useTimezone;
15
+ exports.useTimezoneDate = useTimezoneDate.useTimezoneDate;
16
+ //# sourceMappingURL=index.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;"}
@@ -0,0 +1,4 @@
1
+ export * from "./use-format-date.js";
2
+ export * from "./use-timezone.js";
3
+ export * from "./use-timezone-date.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/timezone/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC"}
@@ -0,0 +1,5 @@
1
+ "use client";
2
+ export { u as useFormatDate, a as useFormatDateTime, b as useFormatDateTimeWithSeconds, c as useFormatTime, d as useTimezone, e as useTimezoneDate } from '../use-timezone-date-B-P03WD_.js';
3
+ import '@temboplus/frontend-core';
4
+ import 'react';
5
+ //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Centralised, reactive date-formatter hooks. Each returns a function
3
+ * `(date: Date) => string` bound to the user's current timezone
4
+ * preference. Consumers re-render automatically when the preference
5
+ * changes in Settings.
6
+ *
7
+ * For non-React call sites, import the imperative siblings directly
8
+ * from `@temboplus/frontend-core`.
9
+ */
10
+ type Options = {
11
+ storageKey: string;
12
+ defaultTimezone?: string;
13
+ };
14
+ /** `25 MAY 2026` */
15
+ export declare function useFormatDate(options: Options): (date: Date) => string;
16
+ /** `25 MAY 2026, 02:30 PM` */
17
+ export declare function useFormatDateTime(options: Options): (date: Date) => string;
18
+ /** `25 MAY 2026, 02:30:45 PM` — audit rows where seconds matter. */
19
+ export declare function useFormatDateTimeWithSeconds(options: Options): (date: Date) => string;
20
+ /** `02:30 PM` */
21
+ export declare function useFormatTime(options: Options): (date: Date) => string;
22
+ export {};
23
+ //# sourceMappingURL=use-format-date.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-format-date.d.ts","sourceRoot":"","sources":["../../src/timezone/use-format-date.ts"],"names":[],"mappings":"AAYA;;;;;;;;GAQG;AAEH,KAAK,OAAO,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhE,oBAAoB;AACpB,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAGtE;AAED,8BAA8B;AAC9B,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAG1E;AAED,oEAAoE;AACpE,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,OAAO,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAGrF;AAED,iBAAiB;AACjB,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAGtE"}
@@ -0,0 +1,18 @@
1
+ import { toUtcInstant } from "@temboplus/frontend-core";
2
+ /**
3
+ * Boundary helpers bound to the user's current timezone preference.
4
+ * Re-memoises when the preference changes so date pickers / range
5
+ * builders pick up the new offset automatically.
6
+ */
7
+ export declare function useTimezoneDate(options: {
8
+ storageKey: string;
9
+ defaultTimezone?: string;
10
+ }): {
11
+ tz: string;
12
+ startOfDay: (date: Date) => Date;
13
+ endOfDay: (date: Date) => Date;
14
+ parse: (iso: string) => import("@date-fns/tz").TZDate;
15
+ sameDay: (a: Date, b: Date) => boolean;
16
+ toUtc: typeof toUtcInstant;
17
+ };
18
+ //# sourceMappingURL=use-timezone-date.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-timezone-date.d.ts","sourceRoot":"","sources":["../../src/timezone/use-timezone-date.ts"],"names":[],"mappings":"AAEA,OAAO,EAKL,YAAY,EACb,MAAM,0BAA0B,CAAC;AAKlC;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE;;uBAKhE,IAAI;qBACN,IAAI;iBACR,MAAM;iBACN,IAAI,KAAK,IAAI;;EAK/B"}