@temboplus/frontend-react-core 0.1.3-beta.38 → 0.1.3-beta.39

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 (105) hide show
  1. package/README.md +72 -338
  2. package/dist/index.cjs.js +15 -1
  3. package/dist/index.cjs.js.map +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.esm.js +4 -1
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/timezone/index.cjs.js +16 -0
  8. package/dist/{notifications → timezone}/index.cjs.js.map +1 -1
  9. package/dist/timezone/index.d.ts +3 -0
  10. package/dist/timezone/index.js +5 -0
  11. package/dist/{notifications → timezone}/index.js.map +1 -1
  12. package/dist/timezone/use-format-date.d.ts +22 -0
  13. package/dist/timezone/use-timezone-date.d.ts +17 -0
  14. package/dist/timezone/use-timezone.d.ts +19 -0
  15. package/dist/use-format-date--9gcnP7_.js +82 -0
  16. package/dist/use-format-date--9gcnP7_.js.map +1 -0
  17. package/dist/use-format-date-BvwtJTnU.js +89 -0
  18. package/dist/use-format-date-BvwtJTnU.js.map +1 -0
  19. package/package.json +23 -51
  20. package/dist/InfoCircleOutlined-B4_-Z3H4.js +0 -6
  21. package/dist/InfoCircleOutlined-B4_-Z3H4.js.map +0 -1
  22. package/dist/InfoCircleOutlined-pKxRobXG.js +0 -6
  23. package/dist/InfoCircleOutlined-pKxRobXG.js.map +0 -1
  24. package/dist/alerts/index.cjs.js +0 -2
  25. package/dist/alerts/index.cjs.js.map +0 -1
  26. package/dist/alerts/index.d.ts +0 -1
  27. package/dist/alerts/index.js +0 -2
  28. package/dist/alerts/index.js.map +0 -1
  29. package/dist/dialogs/index.cjs.js +0 -2
  30. package/dist/dialogs/index.cjs.js.map +0 -1
  31. package/dist/dialogs/index.d.ts +0 -1
  32. package/dist/dialogs/index.js +0 -2
  33. package/dist/dialogs/index.js.map +0 -1
  34. package/dist/features/alerts/alert.d.ts +0 -12
  35. package/dist/features/alerts/alert.js +0 -95
  36. package/dist/features/alerts/index.d.ts +0 -1
  37. package/dist/features/alerts/index.js +0 -1
  38. package/dist/features/dialogs/index.d.ts +0 -1
  39. package/dist/features/dialogs/index.js +0 -1
  40. package/dist/features/dialogs/modal-provider.d.ts +0 -3
  41. package/dist/features/dialogs/modal-provider.js +0 -6
  42. package/dist/features/dialogs/tembo-confirm.d.ts +0 -63
  43. package/dist/features/dialogs/tembo-confirm.js +0 -98
  44. package/dist/features/input-validation/account-name-validator.d.ts +0 -13
  45. package/dist/features/input-validation/account-name-validator.js +0 -28
  46. package/dist/features/input-validation/account-number-validator.d.ts +0 -13
  47. package/dist/features/input-validation/account-number-validator.js +0 -65
  48. package/dist/features/input-validation/amount-validator.d.ts +0 -78
  49. package/dist/features/input-validation/amount-validator.js +0 -100
  50. package/dist/features/input-validation/index.d.ts +0 -5
  51. package/dist/features/input-validation/index.js +0 -5
  52. package/dist/features/input-validation/phone-number-validator.d.ts +0 -25
  53. package/dist/features/input-validation/phone-number-validator.js +0 -79
  54. package/dist/features/input-validation/swift-code-validator.d.ts +0 -13
  55. package/dist/features/input-validation/swift-code-validator.js +0 -38
  56. package/dist/features/notifications/index.d.ts +0 -3
  57. package/dist/features/notifications/index.js +0 -3
  58. package/dist/features/notifications/tembo-notify.d.ts +0 -51
  59. package/dist/features/notifications/tembo-notify.js +0 -140
  60. package/dist/features/notifications/toast-config.d.ts +0 -13
  61. package/dist/features/notifications/toast-config.js +0 -60
  62. package/dist/features/notifications/toast-container.d.ts +0 -19
  63. package/dist/features/notifications/toast-container.js +0 -104
  64. package/dist/index.js +0 -1
  65. package/dist/notifications/index.cjs.js +0 -2
  66. package/dist/notifications/index.d.ts +0 -1
  67. package/dist/notifications/index.js +0 -2
  68. package/dist/providers.d.ts +0 -7
  69. package/dist/providers.js +0 -11
  70. package/dist/tembo-notify-By9oLCSX.js +0 -3
  71. package/dist/tembo-notify-By9oLCSX.js.map +0 -1
  72. package/dist/tembo-notify-CssdTeIP.js +0 -3
  73. package/dist/tembo-notify-CssdTeIP.js.map +0 -1
  74. package/dist/theme/deprecated/colors.d.ts +0 -278
  75. package/dist/theme/deprecated/colors.js +0 -212
  76. package/dist/theme/deprecated/constants.d.ts +0 -143
  77. package/dist/theme/deprecated/constants.js +0 -82
  78. package/dist/theme/deprecated/theme-provider.d.ts +0 -100
  79. package/dist/theme/deprecated/theme-provider.js +0 -405
  80. package/dist/theme/index.cjs.js +0 -2
  81. package/dist/theme/index.cjs.js.map +0 -1
  82. package/dist/theme/index.d.ts +0 -4
  83. package/dist/theme/index.js +0 -2
  84. package/dist/theme/index.js.map +0 -1
  85. package/dist/theme/theme-config.d.ts +0 -15
  86. package/dist/theme/theme-config.js +0 -735
  87. package/dist/theme/theme-provider.d.ts +0 -29
  88. package/dist/theme/theme-provider.js +0 -32
  89. package/dist/theme/tokens/colors.d.ts +0 -153
  90. package/dist/theme/tokens/colors.js +0 -306
  91. package/dist/theme/tokens/constants.d.ts +0 -136
  92. package/dist/theme/tokens/constants.js +0 -137
  93. package/dist/theme-provider-XqWasApp.js +0 -11
  94. package/dist/theme-provider-XqWasApp.js.map +0 -1
  95. package/dist/theme-provider-c4R_KW4X.js +0 -2
  96. package/dist/theme-provider-c4R_KW4X.js.map +0 -1
  97. package/dist/theme-provider-slJZwhTc.js +0 -11
  98. package/dist/theme-provider-slJZwhTc.js.map +0 -1
  99. package/dist/theme-provider-slTbQLX5.js +0 -2
  100. package/dist/theme-provider-slTbQLX5.js.map +0 -1
  101. package/dist/validation/index.cjs.js +0 -2
  102. package/dist/validation/index.cjs.js.map +0 -1
  103. package/dist/validation/index.d.ts +0 -1
  104. package/dist/validation/index.js +0 -2
  105. package/dist/validation/index.js.map +0 -1
package/README.md CHANGED
@@ -1,385 +1,119 @@
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
10
+ bun add @temboplus/frontend-react-core
9
11
  # or
10
- yarn add @temboplus/frontend-react-core
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
- };
33
- ```
34
-
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
42
-
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
48
-
49
- ## 📋 API Reference
50
-
51
- ### Form Validators
52
-
53
- All validators follow the Ant Design validator pattern and return Promise-based validation results.
54
-
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
66
- ```
67
-
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).
77
-
78
- ```typescript
79
- import { MOBILE_PHONE_VALIDATOR } from '@temboplus/frontend-react-core';
80
-
81
- const rules = [{ required: true, validator: MOBILE_PHONE_VALIDATOR('TZ') }];
18
+ bun add react
82
19
  ```
83
20
 
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
21
+ Runtime dependency (auto-installed):
89
22
 
90
- #### `ACCOUNT_NAME_VALIDATOR`
23
+ - `@temboplus/frontend-core` — the pure utilities and domain entities the hooks wrap.
91
24
 
92
- Validates bank account holder names.
93
-
94
- ```typescript
95
- import { ACCOUNT_NAME_VALIDATOR } from '@temboplus/frontend-react-core';
96
-
97
- const rules = [{ required: true, validator: ACCOUNT_NAME_VALIDATOR }];
98
- ```
25
+ ## Subpath imports
99
26
 
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
27
+ Import the focused surface or the root entry; both work:
106
28
 
107
- #### `SWIFT_CODE_VALIDATOR(countryCode?: ISO2CountryCode)`
29
+ ```ts
30
+ // Focused (recommended — keeps your bundles honest):
31
+ import { useTimezone } from "@temboplus/frontend-react-core/timezone";
108
32
 
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() }];
33
+ // Or the root:
34
+ import { useTimezone } from "@temboplus/frontend-react-core";
119
35
  ```
120
36
 
121
- **Features:**
122
- - Validates against known bank SWIFT codes
123
- - Country-specific validation (TZ, KE)
124
- - Automatic uppercase conversion
125
- - Real bank verification
37
+ ## Timezone hooks
126
38
 
127
- #### `ACCOUNT_NUMBER_VALIDATOR(countryCode?: ISO2CountryCode)`
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:
128
40
 
129
- Validates bank account numbers with country-specific formats.
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";
130
50
 
131
- ```typescript
132
- import { ACCOUNT_NUMBER_VALIDATOR } from '@temboplus/frontend-react-core';
51
+ const STORAGE_KEY = "myapp.timezone";
133
52
 
134
- const rules = [{ required: true, validator: ACCOUNT_NUMBER_VALIDATOR('KE') }];
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 });
135
58
  ```
136
59
 
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
- ```
60
+ ### `useTimezone()`
175
61
 
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
- }
62
+ ```ts
63
+ const [timezone, setTimezone] = useTimezone();
191
64
  ```
192
65
 
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
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.
199
69
 
200
- ## 🎯 Usage Examples
70
+ ### `useTimezoneDate()`
201
71
 
202
- ### Complete Form Example
72
+ Boundary helpers bound to the current timezone preference. Re-memoises when the preference changes.
203
73
 
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
- ```
74
+ ```ts
75
+ const { tz, startOfDay, endOfDay, parse, sameDay, toUtc } = useTimezoneDate();
282
76
 
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
- };
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
307
80
  ```
308
81
 
309
- ## 🌍 Supported Countries
310
-
311
- | Country | Code | Phone | Mobile | Bank | Currency |
312
- | -------- | ---- | ----- | ------ | ---- | --------------- |
313
- | Tanzania | TZ | ✅ | ✅ | ✅ | TZS |
314
- | Kenya | KE | ✅ | ✅ | ✅ | KES |
315
- | Global | * | ✅ | ❌ | ❌ | 150+ currencies |
316
-
317
- ## 🔧 Error Handling
318
-
319
- All validators provide clear, user-friendly error messages:
320
-
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."
82
+ ### `useFormatDate*()`
328
83
 
329
- // Account name errors
330
- "Invalid account name. Please enter a valid full name with at least two words."
84
+ Reactive formatters. Re-render automatically when the user changes their timezone preference.
331
85
 
332
- // SWIFT code errors
333
- "Invalid Tanzania SWIFT code. Please enter a valid Tanzania bank SWIFT/BIC code."
86
+ ```ts
87
+ const fmtDateTime = useFormatDateTime();
88
+ return <span>{fmtDateTime(row.createdAt)}</span>; // "25 MAY 2026, 02:30 PM"
334
89
  ```
335
90
 
336
- ## 🏆 Best Practices
91
+ Available variants:
337
92
 
338
- ### 1. Always Specify Country Context
339
- ```typescript
340
- // Good - specific country validation
341
- validator: PHONE_NUMBER_VALIDATOR('TZ')
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` |
342
99
 
343
- // Less ideal - generic validation
344
- validator: PHONE_NUMBER_VALIDATOR()
345
- ```
100
+ For non-React call sites, the imperative siblings live in [`@temboplus/frontend-core`](https://github.com/TemboPlus-Frontend/frontend-core-js):
346
101
 
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
- ]
102
+ ```ts
103
+ import { formatDateTime } from "@temboplus/frontend-core";
354
104
 
355
- // Also good - validator handles required
356
- rules: [{ required: true, validator: PHONE_NUMBER_VALIDATOR('TZ') }]
105
+ formatDateTime(date, "Africa/Nairobi"); // "25 MAY 2026, 02:30 PM"
357
106
  ```
358
107
 
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
- ```
108
+ ## Development
368
109
 
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
- };
110
+ ```bash
111
+ make help # list targets
112
+ make install # bun install
113
+ make build # rollup + tsc declarations
114
+ make test # bun test
115
+ make publish-dry # see what would publish
116
+ make publish # build, test, then publish to npm
377
117
  ```
378
118
 
379
- ## 📄 License
380
-
381
- Private package for TemboPlus applications.
382
-
383
- ## 🔗 Related Packages
384
-
385
- - **[@temboplus/frontend-core](https://github.com/TemboPlus-Frontend/temboplus-frontend-core)** - Core business logic and models
119
+ Bun handles install, test, and publish. Rollup bundles ESM + CJS (with a `"use client"` banner per output, since every hook is client-only).
package/dist/index.cjs.js CHANGED
@@ -1,2 +1,16 @@
1
- "use strict";var n=require("./theme-provider-XqWasApp.js"),t=require("@ebay/nice-modal-react"),e=require("tslib"),r=require("react"),o=require("react-toastify"),s=require("./tembo-notify-CssdTeIP.js");function i(n){return n&&n.__esModule?n:{default:n}}require("antd"),require("./InfoCircleOutlined-pKxRobXG.js");var a=i(t);const d=t=>n.jsxRuntimeExports.jsx(a.default.Provider,{children:t.children}),m=t=>{var{showCloseButton:i=!1,showProgressBar:a=!0}=t,d=e.__rest(t,["showCloseButton","showProgressBar"]);const{colors:m,constants:l}=n.useNewTemboTheme();return r.useEffect(()=>{s.TemboNotify.init(m,l)},[m,l]),n.jsxRuntimeExports.jsxs(n.jsxRuntimeExports.Fragment,{children:[n.jsxRuntimeExports.jsx(o.ToastContainer,Object.assign({closeButton:i,hideProgressBar:!a},d)),n.jsxRuntimeExports.jsx("style",{children:`\n .Toastify__toast-container {\n z-index: 9999;\n }\n\n .Toastify__toast {\n font-family: ${l.typography.fontFamily};\n line-height: 1.5;\n display: flex;\n align-items: center;\n overflow: hidden;\n margin-bottom: 1rem;\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;\n }\n\n .Toastify__progress-bar-container {\n height: 4px;\n background: transparent !important;\n }\n\n .Toastify__progress-bar {\n height: 4px !important;\n background: #000000 !important;\n }\n\n .Toastify__progress-bar--bg {\n background: transparent !important;\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 /* 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 .Toastify__progress-bar {\n border-radius: 0;\n }\n }\n `})]})};exports.TemboUIProviders=({children:t,mode:e,antdThemeOverrides:r})=>n.jsxRuntimeExports.jsx(n.TemboThemeProvider,{mode:e,antdThemeOverrides:r,children:n.jsxRuntimeExports.jsxs(d,{children:[t,n.jsxRuntimeExports.jsx(m,{showProgressBar:!0})]})});
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var useFormatDate = require('./use-format-date-BvwtJTnU.js');
5
+ require('react');
6
+ require('@temboplus/frontend-core');
7
+
8
+
9
+
10
+ exports.useFormatDate = useFormatDate.useFormatDate;
11
+ exports.useFormatDateTime = useFormatDate.useFormatDateTime;
12
+ exports.useFormatDateTimeWithSeconds = useFormatDate.useFormatDateTimeWithSeconds;
13
+ exports.useFormatTime = useFormatDate.useFormatTime;
14
+ exports.useTimezone = useFormatDate.useTimezone;
15
+ exports.useTimezoneDate = useFormatDate.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","useNewTemboTheme","useEffect","TemboNotify","init","_jsxs","_Fragment","ToastContainer","Object","assign","closeButton","hideProgressBar","typography","fontFamily","mode","antdThemeOverrides","TemboThemeProvider"],"mappings":"mUAGA,MAAMA,EAAmDC,GAC9CC,EAAAA,kBAAAA,IAACC,EAAAA,QAAUC,mBAAUH,EAAMI,WCsBhCC,EAA2DC,IAAA,IAAAC,gBAC7DA,GAAkB,EAAKC,gBACvBA,GAAkB,GAAIF,EACnBG,EAAIC,EAAAA,OAAAJ,EAHsD,CAAA,kBAAA,oBAK7D,MAAMK,OAAEA,EAAMC,UAAEA,GAAcC,qBAM9B,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,iLAMeQ,EAAUY,WAAWC,0jFC1CW,EAC/DrB,WACAsB,OACAC,wBAGI1B,EAAAA,kBAAAA,IAAC2B,qBAAkB,CACfF,KAAMA,EACNC,mBAAoBA,EAAkBvB,SAEtCa,EAAAA,kBAAAA,KAAClB,EAAkB,CAAAK,SAAA,CACdA,EACDH,EAAAA,kBAAAA,IAACI,EAAmB,CAACG,iBAAe"}
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;"}
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export * from "./providers.js";
1
+ export * from "./timezone/index.js";
package/dist/index.esm.js CHANGED
@@ -1,2 +1,5 @@
1
- import{j as n,u as t,T as o}from"./theme-provider-slJZwhTc.js";import r from"@ebay/nice-modal-react";import{__rest as a}from"tslib";import{useEffect as i}from"react";import{ToastContainer as s}from"react-toastify";import{T as e}from"./tembo-notify-By9oLCSX.js";import"antd";import"./InfoCircleOutlined-B4_-Z3H4.js";const m=t=>n.jsx(r.Provider,{children:t.children}),d=o=>{var{showCloseButton:r=!1,showProgressBar:m=!0}=o,d=a(o,["showCloseButton","showProgressBar"]);const{colors:l,constants:p}=t();return i(()=>{e.init(l,p)},[l,p]),n.jsxs(n.Fragment,{children:[n.jsx(s,Object.assign({closeButton:r,hideProgressBar:!m},d)),n.jsx("style",{children:`\n .Toastify__toast-container {\n z-index: 9999;\n }\n\n .Toastify__toast {\n font-family: ${p.typography.fontFamily};\n line-height: 1.5;\n display: flex;\n align-items: center;\n overflow: hidden;\n margin-bottom: 1rem;\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;\n }\n\n .Toastify__progress-bar-container {\n height: 4px;\n background: transparent !important;\n }\n\n .Toastify__progress-bar {\n height: 4px !important;\n background: #000000 !important;\n }\n\n .Toastify__progress-bar--bg {\n background: transparent !important;\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 /* 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 .Toastify__progress-bar {\n border-radius: 0;\n }\n }\n `})]})},l=({children:t,mode:r,antdThemeOverrides:a})=>n.jsx(o,{mode:r,antdThemeOverrides:a,children:n.jsxs(m,{children:[t,n.jsx(d,{showProgressBar:!0})]})});export{l as TemboUIProviders};
1
+ "use client";
2
+ export { b as useFormatDate, c as useFormatDateTime, d as useFormatDateTimeWithSeconds, e as useFormatTime, u as useTimezone, a as useTimezoneDate } from './use-format-date--9gcnP7_.js';
3
+ import 'react';
4
+ import '@temboplus/frontend-core';
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","useNewTemboTheme","useEffect","TemboNotify","init","_jsxs","_Fragment","ToastContainer","Object","assign","closeButton","hideProgressBar","typography","fontFamily","TemboUIProviders","mode","antdThemeOverrides","TemboThemeProvider"],"mappings":"2TAGA,MAAMA,EAAmDC,GAC9CC,EAAAA,IAACC,EAAUC,mBAAUH,EAAMI,WCsBhCC,EAA2DC,IAAA,IAAAC,gBAC7DA,GAAkB,EAAKC,gBACvBA,GAAkB,GAAIF,EACnBG,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,iLAMeQ,EAAUY,WAAWC,iiFC1C3CC,EAAsD,EAC/DtB,WACAuB,OACAC,wBAGI3B,EAAAA,IAAC4B,EAAkB,CACfF,KAAMA,EACNC,mBAAoBA,EAAkBxB,SAEtCa,EAAAA,KAAClB,EAAkB,CAAAK,SAAA,CACdA,EACDH,EAAAA,IAACI,EAAmB,CAACG,iBAAe"}
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var useFormatDate = require('../use-format-date-BvwtJTnU.js');
5
+ require('react');
6
+ require('@temboplus/frontend-core');
7
+
8
+
9
+
10
+ exports.useFormatDate = useFormatDate.useFormatDate;
11
+ exports.useFormatDateTime = useFormatDate.useFormatDateTime;
12
+ exports.useFormatDateTimeWithSeconds = useFormatDate.useFormatDateTimeWithSeconds;
13
+ exports.useFormatTime = useFormatDate.useFormatTime;
14
+ exports.useTimezone = useFormatDate.useTimezone;
15
+ exports.useTimezoneDate = useFormatDate.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,3 @@
1
+ export * from "./use-timezone.js";
2
+ export * from "./use-timezone-date.js";
3
+ export * from "./use-format-date.js";
@@ -0,0 +1,5 @@
1
+ "use client";
2
+ export { b as useFormatDate, c as useFormatDateTime, d as useFormatDateTimeWithSeconds, e as useFormatTime, u as useTimezone, a as useTimezoneDate } from '../use-format-date--9gcnP7_.js';
3
+ import 'react';
4
+ import '@temboplus/frontend-core';
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,22 @@
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 {};
@@ -0,0 +1,17 @@
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
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Hook over the user's timezone preference, persisted to localStorage.
3
+ *
4
+ * Each app supplies its own `storageKey` (e.g. `"afloat.timezone"`,
5
+ * `"dashboard.timezone"`) so preferences don't collide across products
6
+ * sharing the same browser origin.
7
+ *
8
+ * Initial render returns `defaultTimezone` (or {@link DEFAULT_TIMEZONE});
9
+ * the persisted value is rehydrated on mount to avoid SSR mismatch.
10
+ *
11
+ * When backend-stored user preferences arrive later, the source of truth
12
+ * inside this hook can swap to a hybrid local+remote scheme without
13
+ * consumers having to touch their call sites — the `[tz, setTz]` shape
14
+ * stays stable.
15
+ */
16
+ export declare function useTimezone(options: {
17
+ storageKey: string;
18
+ defaultTimezone?: string;
19
+ }): readonly [string, (next: string) => void];