orn-ui 0.1.1 → 0.2.1
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 +20 -2
- package/lib/commonjs/atoms/PressableScale.js +9 -10
- package/lib/commonjs/atoms/PressableScale.js.map +1 -1
- package/lib/commonjs/atoms/Skeleton.js +96 -0
- package/lib/commonjs/atoms/Skeleton.js.map +1 -0
- package/lib/commonjs/atoms/Transition.js +171 -0
- package/lib/commonjs/atoms/Transition.js.map +1 -0
- package/lib/commonjs/atoms/index.js +20 -0
- package/lib/commonjs/atoms/index.js.map +1 -1
- package/lib/commonjs/molecules/OptionCard.js +55 -22
- package/lib/commonjs/molecules/OptionCard.js.map +1 -1
- package/lib/commonjs/molecules/{ThemeToggle.js → SegmentedControl.js} +36 -36
- package/lib/commonjs/molecules/SegmentedControl.js.map +1 -0
- package/lib/commonjs/molecules/Stepper.js +124 -47
- package/lib/commonjs/molecules/Stepper.js.map +1 -1
- package/lib/commonjs/molecules/Steps.js +36 -6
- package/lib/commonjs/molecules/Steps.js.map +1 -1
- package/lib/commonjs/molecules/index.js +9 -3
- package/lib/commonjs/molecules/index.js.map +1 -1
- package/lib/commonjs/organisms/AlertProvider.js +26 -0
- package/lib/commonjs/organisms/AlertProvider.js.map +1 -1
- package/lib/commonjs/organisms/BottomSheet.js +118 -39
- package/lib/commonjs/organisms/BottomSheet.js.map +1 -1
- package/lib/commonjs/organisms/DateField.js +46 -15
- package/lib/commonjs/organisms/DateField.js.map +1 -1
- package/lib/commonjs/organisms/DatePicker.js +129 -50
- package/lib/commonjs/organisms/DatePicker.js.map +1 -1
- package/lib/commonjs/organisms/List.js +66 -44
- package/lib/commonjs/organisms/List.js.map +1 -1
- package/lib/commonjs/organisms/Modal.js +42 -87
- package/lib/commonjs/organisms/Modal.js.map +1 -1
- package/lib/commonjs/organisms/NavigationBar.js +210 -0
- package/lib/commonjs/organisms/NavigationBar.js.map +1 -0
- package/lib/commonjs/organisms/Screen.js +9 -2
- package/lib/commonjs/organisms/Screen.js.map +1 -1
- package/lib/commonjs/organisms/SearchList.js +95 -27
- package/lib/commonjs/organisms/SearchList.js.map +1 -1
- package/lib/commonjs/organisms/Select.js +222 -50
- package/lib/commonjs/organisms/Select.js.map +1 -1
- package/lib/commonjs/organisms/ThemeToggle.js +52 -0
- package/lib/commonjs/organisms/ThemeToggle.js.map +1 -0
- package/lib/commonjs/organisms/Toast.js +8 -22
- package/lib/commonjs/organisms/Toast.js.map +1 -1
- package/lib/commonjs/organisms/ToastProvider.js +24 -0
- package/lib/commonjs/organisms/ToastProvider.js.map +1 -1
- package/lib/commonjs/organisms/Wizard.js +23 -2
- package/lib/commonjs/organisms/Wizard.js.map +1 -1
- package/lib/commonjs/organisms/imperativeBridge.js +28 -0
- package/lib/commonjs/organisms/imperativeBridge.js.map +1 -0
- package/lib/commonjs/organisms/index.js +50 -0
- package/lib/commonjs/organisms/index.js.map +1 -1
- package/lib/commonjs/theme/colors.js +207 -0
- package/lib/commonjs/theme/colors.js.map +1 -0
- package/lib/commonjs/theme/createTheme.js +25 -2
- package/lib/commonjs/theme/createTheme.js.map +1 -1
- package/lib/commonjs/theme/index.js +25 -0
- package/lib/commonjs/theme/index.js.map +1 -1
- package/lib/commonjs/theme/labels.js +5 -1
- package/lib/commonjs/theme/labels.js.map +1 -1
- package/lib/commonjs/theme/palettes.js +5 -2
- package/lib/commonjs/theme/palettes.js.map +1 -1
- package/lib/module/atoms/PressableScale.js +9 -10
- package/lib/module/atoms/PressableScale.js.map +1 -1
- package/lib/module/atoms/Skeleton.js +91 -0
- package/lib/module/atoms/Skeleton.js.map +1 -0
- package/lib/module/atoms/Transition.js +165 -0
- package/lib/module/atoms/Transition.js.map +1 -0
- package/lib/module/atoms/index.js +2 -0
- package/lib/module/atoms/index.js.map +1 -1
- package/lib/module/molecules/OptionCard.js +56 -23
- package/lib/module/molecules/OptionCard.js.map +1 -1
- package/lib/module/molecules/{ThemeToggle.js → SegmentedControl.js} +36 -36
- package/lib/module/molecules/SegmentedControl.js.map +1 -0
- package/lib/module/molecules/Stepper.js +125 -49
- package/lib/module/molecules/Stepper.js.map +1 -1
- package/lib/module/molecules/Steps.js +37 -7
- package/lib/module/molecules/Steps.js.map +1 -1
- package/lib/module/molecules/index.js +2 -2
- package/lib/module/molecules/index.js.map +1 -1
- package/lib/module/organisms/AlertProvider.js +24 -1
- package/lib/module/organisms/AlertProvider.js.map +1 -1
- package/lib/module/organisms/BottomSheet.js +120 -41
- package/lib/module/organisms/BottomSheet.js.map +1 -1
- package/lib/module/organisms/DateField.js +49 -18
- package/lib/module/organisms/DateField.js.map +1 -1
- package/lib/module/organisms/DatePicker.js +131 -52
- package/lib/module/organisms/DatePicker.js.map +1 -1
- package/lib/module/organisms/List.js +68 -46
- package/lib/module/organisms/List.js.map +1 -1
- package/lib/module/organisms/Modal.js +44 -89
- package/lib/module/organisms/Modal.js.map +1 -1
- package/lib/module/organisms/NavigationBar.js +205 -0
- package/lib/module/organisms/NavigationBar.js.map +1 -0
- package/lib/module/organisms/Screen.js +9 -2
- package/lib/module/organisms/Screen.js.map +1 -1
- package/lib/module/organisms/SearchList.js +96 -28
- package/lib/module/organisms/SearchList.js.map +1 -1
- package/lib/module/organisms/Select.js +224 -52
- package/lib/module/organisms/Select.js.map +1 -1
- package/lib/module/organisms/ThemeToggle.js +47 -0
- package/lib/module/organisms/ThemeToggle.js.map +1 -0
- package/lib/module/organisms/Toast.js +10 -24
- package/lib/module/organisms/Toast.js.map +1 -1
- package/lib/module/organisms/ToastProvider.js +21 -0
- package/lib/module/organisms/ToastProvider.js.map +1 -1
- package/lib/module/organisms/Wizard.js +24 -3
- package/lib/module/organisms/Wizard.js.map +1 -1
- package/lib/module/organisms/imperativeBridge.js +24 -0
- package/lib/module/organisms/imperativeBridge.js.map +1 -0
- package/lib/module/organisms/index.js +4 -2
- package/lib/module/organisms/index.js.map +1 -1
- package/lib/module/theme/colors.js +194 -0
- package/lib/module/theme/colors.js.map +1 -0
- package/lib/module/theme/createTheme.js +25 -2
- package/lib/module/theme/createTheme.js.map +1 -1
- package/lib/module/theme/index.js +1 -0
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/theme/labels.js +5 -1
- package/lib/module/theme/labels.js.map +1 -1
- package/lib/module/theme/palettes.js +5 -2
- package/lib/module/theme/palettes.js.map +1 -1
- package/lib/typescript/atoms/PressableScale.d.ts +3 -2
- package/lib/typescript/atoms/PressableScale.d.ts.map +1 -1
- package/lib/typescript/atoms/Skeleton.d.ts +29 -0
- package/lib/typescript/atoms/Skeleton.d.ts.map +1 -0
- package/lib/typescript/atoms/Transition.d.ts +51 -0
- package/lib/typescript/atoms/Transition.d.ts.map +1 -0
- package/lib/typescript/atoms/index.d.ts +4 -0
- package/lib/typescript/atoms/index.d.ts.map +1 -1
- package/lib/typescript/molecules/OptionCard.d.ts +11 -6
- package/lib/typescript/molecules/OptionCard.d.ts.map +1 -1
- package/lib/typescript/molecules/SegmentedControl.d.ts +24 -0
- package/lib/typescript/molecules/SegmentedControl.d.ts.map +1 -0
- package/lib/typescript/molecules/Stepper.d.ts +35 -3
- package/lib/typescript/molecules/Stepper.d.ts.map +1 -1
- package/lib/typescript/molecules/Steps.d.ts.map +1 -1
- package/lib/typescript/molecules/index.d.ts +4 -4
- package/lib/typescript/molecules/index.d.ts.map +1 -1
- package/lib/typescript/organisms/AlertProvider.d.ts +9 -0
- package/lib/typescript/organisms/AlertProvider.d.ts.map +1 -1
- package/lib/typescript/organisms/BottomSheet.d.ts +26 -4
- package/lib/typescript/organisms/BottomSheet.d.ts.map +1 -1
- package/lib/typescript/organisms/DateField.d.ts +23 -5
- package/lib/typescript/organisms/DateField.d.ts.map +1 -1
- package/lib/typescript/organisms/DatePicker.d.ts +5 -1
- package/lib/typescript/organisms/DatePicker.d.ts.map +1 -1
- package/lib/typescript/organisms/List.d.ts +9 -2
- package/lib/typescript/organisms/List.d.ts.map +1 -1
- package/lib/typescript/organisms/Modal.d.ts +3 -5
- package/lib/typescript/organisms/Modal.d.ts.map +1 -1
- package/lib/typescript/organisms/NavigationBar.d.ts +32 -0
- package/lib/typescript/organisms/NavigationBar.d.ts.map +1 -0
- package/lib/typescript/organisms/Screen.d.ts +2 -1
- package/lib/typescript/organisms/Screen.d.ts.map +1 -1
- package/lib/typescript/organisms/SearchList.d.ts +21 -5
- package/lib/typescript/organisms/SearchList.d.ts.map +1 -1
- package/lib/typescript/organisms/Select.d.ts +19 -4
- package/lib/typescript/organisms/Select.d.ts.map +1 -1
- package/lib/typescript/{molecules → organisms}/ThemeToggle.d.ts +4 -4
- package/lib/typescript/organisms/ThemeToggle.d.ts.map +1 -0
- package/lib/typescript/organisms/Toast.d.ts.map +1 -1
- package/lib/typescript/organisms/ToastProvider.d.ts +8 -0
- package/lib/typescript/organisms/ToastProvider.d.ts.map +1 -1
- package/lib/typescript/organisms/Wizard.d.ts +7 -1
- package/lib/typescript/organisms/Wizard.d.ts.map +1 -1
- package/lib/typescript/organisms/imperativeBridge.d.ts +12 -0
- package/lib/typescript/organisms/imperativeBridge.d.ts.map +1 -0
- package/lib/typescript/organisms/index.d.ts +7 -3
- package/lib/typescript/organisms/index.d.ts.map +1 -1
- package/lib/typescript/theme/colors.d.ts +44 -0
- package/lib/typescript/theme/colors.d.ts.map +1 -0
- package/lib/typescript/theme/createTheme.d.ts +3 -0
- package/lib/typescript/theme/createTheme.d.ts.map +1 -1
- package/lib/typescript/theme/index.d.ts +3 -1
- package/lib/typescript/theme/index.d.ts.map +1 -1
- package/lib/typescript/theme/labels.d.ts +6 -0
- package/lib/typescript/theme/labels.d.ts.map +1 -1
- package/lib/typescript/theme/palettes.d.ts.map +1 -1
- package/lib/typescript/theme/types.d.ts +9 -0
- package/lib/typescript/theme/types.d.ts.map +1 -1
- package/package.json +67 -6
- package/registry/alert-provider.json +9 -2
- package/registry/bottom-sheet.json +6 -2
- package/registry/core.json +9 -5
- package/registry/date-field.json +2 -1
- package/registry/date-picker.json +1 -1
- package/registry/index.json +99 -30
- package/registry/list.json +2 -1
- package/registry/manifest.json +44 -6
- package/registry/modal.json +3 -2
- package/registry/navigation-bar.json +18 -0
- package/registry/option-card.json +3 -2
- package/registry/pressable-scale.json +1 -1
- package/registry/screen.json +1 -1
- package/registry/search-list.json +2 -1
- package/registry/segmented-control.json +18 -0
- package/registry/select.json +1 -1
- package/registry/skeleton.json +18 -0
- package/registry/stepper.json +3 -2
- package/registry/steps.json +1 -1
- package/registry/theme-toggle.json +5 -4
- package/registry/toast-provider.json +11 -3
- package/registry/toast.json +3 -2
- package/registry/transition.json +19 -0
- package/registry/wizard.json +3 -2
- package/src/atoms/PressableScale.tsx +16 -6
- package/src/atoms/Skeleton.tsx +114 -0
- package/src/atoms/Transition.tsx +189 -0
- package/src/atoms/index.ts +4 -0
- package/src/molecules/OptionCard.tsx +62 -25
- package/src/molecules/SegmentedControl.tsx +84 -0
- package/src/molecules/Stepper.tsx +147 -32
- package/src/molecules/Steps.tsx +27 -8
- package/src/molecules/index.ts +4 -4
- package/src/organisms/AlertProvider.tsx +26 -1
- package/src/organisms/BottomSheet.tsx +214 -62
- package/src/organisms/DateField.tsx +79 -19
- package/src/organisms/DatePicker.tsx +198 -80
- package/src/organisms/List.tsx +73 -41
- package/src/organisms/Modal.tsx +49 -61
- package/src/organisms/NavigationBar.tsx +226 -0
- package/src/organisms/Screen.tsx +11 -3
- package/src/organisms/SearchList.tsx +131 -35
- package/src/organisms/Select.tsx +223 -45
- package/src/organisms/ThemeToggle.tsx +30 -0
- package/src/organisms/Toast.tsx +8 -21
- package/src/organisms/ToastProvider.tsx +24 -0
- package/src/organisms/Wizard.tsx +37 -3
- package/src/organisms/imperativeBridge.ts +28 -0
- package/src/organisms/index.ts +7 -3
- package/src/theme/colors.ts +175 -0
- package/src/theme/createTheme.ts +38 -4
- package/src/theme/index.ts +3 -1
- package/src/theme/labels.ts +13 -0
- package/src/theme/palettes.ts +5 -2
- package/src/theme/types.ts +10 -0
- package/lib/commonjs/molecules/ThemeToggle.js.map +0 -1
- package/lib/module/molecules/ThemeToggle.js.map +0 -1
- package/lib/typescript/molecules/ThemeToggle.d.ts.map +0 -1
- package/src/molecules/ThemeToggle.tsx +0 -67
package/registry/index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"core": {
|
|
3
|
-
"bytes":
|
|
3
|
+
"bytes": 35592
|
|
4
4
|
},
|
|
5
5
|
"components": [
|
|
6
6
|
{
|
|
@@ -141,6 +141,31 @@
|
|
|
141
141
|
],
|
|
142
142
|
"bytes": 4626
|
|
143
143
|
},
|
|
144
|
+
{
|
|
145
|
+
"name": "Skeleton",
|
|
146
|
+
"slug": "skeleton",
|
|
147
|
+
"category": "atoms",
|
|
148
|
+
"exportedNames": [
|
|
149
|
+
"Skeleton"
|
|
150
|
+
],
|
|
151
|
+
"registryDependencies": [
|
|
152
|
+
"core"
|
|
153
|
+
],
|
|
154
|
+
"bytes": 3554
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"name": "Transition",
|
|
158
|
+
"slug": "transition",
|
|
159
|
+
"category": "atoms",
|
|
160
|
+
"exportedNames": [
|
|
161
|
+
"Transition",
|
|
162
|
+
"useReduceMotion"
|
|
163
|
+
],
|
|
164
|
+
"registryDependencies": [
|
|
165
|
+
"core"
|
|
166
|
+
],
|
|
167
|
+
"bytes": 6558
|
|
168
|
+
},
|
|
144
169
|
{
|
|
145
170
|
"name": "EmptyState",
|
|
146
171
|
"slug": "empty-state",
|
|
@@ -188,19 +213,20 @@
|
|
|
188
213
|
"registryDependencies": [
|
|
189
214
|
"core"
|
|
190
215
|
],
|
|
191
|
-
"bytes":
|
|
216
|
+
"bytes": 1709
|
|
192
217
|
},
|
|
193
218
|
{
|
|
194
219
|
"name": "Stepper",
|
|
195
220
|
"slug": "stepper",
|
|
196
221
|
"category": "molecules",
|
|
197
222
|
"exportedNames": [
|
|
198
|
-
"Stepper"
|
|
223
|
+
"Stepper",
|
|
224
|
+
"sanitizeNumeric"
|
|
199
225
|
],
|
|
200
226
|
"registryDependencies": [
|
|
201
227
|
"core"
|
|
202
228
|
],
|
|
203
|
-
"bytes":
|
|
229
|
+
"bytes": 6717
|
|
204
230
|
},
|
|
205
231
|
{
|
|
206
232
|
"name": "OptionCard",
|
|
@@ -210,9 +236,10 @@
|
|
|
210
236
|
"OptionCard"
|
|
211
237
|
],
|
|
212
238
|
"registryDependencies": [
|
|
213
|
-
"core"
|
|
239
|
+
"core",
|
|
240
|
+
"pressable-scale"
|
|
214
241
|
],
|
|
215
|
-
"bytes":
|
|
242
|
+
"bytes": 4156
|
|
216
243
|
},
|
|
217
244
|
{
|
|
218
245
|
"name": "InfoRow",
|
|
@@ -253,16 +280,16 @@
|
|
|
253
280
|
"bytes": 2487
|
|
254
281
|
},
|
|
255
282
|
{
|
|
256
|
-
"name": "
|
|
257
|
-
"slug": "
|
|
283
|
+
"name": "SegmentedControl",
|
|
284
|
+
"slug": "segmented-control",
|
|
258
285
|
"category": "molecules",
|
|
259
286
|
"exportedNames": [
|
|
260
|
-
"
|
|
287
|
+
"SegmentedControl"
|
|
261
288
|
],
|
|
262
289
|
"registryDependencies": [
|
|
263
290
|
"core"
|
|
264
291
|
],
|
|
265
|
-
"bytes":
|
|
292
|
+
"bytes": 2884
|
|
266
293
|
},
|
|
267
294
|
{
|
|
268
295
|
"name": "Steps",
|
|
@@ -274,7 +301,7 @@
|
|
|
274
301
|
"registryDependencies": [
|
|
275
302
|
"core"
|
|
276
303
|
],
|
|
277
|
-
"bytes":
|
|
304
|
+
"bytes": 8302
|
|
278
305
|
},
|
|
279
306
|
{
|
|
280
307
|
"name": "Modal",
|
|
@@ -287,9 +314,10 @@
|
|
|
287
314
|
"core",
|
|
288
315
|
"title",
|
|
289
316
|
"icon-button",
|
|
290
|
-
"pressable-scale"
|
|
317
|
+
"pressable-scale",
|
|
318
|
+
"transition"
|
|
291
319
|
],
|
|
292
|
-
"bytes":
|
|
320
|
+
"bytes": 8647
|
|
293
321
|
},
|
|
294
322
|
{
|
|
295
323
|
"name": "BottomSheet",
|
|
@@ -299,9 +327,13 @@
|
|
|
299
327
|
"BottomSheet"
|
|
300
328
|
],
|
|
301
329
|
"registryDependencies": [
|
|
302
|
-
"core"
|
|
330
|
+
"core",
|
|
331
|
+
"title",
|
|
332
|
+
"icon-button",
|
|
333
|
+
"pressable-scale",
|
|
334
|
+
"transition"
|
|
303
335
|
],
|
|
304
|
-
"bytes":
|
|
336
|
+
"bytes": 8662
|
|
305
337
|
},
|
|
306
338
|
{
|
|
307
339
|
"name": "Select",
|
|
@@ -313,7 +345,7 @@
|
|
|
313
345
|
"registryDependencies": [
|
|
314
346
|
"core"
|
|
315
347
|
],
|
|
316
|
-
"bytes":
|
|
348
|
+
"bytes": 12122
|
|
317
349
|
},
|
|
318
350
|
{
|
|
319
351
|
"name": "Alert",
|
|
@@ -334,14 +366,17 @@
|
|
|
334
366
|
"category": "organisms",
|
|
335
367
|
"exportedNames": [
|
|
336
368
|
"AlertProvider",
|
|
337
|
-
"useAlert"
|
|
369
|
+
"useAlert",
|
|
370
|
+
"showAlert",
|
|
371
|
+
"showConfirm",
|
|
372
|
+
"hideAlert"
|
|
338
373
|
],
|
|
339
374
|
"registryDependencies": [
|
|
340
375
|
"core",
|
|
341
376
|
"alert",
|
|
342
377
|
"button"
|
|
343
378
|
],
|
|
344
|
-
"bytes":
|
|
379
|
+
"bytes": 5470
|
|
345
380
|
},
|
|
346
381
|
{
|
|
347
382
|
"name": "Screen",
|
|
@@ -353,7 +388,7 @@
|
|
|
353
388
|
"registryDependencies": [
|
|
354
389
|
"core"
|
|
355
390
|
],
|
|
356
|
-
"bytes":
|
|
391
|
+
"bytes": 3321
|
|
357
392
|
},
|
|
358
393
|
{
|
|
359
394
|
"name": "List",
|
|
@@ -365,9 +400,10 @@
|
|
|
365
400
|
"registryDependencies": [
|
|
366
401
|
"core",
|
|
367
402
|
"empty-state",
|
|
403
|
+
"skeleton",
|
|
368
404
|
"spinner"
|
|
369
405
|
],
|
|
370
|
-
"bytes":
|
|
406
|
+
"bytes": 6039
|
|
371
407
|
},
|
|
372
408
|
{
|
|
373
409
|
"name": "SearchList",
|
|
@@ -380,11 +416,12 @@
|
|
|
380
416
|
"core",
|
|
381
417
|
"input",
|
|
382
418
|
"empty-state",
|
|
419
|
+
"skeleton",
|
|
383
420
|
"spinner",
|
|
384
421
|
"icon-button",
|
|
385
422
|
"pressable-scale"
|
|
386
423
|
],
|
|
387
|
-
"bytes":
|
|
424
|
+
"bytes": 9285
|
|
388
425
|
},
|
|
389
426
|
{
|
|
390
427
|
"name": "Toast",
|
|
@@ -394,9 +431,10 @@
|
|
|
394
431
|
"Toast"
|
|
395
432
|
],
|
|
396
433
|
"registryDependencies": [
|
|
397
|
-
"core"
|
|
434
|
+
"core",
|
|
435
|
+
"transition"
|
|
398
436
|
],
|
|
399
|
-
"bytes":
|
|
437
|
+
"bytes": 4402
|
|
400
438
|
},
|
|
401
439
|
{
|
|
402
440
|
"name": "ToastProvider",
|
|
@@ -404,13 +442,17 @@
|
|
|
404
442
|
"category": "organisms",
|
|
405
443
|
"exportedNames": [
|
|
406
444
|
"ToastProvider",
|
|
407
|
-
"useToast"
|
|
445
|
+
"useToast",
|
|
446
|
+
"showToast",
|
|
447
|
+
"hideToast",
|
|
448
|
+
"hideAllToasts"
|
|
408
449
|
],
|
|
409
450
|
"registryDependencies": [
|
|
410
451
|
"core",
|
|
411
|
-
"toast"
|
|
452
|
+
"toast",
|
|
453
|
+
"transition"
|
|
412
454
|
],
|
|
413
|
-
"bytes":
|
|
455
|
+
"bytes": 6368
|
|
414
456
|
},
|
|
415
457
|
{
|
|
416
458
|
"name": "DatePicker",
|
|
@@ -427,7 +469,7 @@
|
|
|
427
469
|
"icon-button",
|
|
428
470
|
"pressable-scale"
|
|
429
471
|
],
|
|
430
|
-
"bytes":
|
|
472
|
+
"bytes": 13400
|
|
431
473
|
},
|
|
432
474
|
{
|
|
433
475
|
"name": "DateField",
|
|
@@ -442,10 +484,11 @@
|
|
|
442
484
|
"title",
|
|
443
485
|
"icon-button",
|
|
444
486
|
"pressable-scale",
|
|
487
|
+
"transition",
|
|
445
488
|
"date-picker",
|
|
446
489
|
"button"
|
|
447
490
|
],
|
|
448
|
-
"bytes":
|
|
491
|
+
"bytes": 6402
|
|
449
492
|
},
|
|
450
493
|
{
|
|
451
494
|
"name": "Wizard",
|
|
@@ -457,9 +500,35 @@
|
|
|
457
500
|
"registryDependencies": [
|
|
458
501
|
"core",
|
|
459
502
|
"steps",
|
|
460
|
-
"button"
|
|
503
|
+
"button",
|
|
504
|
+
"transition"
|
|
505
|
+
],
|
|
506
|
+
"bytes": 5625
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
"name": "ThemeToggle",
|
|
510
|
+
"slug": "theme-toggle",
|
|
511
|
+
"category": "organisms",
|
|
512
|
+
"exportedNames": [
|
|
513
|
+
"ThemeToggle"
|
|
514
|
+
],
|
|
515
|
+
"registryDependencies": [
|
|
516
|
+
"core",
|
|
517
|
+
"segmented-control"
|
|
518
|
+
],
|
|
519
|
+
"bytes": 1189
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
"name": "NavigationBar",
|
|
523
|
+
"slug": "navigation-bar",
|
|
524
|
+
"category": "organisms",
|
|
525
|
+
"exportedNames": [
|
|
526
|
+
"NavigationBar"
|
|
527
|
+
],
|
|
528
|
+
"registryDependencies": [
|
|
529
|
+
"core"
|
|
461
530
|
],
|
|
462
|
-
"bytes":
|
|
531
|
+
"bytes": 7623
|
|
463
532
|
}
|
|
464
533
|
]
|
|
465
534
|
}
|
package/registry/list.json
CHANGED
|
@@ -9,12 +9,13 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
{
|
|
11
11
|
"path": "organisms/List.tsx",
|
|
12
|
-
"content": "import React, {
|
|
12
|
+
"content": "import React, { useCallback } from 'react';\nimport { FlatList, RefreshControl, View, type FlatListProps, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useColors, useLabels } from '../theme/UIProvider';\nimport { EmptyState } from '../atoms/EmptyState';\nimport { Skeleton } from '../atoms/Skeleton';\nimport { Spinner } from '../atoms/Spinner';\nimport type { IconName } from '../icons/types';\n\nexport interface ListProps<T> {\n data: T[];\n keyExtractor: (item: T, index: number) => string;\n renderItem: FlatListProps<T>['renderItem'];\n\n /** Carga inicial: mientras sea true y no haya datos, se muestran placeholders. */\n isLoading: boolean;\n /** Recarga con datos ya en pantalla (pull-to-refresh). @default false */\n isRefreshing?: boolean;\n isLoadingMore?: boolean;\n /** Controlado por el consumidor: mientras es false, se muestra un spinner de pantalla completa. @default true */\n isReady?: boolean;\n loadingText?: string;\n loadingMoreText?: string;\n\n /** Filas fantasma de la carga inicial. @default 6 */\n skeletonCount?: number;\n /** Placeholder propio, para que calce con la forma real del ítem. */\n renderSkeletonItem?: () => React.ReactElement;\n\n onEndReached?: () => void;\n onEndReachedThreshold?: number;\n onRefresh?: () => void;\n\n emptyTitle?: string;\n emptyDescription?: string;\n emptyIconName?: IconName;\n\n ListHeaderComponent?: FlatListProps<T>['ListHeaderComponent'];\n ListFooterComponent?: FlatListProps<T>['ListFooterComponent'];\n\n contentContainerStyle?: ViewStyle;\n containerStyle?: ViewStyle;\n\n /** Componente de lista a usar (FlashList, etc). @default FlatList */\n ListComponent?: React.ComponentType<any>;\n listProps?: Record<string, unknown>;\n}\n\nconst useStyles = createStyles((theme) => ({\n container: { flex: 1, backgroundColor: theme.colors.background },\n listContent: { padding: theme.tokens.spacing.md },\n footerLoader: { paddingVertical: theme.tokens.spacing.xl, alignItems: 'center' },\n skeletonRow: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: theme.tokens.spacing.md,\n padding: theme.tokens.spacing.md,\n },\n skeletonText: { flex: 1, gap: theme.tokens.spacing.sm },\n}));\n\n/**\n * List\n * Lista con estados de carga/paginación/vacío resueltos, sin atarse a\n * expo-router ni a FlashList: `ListComponent` permite inyectar cualquiera\n * (por defecto FlatList del propio React Native).\n */\nexport function List<T>({\n data,\n keyExtractor,\n renderItem,\n isLoading,\n isRefreshing = false,\n isLoadingMore = false,\n isReady = true,\n loadingText,\n loadingMoreText,\n skeletonCount = 6,\n renderSkeletonItem,\n onEndReached,\n onEndReachedThreshold = 0.3,\n onRefresh,\n emptyTitle,\n emptyDescription,\n emptyIconName,\n ListHeaderComponent,\n ListFooterComponent,\n contentContainerStyle,\n containerStyle,\n ListComponent = FlatList,\n listProps,\n}: ListProps<T>) {\n const colors = useColors();\n const styles = useStyles();\n const labels = useLabels();\n const resolvedLoadingText = loadingText ?? labels.loading;\n const resolvedLoadingMoreText = loadingMoreText ?? labels.loadingMore;\n\n const isInitialLoading = isLoading && data.length === 0;\n\n // El footer propio y el loader de paginación conviven: antes el primero\n // tapaba al segundo y la lista dejaba de avisar que estaba trayendo más.\n const renderFooter = useCallback(() => {\n const custom =\n typeof ListFooterComponent === 'function' ? (\n <ListFooterComponent />\n ) : (\n (ListFooterComponent as React.ReactElement | undefined) ?? null\n );\n\n return (\n <>\n {custom}\n {isLoadingMore && (\n <View style={styles.footerLoader}>\n <Spinner text={resolvedLoadingMoreText} fullscreen={false} />\n </View>\n )}\n </>\n );\n }, [isLoadingMore, resolvedLoadingMoreText, ListFooterComponent, styles.footerLoader]);\n\n // Vacío sólo cuando ya se sabe que no hay nada: durante la carga inicial se\n // muestran placeholders, no \"sin resultados\".\n const renderEmpty = useCallback(() => {\n if (isLoading || isRefreshing) return null;\n return <EmptyState title={emptyTitle} description={emptyDescription} iconName={emptyIconName} style={{ marginTop: 50 }} />;\n }, [isLoading, isRefreshing, emptyTitle, emptyDescription, emptyIconName]);\n\n if (!isReady) {\n return (\n <View style={styles.container}>\n <Spinner text={resolvedLoadingText} />\n </View>\n );\n }\n\n if (isInitialLoading) {\n return (\n <View style={[styles.container, containerStyle]} testID=\"list-skeleton\">\n <View style={[styles.listContent, contentContainerStyle]}>\n {Array.from({ length: skeletonCount }, (_, i) => (\n <React.Fragment key={i}>\n {renderSkeletonItem?.() ?? (\n <View style={styles.skeletonRow}>\n <Skeleton variant=\"circle\" width={40} />\n <View style={styles.skeletonText}>\n <Skeleton width=\"60%\" height={14} />\n <Skeleton width=\"35%\" height={12} />\n </View>\n </View>\n )}\n </React.Fragment>\n ))}\n </View>\n </View>\n );\n }\n\n return (\n <View style={[styles.container, containerStyle]}>\n <ListComponent\n data={data}\n keyExtractor={keyExtractor}\n renderItem={renderItem}\n contentContainerStyle={[styles.listContent, contentContainerStyle]}\n onEndReached={onEndReached}\n onEndReachedThreshold={onEndReachedThreshold}\n ListHeaderComponent={ListHeaderComponent}\n ListFooterComponent={renderFooter}\n ListEmptyComponent={renderEmpty}\n refreshControl={\n onRefresh ? (\n <RefreshControl\n refreshing={isRefreshing}\n onRefresh={onRefresh}\n colors={[colors.primary]}\n tintColor={colors.primary}\n />\n ) : undefined\n }\n {...listProps}\n />\n </View>\n );\n}\n"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"registryDependencies": [
|
|
16
16
|
"core",
|
|
17
17
|
"empty-state",
|
|
18
|
+
"skeleton",
|
|
18
19
|
"spinner"
|
|
19
20
|
]
|
|
20
21
|
}
|
package/registry/manifest.json
CHANGED
|
@@ -83,6 +83,22 @@
|
|
|
83
83
|
"file": "atoms/Spinner.tsx",
|
|
84
84
|
"slug": "spinner"
|
|
85
85
|
},
|
|
86
|
+
{
|
|
87
|
+
"name": "Skeleton",
|
|
88
|
+
"siblingNames": [],
|
|
89
|
+
"category": "atoms",
|
|
90
|
+
"file": "atoms/Skeleton.tsx",
|
|
91
|
+
"slug": "skeleton"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"name": "Transition",
|
|
95
|
+
"siblingNames": [
|
|
96
|
+
"useReduceMotion"
|
|
97
|
+
],
|
|
98
|
+
"category": "atoms",
|
|
99
|
+
"file": "atoms/Transition.tsx",
|
|
100
|
+
"slug": "transition"
|
|
101
|
+
},
|
|
86
102
|
{
|
|
87
103
|
"name": "EmptyState",
|
|
88
104
|
"siblingNames": [],
|
|
@@ -113,7 +129,9 @@
|
|
|
113
129
|
},
|
|
114
130
|
{
|
|
115
131
|
"name": "Stepper",
|
|
116
|
-
"siblingNames": [
|
|
132
|
+
"siblingNames": [
|
|
133
|
+
"sanitizeNumeric"
|
|
134
|
+
],
|
|
117
135
|
"category": "molecules",
|
|
118
136
|
"file": "molecules/Stepper.tsx",
|
|
119
137
|
"slug": "stepper"
|
|
@@ -147,11 +165,11 @@
|
|
|
147
165
|
"slug": "avatar-header"
|
|
148
166
|
},
|
|
149
167
|
{
|
|
150
|
-
"name": "
|
|
168
|
+
"name": "SegmentedControl",
|
|
151
169
|
"siblingNames": [],
|
|
152
170
|
"category": "molecules",
|
|
153
|
-
"file": "molecules/
|
|
154
|
-
"slug": "
|
|
171
|
+
"file": "molecules/SegmentedControl.tsx",
|
|
172
|
+
"slug": "segmented-control"
|
|
155
173
|
},
|
|
156
174
|
{
|
|
157
175
|
"name": "Steps",
|
|
@@ -191,7 +209,10 @@
|
|
|
191
209
|
{
|
|
192
210
|
"name": "AlertProvider",
|
|
193
211
|
"siblingNames": [
|
|
194
|
-
"useAlert"
|
|
212
|
+
"useAlert",
|
|
213
|
+
"showAlert",
|
|
214
|
+
"showConfirm",
|
|
215
|
+
"hideAlert"
|
|
195
216
|
],
|
|
196
217
|
"category": "organisms",
|
|
197
218
|
"file": "organisms/AlertProvider.tsx",
|
|
@@ -228,7 +249,10 @@
|
|
|
228
249
|
{
|
|
229
250
|
"name": "ToastProvider",
|
|
230
251
|
"siblingNames": [
|
|
231
|
-
"useToast"
|
|
252
|
+
"useToast",
|
|
253
|
+
"showToast",
|
|
254
|
+
"hideToast",
|
|
255
|
+
"hideAllToasts"
|
|
232
256
|
],
|
|
233
257
|
"category": "organisms",
|
|
234
258
|
"file": "organisms/ToastProvider.tsx",
|
|
@@ -258,6 +282,20 @@
|
|
|
258
282
|
"category": "organisms",
|
|
259
283
|
"file": "organisms/Wizard.tsx",
|
|
260
284
|
"slug": "wizard"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"name": "ThemeToggle",
|
|
288
|
+
"siblingNames": [],
|
|
289
|
+
"category": "organisms",
|
|
290
|
+
"file": "organisms/ThemeToggle.tsx",
|
|
291
|
+
"slug": "theme-toggle"
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
"name": "NavigationBar",
|
|
295
|
+
"siblingNames": [],
|
|
296
|
+
"category": "organisms",
|
|
297
|
+
"file": "organisms/NavigationBar.tsx",
|
|
298
|
+
"slug": "navigation-bar"
|
|
261
299
|
}
|
|
262
300
|
]
|
|
263
301
|
}
|
package/registry/modal.json
CHANGED
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
{
|
|
11
11
|
"path": "organisms/Modal.tsx",
|
|
12
|
-
"content": "import React, { memo, useEffect,
|
|
12
|
+
"content": "import React, { memo, useEffect, useState } from 'react';\nimport {\n Dimensions,\n KeyboardAvoidingView,\n Modal as RNModal,\n Platform,\n Pressable,\n ScrollView,\n View,\n type ModalProps as RNModalProps,\n type ViewStyle,\n} from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useColors, useInsets } from '../theme/UIProvider';\nimport { Title } from '../atoms/Text';\nimport { IconButton } from '../atoms/IconButton';\nimport { Transition, type TransitionPreset } from '../atoms/Transition';\n\nconst { height: SCREEN_HEIGHT } = Dimensions.get('window');\n\nexport type ModalVariant = 'full' | 'overlay' | 'fullScreen';\n\nexport interface ModalProps\n extends Pick<RNModalProps, 'statusBarTranslucent' | 'onRequestClose' | 'hardwareAccelerated'> {\n visible: boolean;\n onClose?: () => void;\n title?: string;\n closeAccessibilityLabel?: string;\n children: React.ReactNode;\n footer?: React.ReactNode;\n containerStyle?: ViewStyle;\n contentStyle?: ViewStyle;\n variant?: ModalVariant;\n scrollable?: boolean;\n}\n\nconst useStyles = createStyles((theme) => ({\n flex1: { flex: 1 },\n fullWrapper: { flex: 1, backgroundColor: theme.colors.background },\n overlayContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: theme.tokens.spacing.xxl - 4 },\n backdrop: { backgroundColor: theme.colors.overlay },\n overlayCard: {\n width: '100%',\n maxHeight: '85%',\n backgroundColor: theme.colors.surface,\n borderRadius: theme.tokens.radius.xxl,\n overflow: 'hidden',\n ...theme.tokens.shadow.lg,\n },\n header: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n padding: theme.tokens.spacing.xl,\n borderBottomWidth: 1,\n borderBottomColor: theme.colors.border,\n backgroundColor: theme.colors.surface,\n },\n title: { fontSize: theme.tokens.fontSize.xl, fontWeight: theme.tokens.fontWeight.bold, flex: 1 },\n closeButton: { marginLeft: theme.tokens.spacing.md },\n contentWrapper: { flexShrink: 1, backgroundColor: theme.colors.surface },\n scrollWrapper: { flexShrink: 1 },\n scrollContent: { padding: theme.tokens.spacing.xl, flexGrow: 1 },\n scrollContentFull: { padding: 0, flexGrow: 1 },\n footer: {\n padding: theme.tokens.spacing.xl,\n borderTopWidth: 1,\n borderTopColor: theme.colors.border,\n backgroundColor: theme.colors.surface,\n },\n}));\n\n/**\n * Modal\n * Un solo componente para todos los modales de la app: maneja scroll,\n * teclado, insets y layout según `variant`. Para una hoja desde abajo está\n * BottomSheet, que además trae arrastre para cerrar.\n */\nexport const Modal = memo(\n ({\n visible,\n onClose,\n title,\n closeAccessibilityLabel = 'Close',\n children,\n footer,\n containerStyle,\n contentStyle,\n variant = 'full',\n scrollable = true,\n statusBarTranslucent = true,\n onRequestClose,\n hardwareAccelerated,\n }: ModalProps) => {\n const isFull = variant === 'full';\n const isOverlay = variant === 'overlay';\n const isFullScreen = variant === 'fullScreen';\n const isAnimatedVariant = isOverlay || isFull;\n const insets = useInsets();\n const colors = useColors();\n const styles = useStyles();\n\n const [mounted, setMounted] = useState(!isAnimatedVariant && visible);\n\n useEffect(() => {\n if (!isAnimatedVariant) setMounted(visible);\n else if (visible) setMounted(true);\n }, [visible, isAnimatedVariant]);\n\n // La tarjeta nunca anima opacity: hacerlo dispara un bug de Android donde\n // los hijos con elevation parpadean. El fondo semitransparente sí.\n const cardPreset: TransitionPreset[] = isFull ? ['slide-up'] : ['slide-down', 'scale'];\n const cardDistance = isFull ? SCREEN_HEIGHT : 40;\n\n const behavior = Platform.OS === 'ios' ? (isFull ? undefined : 'padding') : 'height';\n const keyboardOffset = Platform.OS === 'ios' ? 0 : 20;\n\n const renderHeader = () => {\n if (!title && !onClose) return null;\n return (\n <View style={styles.header}>\n {!!title && (\n <Title style={styles.title} numberOfLines={1}>\n {title}\n </Title>\n )}\n {!title && <View style={styles.flex1} />}\n {!!onClose && (\n <IconButton iconName=\"close\" size={24} color={colors.text} onPress={onClose} accessibilityLabel={closeAccessibilityLabel} style={styles.closeButton} />\n )}\n </View>\n );\n };\n\n const renderMainContent = () => (\n <View style={[styles.contentWrapper, isFull && styles.flex1]}>\n {scrollable ? (\n <ScrollView\n style={[styles.scrollWrapper, isFull && styles.flex1]}\n contentContainerStyle={[!isFull ? styles.scrollContent : styles.scrollContentFull, contentStyle]}\n keyboardShouldPersistTaps=\"handled\"\n showsVerticalScrollIndicator={false}\n bounces={Platform.OS === 'ios'}\n automaticallyAdjustKeyboardInsets\n >\n {children}\n </ScrollView>\n ) : (\n <View style={[!isFull ? styles.scrollContent : styles.scrollContentFull, isFull && styles.flex1, contentStyle]}>\n {children}\n </View>\n )}\n </View>\n );\n\n const renderFooter = () => (footer ? <View style={styles.footer}>{footer}</View> : null);\n\n if (!mounted) return null;\n\n return (\n <RNModal\n visible\n onRequestClose={onRequestClose ?? onClose}\n animationType={isAnimatedVariant ? 'none' : 'slide'}\n // `full` anima su propia entrada (translateY) con animationType=\"none\"\n // y necesita `transparent` para que no se vea un fondo negro detrás\n // mientras desliza. RN sólo admite `transparent` junto a\n // 'overFullScreen' — con 'pageSheet' avisa que la combinación no está\n // soportada. Por eso `full` usa overFullScreen: la presentación nativa\n // de pageSheet sería redundante con nuestra animación de todos modos.\n presentationStyle={\n Platform.OS === 'ios' ? (isFullScreen ? 'fullScreen' : 'overFullScreen') : undefined\n }\n transparent={isOverlay || isFull}\n statusBarTranslucent={statusBarTranslucent}\n hardwareAccelerated={hardwareAccelerated}\n >\n <KeyboardAvoidingView behavior={behavior} keyboardVerticalOffset={keyboardOffset} style={styles.flex1}>\n {isOverlay ? (\n <View style={styles.overlayContainer}>\n {/* Sin accessibilityLabel propio: el header ya expone un botón \"Close\"\n accesible; duplicar el label aquí generaría dos elementos con el\n mismo nombre para lectores de pantalla. Tocar afuera es un atajo\n puramente táctil. */}\n <Transition\n visible={visible}\n preset=\"fade\"\n duration={200}\n keepMounted\n style={[{ ...StyleSheetAbsoluteFill }, styles.backdrop]}\n >\n <Pressable style={StyleSheetAbsoluteFill} onPress={onClose} testID=\"modal-backdrop\" />\n </Transition>\n\n <Transition\n visible={visible}\n preset={cardPreset}\n distance={cardDistance}\n duration={250}\n onExited={() => setMounted(false)}\n style={[styles.overlayCard, containerStyle]}\n >\n {renderHeader()}\n {renderMainContent()}\n {renderFooter()}\n </Transition>\n </View>\n ) : isFull ? (\n <Transition\n visible={visible}\n preset={cardPreset}\n distance={cardDistance}\n duration={250}\n onExited={() => setMounted(false)}\n style={[styles.fullWrapper, containerStyle]}\n >\n <View style={[styles.flex1, { paddingTop: insets.top, paddingBottom: insets.bottom }]}>\n {renderHeader()}\n {renderMainContent()}\n {renderFooter()}\n </View>\n </Transition>\n ) : (\n <View style={[styles.fullWrapper, containerStyle]}>\n <View style={[styles.flex1, { paddingTop: insets.top, paddingBottom: insets.bottom }]}>\n {renderHeader()}\n {renderMainContent()}\n {renderFooter()}\n </View>\n </View>\n )}\n </KeyboardAvoidingView>\n </RNModal>\n );\n }\n);\nModal.displayName = 'Modal';\n\nconst StyleSheetAbsoluteFill: ViewStyle = { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 };\n"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"registryDependencies": [
|
|
16
16
|
"core",
|
|
17
17
|
"title",
|
|
18
18
|
"icon-button",
|
|
19
|
-
"pressable-scale"
|
|
19
|
+
"pressable-scale",
|
|
20
|
+
"transition"
|
|
20
21
|
]
|
|
21
22
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "NavigationBar",
|
|
3
|
+
"slug": "navigation-bar",
|
|
4
|
+
"type": "component",
|
|
5
|
+
"category": "organisms",
|
|
6
|
+
"exportedNames": [
|
|
7
|
+
"NavigationBar"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "organisms/NavigationBar.tsx",
|
|
12
|
+
"content": "import React, { memo, useEffect, useRef, useState } from 'react';\nimport {\n Animated,\n StyleSheet,\n Text,\n TouchableOpacity,\n View,\n type LayoutChangeEvent,\n type StyleProp,\n type ViewStyle,\n} from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors, useInsets, useTheme } from '../theme/UIProvider';\nimport { Icon } from '../icons/Icon';\nimport type { IconName } from '../icons/types';\n\nexport interface NavigationBarItem {\n key: string;\n label: string;\n iconName?: IconName;\n /** Contador sobre el ícono. `true` pinta sólo el punto. */\n badge?: number | string | true;\n}\n\nexport interface NavigationBarProps {\n items: NavigationBarItem[];\n activeKey: string;\n onChange: (key: string) => void;\n /** Borde de la pantalla donde vive la barra. @default 'bottom' */\n position?: 'bottom' | 'top';\n /** @default true */\n showLabels?: boolean;\n /** Suma el inset de safe area del borde donde vive la barra. @default true */\n safeArea?: boolean;\n style?: StyleProp<ViewStyle>;\n testID?: string;\n}\n\nconst useStyles = createStyles((theme) => ({\n bar: {\n flexDirection: 'row',\n backgroundColor: theme.colors.surface,\n borderColor: theme.colors.border,\n paddingHorizontal: theme.tokens.spacing.xs,\n paddingVertical: theme.tokens.spacing.sm,\n },\n barBottom: { borderTopWidth: StyleSheet.hairlineWidth },\n barTop: { borderBottomWidth: StyleSheet.hairlineWidth },\n indicator: {\n position: 'absolute',\n top: theme.tokens.spacing.xs,\n bottom: theme.tokens.spacing.xs,\n left: theme.tokens.spacing.sm,\n borderRadius: theme.tokens.radius.lg,\n backgroundColor: theme.colors.primarySoft,\n },\n item: { flex: 1, paddingVertical: theme.tokens.spacing.xs },\n itemContent: { alignItems: 'center', justifyContent: 'center', gap: 2 },\n label: { fontSize: theme.tokens.fontSize.xs, fontWeight: theme.tokens.fontWeight.medium },\n badge: {\n position: 'absolute',\n top: -4,\n left: '50%',\n marginLeft: 4,\n minWidth: 16,\n height: 16,\n paddingHorizontal: 4,\n borderRadius: theme.tokens.radius.full,\n backgroundColor: theme.colors.error,\n alignItems: 'center',\n justifyContent: 'center',\n },\n badgeDot: { minWidth: 10, height: 10, top: -2, paddingHorizontal: 0 },\n badgeText: {\n fontSize: 10,\n fontWeight: theme.tokens.fontWeight.bold,\n color: theme.colors.onError,\n },\n}));\n\nconst ICON_SIZE = 22;\n\n/**\n * NavigationBar\n * Barra de navegación por pestañas con la misma apariencia y las mismas\n * transiciones en iOS y Android: el indicador se desliza hasta la pestaña\n * activa y su contenido crece y toma el color de acento con un cross-fade.\n * No navega por su cuenta — reporta la pestaña tocada y el consumidor decide.\n */\nexport const NavigationBar = memo(\n ({\n items,\n activeKey,\n onChange,\n position = 'bottom',\n showLabels = true,\n safeArea = true,\n style,\n testID,\n }: NavigationBarProps) => {\n const theme = useTheme();\n const colors = useColors();\n const styles = useStyles();\n const insets = useInsets();\n const allowFontScaling = useAllowFontScaling();\n\n const [barWidth, setBarWidth] = useState(0);\n const slide = useRef(new Animated.Value(0)).current;\n const placed = useRef(false);\n\n const activeIndex = Math.max(\n 0,\n items.findIndex((item) => item.key === activeKey)\n );\n const itemWidth = items.length > 0 ? barWidth / items.length : 0;\n\n useEffect(() => {\n if (itemWidth <= 0) return;\n const to = activeIndex * itemWidth;\n // En el primer layout el indicador aparece en su lugar; animarlo desde 0\n // sería un deslizamiento que el usuario no pidió.\n if (!placed.current) {\n placed.current = true;\n slide.setValue(to);\n return;\n }\n Animated.spring(slide, { toValue: to, useNativeDriver: true, friction: 9, tension: 90 }).start();\n }, [activeIndex, itemWidth, slide]);\n\n const onLayout = (e: LayoutChangeEvent) => {\n const next = e.nativeEvent.layout.width - theme.tokens.spacing.xs * 2;\n if (next !== barWidth) setBarWidth(next);\n };\n\n const renderContent = (item: NavigationBarItem, color: string) => (\n <>\n {!!item.iconName && <Icon name={item.iconName} size={ICON_SIZE} color={color} />}\n {showLabels && (\n <Text allowFontScaling={allowFontScaling} numberOfLines={1} style={[styles.label, { color }]}>\n {item.label}\n </Text>\n )}\n </>\n );\n\n return (\n <View\n testID={testID}\n onLayout={onLayout}\n accessibilityRole=\"tablist\"\n style={[\n styles.bar,\n position === 'bottom' ? styles.barBottom : styles.barTop,\n safeArea && position === 'bottom' && { paddingBottom: theme.tokens.spacing.sm + insets.bottom },\n safeArea && position === 'top' && { paddingTop: theme.tokens.spacing.sm + insets.top },\n style,\n ]}\n >\n {itemWidth > 0 && (\n <Animated.View\n testID=\"navigation-bar-indicator\"\n pointerEvents=\"none\"\n style={[\n styles.indicator,\n safeArea && position === 'bottom' && { bottom: theme.tokens.spacing.xs + insets.bottom },\n safeArea && position === 'top' && { top: theme.tokens.spacing.xs + insets.top },\n {\n width: itemWidth - theme.tokens.spacing.sm * 2,\n transform: [{ translateX: slide }],\n },\n ]}\n />\n )}\n\n {items.map((item, index) => {\n const active = index === activeIndex;\n const range = [(index - 1) * itemWidth, index * itemWidth, (index + 1) * itemWidth];\n const scale =\n itemWidth > 0\n ? slide.interpolate({ inputRange: range, outputRange: [1, 1.08, 1], extrapolate: 'clamp' })\n : 1;\n const activeOpacity =\n itemWidth > 0\n ? slide.interpolate({ inputRange: range, outputRange: [0, 1, 0], extrapolate: 'clamp' })\n : 0;\n\n return (\n <TouchableOpacity\n key={item.key}\n style={styles.item}\n activeOpacity={0.7}\n onPress={() => onChange(item.key)}\n accessibilityRole=\"tab\"\n accessibilityLabel={item.label}\n accessibilityState={{ selected: active }}\n >\n <Animated.View style={[styles.itemContent, { transform: [{ scale }] }]}>\n {renderContent(item, colors.textLight)}\n {/* Copia decorativa: sin ocultarla, el lector lee el label dos veces. */}\n <Animated.View\n pointerEvents=\"none\"\n accessibilityElementsHidden\n importantForAccessibility=\"no-hide-descendants\"\n style={[StyleSheet.absoluteFill, styles.itemContent, { opacity: activeOpacity }]}\n >\n {renderContent(item, colors.primaryText)}\n </Animated.View>\n\n {item.badge !== undefined && (\n <View style={[styles.badge, item.badge === true && styles.badgeDot]}>\n {item.badge !== true && (\n <Text allowFontScaling={false} style={styles.badgeText}>\n {item.badge}\n </Text>\n )}\n </View>\n )}\n </Animated.View>\n </TouchableOpacity>\n );\n })}\n </View>\n );\n }\n);\nNavigationBar.displayName = 'NavigationBar';\n"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"registryDependencies": [
|
|
16
|
+
"core"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
{
|
|
11
11
|
"path": "molecules/OptionCard.tsx",
|
|
12
|
-
"content": "import React, { memo } from 'react';\nimport { Text,
|
|
12
|
+
"content": "import React, { memo } from 'react';\nimport { Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors } from '../theme/UIProvider';\nimport { Icon } from '../icons/Icon';\nimport { PressableScale } from '../atoms/PressableScale';\nimport type { IconName } from '../icons/types';\n\nexport interface OptionCardProps {\n label: string;\n /** Línea secundaria: el detalle que decide la elección (\"llega en 3 días\"). */\n description?: string;\n iconName?: IconName;\n isSelected: boolean;\n onPress: () => void;\n disabled?: boolean;\n /** 'vertical': ícono arriba, texto abajo centrado. 'horizontal': en fila. @default 'horizontal' */\n layout?: 'vertical' | 'horizontal';\n style?: StyleProp<ViewStyle>;\n testID?: string;\n}\n\nconst useStyles = createStyles((theme) => ({\n card: {\n // Las tarjetas se reparten la fila que las contenga, sean dos o cinco.\n flex: 1,\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: theme.colors.surface,\n borderRadius: theme.tokens.radius.md,\n borderWidth: 1,\n borderColor: theme.colors.border,\n ...theme.tokens.shadow.sm,\n },\n cardHorizontal: { flexDirection: 'row', padding: theme.tokens.spacing.md - 2, gap: theme.tokens.spacing.sm },\n cardVertical: { padding: theme.tokens.spacing.md - 2, gap: 6, minHeight: 100 },\n cardSelected: {\n borderColor: theme.colors.primary,\n backgroundColor: theme.colors.primarySoft,\n borderWidth: 1.5,\n elevation: 0,\n shadowOpacity: 0,\n },\n cardDisabled: { opacity: 0.5 },\n text: { flexShrink: 1 },\n textVertical: { alignItems: 'center' },\n label: { color: theme.colors.textLight, fontWeight: theme.tokens.fontWeight.medium },\n labelVertical: { fontSize: theme.tokens.fontSize.xs, textAlign: 'center' },\n labelSelected: { color: theme.colors.primaryText },\n description: { fontSize: theme.tokens.fontSize.xs, color: theme.colors.textLight, marginTop: 2 },\n descriptionVertical: { textAlign: 'center' },\n}));\n\n/**\n * OptionCard\n * Opción excluyente con ícono y label que resalta cuando `isSelected`. Es un\n * radio con cara de tarjeta, así que se anuncia como tal: envolver el grupo en\n * una View con `accessibilityRole=\"radiogroup\"` para que el lector de pantalla\n * lea \"1 de 3\".\n */\nexport const OptionCard = memo(\n ({\n label,\n description,\n iconName,\n isSelected,\n onPress,\n disabled = false,\n layout = 'horizontal',\n style,\n testID,\n }: OptionCardProps) => {\n const colors = useColors();\n const styles = useStyles();\n const allowFontScaling = useAllowFontScaling();\n const isVertical = layout === 'vertical';\n\n // Feedback por transform y no por opacity: con elevation, animar la opacity\n // dispara un bug de Android que redibuja la sombra como un borde oscuro.\n return (\n <PressableScale\n style={[\n styles.card,\n isVertical ? styles.cardVertical : styles.cardHorizontal,\n isSelected && styles.cardSelected,\n disabled && styles.cardDisabled,\n style,\n ]}\n onPress={onPress}\n disabled={disabled}\n accessibilityRole=\"radio\"\n accessibilityState={{ selected: isSelected, checked: isSelected, disabled }}\n accessibilityLabel={label}\n accessibilityHint={description}\n testID={testID}\n >\n {!!iconName && <Icon name={iconName} size={24} color={isSelected ? colors.primary : colors.textLight} />}\n\n <View style={[styles.text, isVertical && styles.textVertical]}>\n <Text\n allowFontScaling={allowFontScaling}\n style={[styles.label, isVertical && styles.labelVertical, isSelected && styles.labelSelected]}\n >\n {label}\n </Text>\n {!!description && (\n <Text\n allowFontScaling={allowFontScaling}\n style={[styles.description, isVertical && styles.descriptionVertical]}\n >\n {description}\n </Text>\n )}\n </View>\n </PressableScale>\n );\n }\n);\nOptionCard.displayName = 'OptionCard';\n"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"registryDependencies": [
|
|
16
|
-
"core"
|
|
16
|
+
"core",
|
|
17
|
+
"pressable-scale"
|
|
17
18
|
]
|
|
18
19
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
{
|
|
11
11
|
"path": "atoms/PressableScale.tsx",
|
|
12
|
-
"content": "import React, { memo, useRef } from 'react';\nimport {
|
|
12
|
+
"content": "import React, { memo, useRef } from 'react';\nimport {\n Animated,\n Pressable,\n type GestureResponderEvent,\n type PressableProps,\n type StyleProp,\n type ViewStyle,\n} from 'react-native';\n\nexport interface PressableScaleProps extends Omit<PressableProps, 'style'> {\n /** Escala objetivo al presionar. @default 0.96 */\n scaleTo?: number;\n style?: StyleProp<ViewStyle>;\n children: React.ReactNode;\n}\n\nconst AnimatedPressable = Animated.createAnimatedComponent(Pressable);\n\n/**\n * PressableScale\n * Feedback táctil de \"achicarse\" al presionar, con Animated.spring de RN\n * (useNativeDriver: true — corre en el hilo nativo, sin coste de JS por frame).\n * No depende de reanimated: la librería entera es cero-dependencias.\n */\nexport const PressableScale = memo(\n ({ scaleTo = 0.96, style, children, onPressIn, onPressOut, disabled, ...props }: PressableScaleProps) => {\n const scale = useRef(new Animated.Value(1)).current;\n\n const animateTo = (value: number) => {\n Animated.spring(scale, {\n toValue: value,\n useNativeDriver: true,\n speed: 50,\n bounciness: 6,\n }).start();\n };\n\n const handlePressIn = (e: GestureResponderEvent) => {\n if (!disabled) animateTo(scaleTo);\n onPressIn?.(e);\n };\n\n const handlePressOut = (e: GestureResponderEvent) => {\n if (!disabled) animateTo(1);\n onPressOut?.(e);\n };\n\n return (\n <AnimatedPressable\n {...props}\n disabled={disabled}\n onPressIn={handlePressIn}\n onPressOut={handlePressOut}\n style={[style, { transform: [{ scale }] }]}\n >\n {children}\n </AnimatedPressable>\n );\n }\n);\nPressableScale.displayName = 'PressableScale';\n"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"registryDependencies": [
|
package/registry/screen.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
{
|
|
11
11
|
"path": "organisms/Screen.tsx",
|
|
12
|
-
"content": "import React, { memo } from 'react';\nimport { KeyboardAvoidingView, Platform, ScrollView, View, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useInsets } from '../theme/UIProvider';\n\nexport type ScreenEdge = 'top' | 'bottom';\n\nexport interface ScreenProps {\n children: React.ReactNode;\n style?: ViewStyle;\n scrollable?: boolean;\n contentContainerStyle?: ViewStyle;\n /** Qué insets de safe area aplicar. @default ['top', 'bottom'] */\n edges?: ScreenEdge[];\n /**\n * Compensa el teclado con un KeyboardAvoidingView. Ponelo en false cuando\n * el contenido ya trae su propio scroller que maneja el teclado (Wizard,\n * List, SearchList): las dos compensaciones se suman y el campo enfocado\n * termina empujado fuera de la pantalla.\n * @default true\n */\n keyboardAvoiding?: boolean;\n}\n\nconst useStyles = createStyles((theme) => ({\n wrapper: { flex: 1, backgroundColor: theme.colors.background },\n container: { flex: 1 },\n contentContainer: { flexGrow: 1 },\n}));\n\n/**\n * Screen\n * Contenedor raíz de pantalla: fondo del theme, safe area (via insets del\n * UIProvider), teclado y scroll opcional resueltos de forma consistente.\n */\nexport const Screen = memo(({\n children,\n style,\n scrollable = true,\n contentContainerStyle,\n edges = ['top', 'bottom'],\n keyboardAvoiding = true,\n}: ScreenProps) => {\n const insets = useInsets();\n const styles = useStyles();\n const applyTop = edges.includes('top');\n const applyBottom = edges.includes('bottom');\n\n const KeyboardWrapper: React.ComponentType<any> =
|
|
12
|
+
"content": "import React, { memo } from 'react';\nimport { KeyboardAvoidingView, Platform, ScrollView, View, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useInsets } from '../theme/UIProvider';\n\nexport type ScreenEdge = 'top' | 'bottom';\n\nexport interface ScreenProps {\n children: React.ReactNode;\n style?: ViewStyle;\n scrollable?: boolean;\n contentContainerStyle?: ViewStyle;\n /** Qué insets de safe area aplicar. @default ['top', 'bottom'] */\n edges?: ScreenEdge[];\n /**\n * Compensa el teclado: en iOS con scroll lo hace el propio ScrollView, en el\n * resto de los casos con un KeyboardAvoidingView. Ponelo en false cuando\n * el contenido ya trae su propio scroller que maneja el teclado (Wizard,\n * List, SearchList): las dos compensaciones se suman y el campo enfocado\n * termina empujado fuera de la pantalla.\n * @default true\n */\n keyboardAvoiding?: boolean;\n}\n\nconst useStyles = createStyles((theme) => ({\n wrapper: { flex: 1, backgroundColor: theme.colors.background },\n container: { flex: 1 },\n contentContainer: { flexGrow: 1 },\n}));\n\n/**\n * Screen\n * Contenedor raíz de pantalla: fondo del theme, safe area (via insets del\n * UIProvider), teclado y scroll opcional resueltos de forma consistente.\n */\nexport const Screen = memo(({\n children,\n style,\n scrollable = true,\n contentContainerStyle,\n edges = ['top', 'bottom'],\n keyboardAvoiding = true,\n}: ScreenProps) => {\n const insets = useInsets();\n const styles = useStyles();\n const applyTop = edges.includes('top');\n const applyBottom = edges.includes('bottom');\n\n // En iOS el ScrollView de abajo ya sube el campo enfocado con\n // automaticallyAdjustKeyboardInsets; envolverlo además en un\n // KeyboardAvoidingView compensa el teclado dos veces y el input termina\n // empujado fuera del borde superior. Android no tiene ese prop, así que ahí\n // el KeyboardAvoidingView sigue siendo el único mecanismo.\n const avoidWithView = keyboardAvoiding && !(scrollable && Platform.OS === 'ios');\n\n const KeyboardWrapper: React.ComponentType<any> = avoidWithView ? KeyboardAvoidingView : View;\n const keyboardProps = avoidWithView\n ? {\n behavior: Platform.OS === 'ios' ? ('padding' as const) : ('height' as const),\n keyboardVerticalOffset: Platform.OS === 'ios' ? 0 : 20,\n }\n : {};\n\n return (\n <View style={[styles.wrapper, style]}>\n <KeyboardWrapper style={styles.container} {...keyboardProps}>\n {scrollable ? (\n <ScrollView\n style={styles.container}\n contentInsetAdjustmentBehavior=\"automatic\"\n automaticallyAdjustKeyboardInsets\n contentContainerStyle={[\n styles.contentContainer,\n { paddingTop: applyTop ? insets.top : 0 },\n Platform.OS === 'android' && applyBottom && { paddingBottom: insets.bottom },\n contentContainerStyle,\n ]}\n keyboardShouldPersistTaps=\"handled\"\n >\n {children}\n </ScrollView>\n ) : (\n <View style={[styles.container, { paddingTop: applyTop ? insets.top : 0, paddingBottom: applyBottom ? insets.bottom : 0 }]}>\n {children}\n </View>\n )}\n </KeyboardWrapper>\n </View>\n );\n});\nScreen.displayName = 'Screen';\n"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"registryDependencies": [
|