@sincpro/mobile-ui 1.1.0 → 2.0.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 +156 -29
- package/assets/brand-mesh.png +0 -0
- package/assets/fonts/FFL.txt +57 -0
- package/assets/fonts/Satoshi-Black.otf +0 -0
- package/assets/fonts/Satoshi-Bold.otf +0 -0
- package/assets/fonts/Satoshi-Medium.otf +0 -0
- package/assets/fonts/Satoshi-Regular.otf +0 -0
- package/dist/Actions/Actions.Button.d.ts +10 -12
- package/dist/Actions/Actions.Button.js +9 -11
- package/dist/Actions/Actions.Dock.d.ts +20 -0
- package/dist/Actions/Actions.Dock.js +89 -0
- package/dist/Actions/Actions.FAB.d.ts +1 -2
- package/dist/Actions/Actions.FAB.js +5 -7
- package/dist/Actions/Actions.IconButton.d.ts +1 -2
- package/dist/Actions/Actions.IconButton.js +2 -2
- package/dist/Actions/index.d.ts +4 -0
- package/dist/Actions/index.js +3 -0
- package/dist/Dialog/BottomSheet/BottomSheet.Root.js +22 -34
- package/dist/Dialog/Dialog.ActionSheet.js +2 -16
- package/dist/Dialog/Dialog.Confirmation.js +7 -5
- package/dist/Dialog/Dialog.EditValue.js +3 -1
- package/dist/Dialog/Dialog.Sheet.d.ts +3 -3
- package/dist/Dialog/Dialog.Sheet.js +18 -25
- package/dist/Display/Display.Accordion.js +7 -3
- package/dist/Display/Display.AmountSummary.d.ts +28 -0
- package/dist/Display/Display.AmountSummary.js +25 -0
- package/dist/Display/Display.Carousel.d.ts +27 -4
- package/dist/Display/Display.Carousel.js +112 -12
- package/dist/Display/Display.ExpoImage.d.ts +8 -0
- package/dist/Display/Display.ExpoImage.js +15 -0
- package/dist/Display/Display.Image.d.ts +10 -6
- package/dist/Display/Display.Image.js +39 -6
- package/dist/Display/Display.ListItem.d.ts +1 -2
- package/dist/Display/Display.ListItem.js +3 -3
- package/dist/Display/Display.Logo.d.ts +2 -2
- package/dist/Display/Display.Logo.js +3 -2
- package/dist/Display/Display.MediaCard.d.ts +2 -2
- package/dist/Display/Display.MediaEmbed.d.ts +2 -2
- package/dist/Display/Display.ProductCard.d.ts +2 -2
- package/dist/Display/Display.Story.d.ts +3 -3
- package/dist/Display/Display.Story.js +5 -4
- package/dist/Display/Display.StoryTray.d.ts +4 -4
- package/dist/Display/Display.StoryTray.js +3 -2
- package/dist/Display/Display.StoryViewer.d.ts +3 -3
- package/dist/Display/Display.StoryViewer.js +6 -3
- package/dist/Display/index.d.ts +9 -0
- package/dist/Display/index.js +4 -0
- package/dist/Feedback/Feedback.DomainSplashScreen.d.ts +13 -4
- package/dist/Feedback/Feedback.DomainSplashScreen.js +68 -53
- package/dist/Feedback/Feedback.EmptyState.d.ts +3 -4
- package/dist/Feedback/Feedback.EmptyState.js +4 -3
- package/dist/Feedback/Feedback.Error.d.ts +1 -2
- package/dist/Feedback/Feedback.Error.js +2 -2
- package/dist/Feedback/Feedback.Loading.d.ts +6 -2
- package/dist/Feedback/Feedback.Loading.js +2 -2
- package/dist/Feedback/Feedback.Progress.d.ts +6 -3
- package/dist/Feedback/Feedback.Progress.js +29 -19
- package/dist/Feedback/Feedback.ProgressCircle.d.ts +3 -2
- package/dist/Feedback/Feedback.ProgressCircle.js +10 -7
- package/dist/Feedback/Feedback.Skeleton.d.ts +4 -4
- package/dist/Feedback/Feedback.Skeleton.js +15 -3
- package/dist/Feedback/Feedback.Spinner.d.ts +3 -3
- package/dist/Feedback/Feedback.Spinner.js +40 -9
- package/dist/Feedback/Feedback.Splash.d.ts +2 -2
- package/dist/Feedback/Feedback.Splash.js +5 -3
- package/dist/Feedback/Toast.context.d.ts +64 -3
- package/dist/Feedback/Toast.context.js +112 -31
- package/dist/Feedback/index.d.ts +15 -1
- package/dist/Feedback/index.js +4 -1
- package/dist/Form/Form.Checkbox.d.ts +1 -2
- package/dist/Form/Form.Checkbox.js +2 -2
- package/dist/Form/Form.DatePicker.d.ts +1 -2
- package/dist/Form/Form.DatePicker.js +2 -2
- package/dist/Form/Form.Dropdown.d.ts +15 -3
- package/dist/Form/Form.Dropdown.js +21 -6
- package/dist/Form/Form.ErrorSummary.d.ts +23 -0
- package/dist/Form/Form.ErrorSummary.js +53 -0
- package/dist/Form/Form.Input.d.ts +1 -2
- package/dist/Form/Form.Input.js +2 -2
- package/dist/Form/Form.Select.d.ts +1 -2
- package/dist/Form/Form.Select.js +6 -19
- package/dist/Form/index.d.ts +3 -0
- package/dist/Form/index.js +2 -0
- package/dist/Navigation/Navigation.AppBar.d.ts +2 -6
- package/dist/Navigation/Navigation.AppBar.js +9 -7
- package/dist/Navigation/Navigation.BottomNav.d.ts +1 -3
- package/dist/Navigation/Navigation.BottomNav.js +7 -6
- package/dist/Navigation/Navigation.Header.d.ts +2 -2
- package/dist/Navigation/Navigation.Menu.js +10 -3
- package/dist/Navigation/Navigation.Search.d.ts +41 -0
- package/dist/Navigation/Navigation.Search.js +47 -0
- package/dist/Navigation/Navigation.SearchBar.d.ts +1 -2
- package/dist/Navigation/Navigation.SearchBar.js +16 -19
- package/dist/Navigation/Navigation.SegmentedControl.d.ts +11 -3
- package/dist/Navigation/Navigation.SegmentedControl.js +29 -16
- package/dist/Navigation/index.d.ts +11 -2
- package/dist/Navigation/index.js +2 -0
- package/dist/branding.d.ts +3 -2
- package/dist/primitives/BaseButton.d.ts +10 -12
- package/dist/primitives/BaseButton.js +9 -11
- package/dist/theme/typography.d.ts +1 -1
- package/dist/utils/image.d.ts +3 -0
- package/dist/utils/image.js +13 -0
- package/dist/utils/keys.d.ts +1 -0
- package/dist/utils/keys.js +7 -0
- package/dist/utils/progress.d.ts +1 -0
- package/dist/utils/progress.js +5 -0
- package/dist/utils/reorder.js +2 -5
- package/dist/utils/useReducedMotion.d.ts +1 -0
- package/dist/utils/useReducedMotion.js +43 -0
- package/dist/utils/useSheetTranslation.d.ts +5 -0
- package/dist/utils/useSheetTranslation.js +23 -0
- package/dist/views/AppSplashView.d.ts +3 -2
- package/dist/views/AppSplashView.js +3 -2
- package/dist/views/AuthFormView.d.ts +2 -2
- package/dist/views/FormViewV2.d.ts +2 -2
- package/dist/views/ListViewV2.context.d.ts +3 -1
- package/dist/views/ListViewV2.context.js +41 -21
- package/dist/views/ListViewV2.d.ts +15 -7
- package/dist/views/ListViewV2.js +44 -23
- package/dist/views/types/IListView.js +6 -5
- package/dist/widgets/CollectionOptions.js +9 -7
- package/dist/widgets/FilterBar.d.ts +1 -1
- package/dist/widgets/FilterBar.js +1 -1
- package/dist/widgets/FilterBuilder.js +7 -7
- package/dist/widgets/HomeHeader.d.ts +2 -2
- package/dist/widgets/PromoModal.js +3 -1
- package/dist/widgets/ScreenHeader.d.ts +2 -2
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ This package is maintained by Sincpro as part of the mobile framework ecosystem.
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npx expo install @sincpro/mobile-ui
|
|
14
|
+
npx expo install @sincpro/mobile-ui expo-image
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Wire the Tailwind preset, then import by category:
|
|
@@ -36,11 +36,7 @@ export default function InvoiceScreen() {
|
|
|
36
36
|
<Display.Card>
|
|
37
37
|
<Display.KeyValue label="Invoice #" value="INV-0042" copyable />
|
|
38
38
|
<Display.Monetary value={1500.0} variant="dataLarge" />
|
|
39
|
-
<Actions.Button
|
|
40
|
-
accessibleSizing
|
|
41
|
-
onPress={() => toast.success("Invoice sent")}
|
|
42
|
-
title="Send Invoice"
|
|
43
|
-
/>
|
|
39
|
+
<Actions.Button onPress={() => toast.success("Invoice sent")} title="Send Invoice" />
|
|
44
40
|
</Display.Card>
|
|
45
41
|
);
|
|
46
42
|
}
|
|
@@ -91,10 +87,23 @@ recoverable error. It uses fictional data and local state, not persistence or a
|
|
|
91
87
|
|
|
92
88
|
### Accessible interaction
|
|
93
89
|
|
|
94
|
-
-
|
|
95
|
-
`Feedback.
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
- Loading feedback respects the live system Reduce Motion setting; `animated={false}`
|
|
91
|
+
keeps `Feedback.Skeleton`, `Progress`, `Spinner` and `Loading` static explicitly.
|
|
92
|
+
Spinners expose a named busy state; progress exposes a percentage only when known.
|
|
93
|
+
Track/indicator styling and native accessibility props remain customizable.
|
|
94
|
+
See the [feedback contract](docs/CATALOG.md#feedback-feedback) and the
|
|
95
|
+
[comparison and priorities](docs/DESIGN-SYSTEM-GAPS.md).
|
|
96
|
+
- v2 requires `expo-image`: `Display.Image`, logos, story media and illustrations share
|
|
97
|
+
the Expo renderer, disk caching, automatic source recycling and Reduce Motion policy.
|
|
98
|
+
`Display.ExpoImage` exposes the direct renderer; `Display.Image` adds error/retry composition.
|
|
99
|
+
See the [breaking migration guide](docs/MIGRATION-V2.md).
|
|
100
|
+
- `ListViewV2.Content` exposes `listProps` for virtualization, `getItemLayout`, viewability
|
|
101
|
+
and `extraData`, plus `listRef` and content styling. `Root hasMore={false}` stops
|
|
102
|
+
pagination; `loadingMore` and refresh pause it. See the
|
|
103
|
+
[mobile performance and usability plan](docs/MOBILE-QUALITY.md).
|
|
104
|
+
- Buttons, icon actions, inputs, selection controls and navigation use their 48-point
|
|
105
|
+
accessible sizing by default. `accessibleSizing` was removed: there is no legacy
|
|
106
|
+
compact-target mode. Labels wrap and follow system scaling; presentation stays configurable.
|
|
98
107
|
- Supply `accessibilityLabel` for icon-only actions. Loading buttons expose busy/disabled
|
|
99
108
|
state and retain their accessible name.
|
|
100
109
|
- `Display.CopyableText` uses a familiar copy icon: tap a short value to copy it.
|
|
@@ -113,7 +122,7 @@ recoverable error. It uses fictional data and local state, not persistence or a
|
|
|
113
122
|
the keyboard. It preserves typography/tokens and backs the longer-text modes above.
|
|
114
123
|
On iOS, `Text selectable` alone only exposes full-paragraph copying, not range handles.
|
|
115
124
|
- Headings and section titles expose native header semantics.
|
|
116
|
-
- Checkbox/Select announce control state
|
|
125
|
+
- Checkbox/Select announce control state and use accessible targets without an opt-in.
|
|
117
126
|
- Sheet supports accessible escape and `showCloseButton`; `SwipeableRow visibleActions`
|
|
118
127
|
supplies a non-gesture alternative. Keep explicit labels for essential actions.
|
|
119
128
|
- `Dialog/NativeSheet` is an optional Expo 58 native presentation (`@expo/ui` peer);
|
|
@@ -122,7 +131,7 @@ recoverable error. It uses fictional data and local state, not persistence or a
|
|
|
122
131
|
summaries, cancelable selection, filters applied only on confirmation and archive/undo.
|
|
123
132
|
- Form/List view refresh always clears the busy state on failure and accepts
|
|
124
133
|
`onRefreshError` for application recovery without discarding content.
|
|
125
|
-
- AppBar, BottomNav and SegmentedControl
|
|
134
|
+
- AppBar, BottomNav and SegmentedControl use accessible sizing; banners expose status
|
|
126
135
|
semantics, and confirmation defaults use explicit verbs.
|
|
127
136
|
|
|
128
137
|
The [audit and validation report](docs/UX-AUDIT.md) records scope, evidence and device limits.
|
|
@@ -219,8 +228,8 @@ Auto wraps above font scale 1.3, otherwise it scrolls horizontally. Quick chips
|
|
|
219
228
|
immediately; the recipe's advanced sheet applies only on confirmation.
|
|
220
229
|
|
|
221
230
|
`Actions.FilterButton variant="mobile"` adds readable wrapping labels and 48-point targets.
|
|
222
|
-
`FilterButtonGroup layout="scroll|wrap"`
|
|
223
|
-
|
|
231
|
+
`FilterButtonGroup layout="scroll|wrap"` supports both layouts. `Navigation.SearchBar`
|
|
232
|
+
uses accessible sizing by default in v2, with no legacy sizing flag.
|
|
224
233
|
When placing these controls in a host ScrollView, use `keyboardShouldPersistTaps="handled"`
|
|
225
234
|
so a filter can act on the first tap while the keyboard is open.
|
|
226
235
|
Use virtualized lists for production-sized collections.
|
|
@@ -324,6 +333,15 @@ template layout without a toolbar.
|
|
|
324
333
|
Multiline Return remains a newline; dismissing the keyboard does not submit the form.
|
|
325
334
|
Story: `Patterns/Forms/Keyboard navigation`.
|
|
326
335
|
|
|
336
|
+
`Form.ErrorSummary` presents ordered `{ id, label, message }` errors with
|
|
337
|
+
`onSelectError(id)`: the host focuses the corresponding native input ref with `.focus()`.
|
|
338
|
+
Use the same messages inline, keep controlled field drafts mounted, and place the summary
|
|
339
|
+
inside `KeyboardForm`. The first appearance is announced; updates while errors remain do
|
|
340
|
+
not repeatedly interrupt typing. Set `announce={false}` if the host already announces.
|
|
341
|
+
`renderHeader` and `renderErrorContent` replace presentation, not the named selection
|
|
342
|
+
action. Root/item/text props and styles remain customizable.
|
|
343
|
+
Story: `Patterns/Forms/Validation` includes real input refs and custom content.
|
|
344
|
+
|
|
327
345
|
`ListViewV2.Content` accepts native `keyboardShouldPersistTaps` and `keyboardDismissMode`
|
|
328
346
|
across populated, empty and error states. Handled row/retry actions receive the first tap
|
|
329
347
|
while search has focus (`"handled"` by default); pass `"never"` for the old dismiss-first
|
|
@@ -391,6 +409,31 @@ host-supplied autocomplete while focused. Choosing a suggestion calls `onSelect`
|
|
|
391
409
|
silently rewriting the query. A host may complete a title or create a field-specific
|
|
392
410
|
condition; it owns matching, request cancellation and stale responses.
|
|
393
411
|
|
|
412
|
+
`Navigation.Search` composes that existing field with horizontally scrolling chips,
|
|
413
|
+
advanced filters in a `Dialog/Sheet`, recent searches and suggestions:
|
|
414
|
+
|
|
415
|
+
```tsx
|
|
416
|
+
<Navigation.Search.Root>
|
|
417
|
+
<Navigation.Search.Field value={query} onChangeText={setQuery} />
|
|
418
|
+
<Navigation.Search.Filters>
|
|
419
|
+
<Actions.FilterButton active title="Todos" onPress={() => {}} variant="mobile" />
|
|
420
|
+
</Navigation.Search.Filters>
|
|
421
|
+
<Navigation.Search.FilterSheet
|
|
422
|
+
activeCount={activeFilterCount}
|
|
423
|
+
onApply={applyFilters}
|
|
424
|
+
onClear={clearDraftFilters}
|
|
425
|
+
>
|
|
426
|
+
<FilterFields />
|
|
427
|
+
</Navigation.Search.FilterSheet>
|
|
428
|
+
<Navigation.Search.History items={recent} onRemove={removeRecent} onSelect={setQuery} />
|
|
429
|
+
</Navigation.Search.Root>
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
It composes presentation only; the host owns query/filter state, history persistence and
|
|
433
|
+
results. Keep `ListViewV2.Header.Search` when its built-in debounced `onSearch` behavior is
|
|
434
|
+
preferred. Use `Navigation.AppBar` for back/context and `Navigation.Steps` for sequential
|
|
435
|
+
flows rather than adding breadcrumb trails to compact mobile headers.
|
|
436
|
+
|
|
394
437
|
Story: `Patterns/Collections/Query configuration` exercises actual local filtering,
|
|
395
438
|
multiple sorting priorities, grouping levels and saving/loading configurations.
|
|
396
439
|
|
|
@@ -461,7 +504,7 @@ const { control } = useForm<{ email: string; amount: number }>();
|
|
|
461
504
|
Queued toast system with promise support — covers the full loading → success / error lifecycle:
|
|
462
505
|
|
|
463
506
|
```tsx
|
|
464
|
-
import { ToastProvider, useToast } from "@sincpro/mobile-ui/Feedback";
|
|
507
|
+
import { Toast, ToastProvider, useToast } from "@sincpro/mobile-ui/Feedback";
|
|
465
508
|
|
|
466
509
|
// 1. Wrap the app once (createAppShell does this automatically)
|
|
467
510
|
<ToastProvider position="top">{children}</ToastProvider>;
|
|
@@ -474,8 +517,62 @@ toast.promise(sendInvoice(), {
|
|
|
474
517
|
success: "Invoice sent",
|
|
475
518
|
error: "Failed to send",
|
|
476
519
|
});
|
|
520
|
+
|
|
521
|
+
// Compose custom content while keeping the shared toast surface and lifecycle
|
|
522
|
+
toast.show({
|
|
523
|
+
render: ({ dismiss, update }) => (
|
|
524
|
+
<>
|
|
525
|
+
<Toast.Icon variant="loading" />
|
|
526
|
+
<Toast.Content>
|
|
527
|
+
<Toast.Title>Syncing catalog</Toast.Title>
|
|
528
|
+
<Toast.Description>Downloading product images…</Toast.Description>
|
|
529
|
+
</Toast.Content>
|
|
530
|
+
<Toast.Action label="Details" onPress={() => update({ message: "Still syncing…" })} />
|
|
531
|
+
<Toast.Close onPress={dismiss} />
|
|
532
|
+
</>
|
|
533
|
+
),
|
|
534
|
+
variant: "loading",
|
|
535
|
+
});
|
|
477
536
|
```
|
|
478
537
|
|
|
538
|
+
Toast cards can also be dismissed with an upward or downward swipe. Custom render content keeps
|
|
539
|
+
the shared card surface, safe-area placement, lifecycle, and swipe behavior.
|
|
540
|
+
|
|
541
|
+
Temporary toasts now show a close action by default too; the `dismissible: false` option explicitly
|
|
542
|
+
removes it. Escape dismisses the default surface. Enter/exit animation follows live
|
|
543
|
+
Reduce Motion; loading uses the common accessible spinner. `Toast.Root` accepts native
|
|
544
|
+
view props/styles; `Toast.Action` and `Toast.Close` accept native pressable props/styles.
|
|
545
|
+
Custom renderers must include their own visible close alternative when appropriate.
|
|
546
|
+
|
|
547
|
+
### Composable mobile navigation and loading
|
|
548
|
+
|
|
549
|
+
`Display.Carousel` supports short, non-virtualized collections with named 48-point
|
|
550
|
+
previous/next controls, localized page labels and optional controlled `index` /
|
|
551
|
+
`onIndexChange`. Rejected controlled swipes restore the accepted page. Measured trailing
|
|
552
|
+
space lets the last narrow card reach its snap position.
|
|
553
|
+
`renderControls({ index, count, canPrevious, canNext, previous, next, goTo })` replaces the
|
|
554
|
+
controls without replacing index validation or scrolling. Custom controls own their
|
|
555
|
+
accessible labels/disabled state and must provide a non-swipe alternative.
|
|
556
|
+
Use `itemWidth` / `gap` for snap geometry; `itemStyle`, root native props/styles,
|
|
557
|
+
`contentContainerStyle`, dot styles and `paginationTextProps` customize presentation.
|
|
558
|
+
Data keys must be unique, non-empty strings. Use a virtualized list for large datasets.
|
|
559
|
+
Stories: `Components/Data display/Carousel` and `Carousel Customized`.
|
|
560
|
+
|
|
561
|
+
`Feedback.DomainSplashScreen` waits for actual `isLoading={false}`, respecting
|
|
562
|
+
`minDuration`, rather than fabricating progress or finishing after a timer.
|
|
563
|
+
Children remain mounted, preserving drafts, but are inaccessible/non-interactive while
|
|
564
|
+
loading. `onComplete` runs once per completed cycle; timers/animations are cleaned up.
|
|
565
|
+
`renderLoading({ domainName, isLoading, reducedMotion })`, `containerStyle` and
|
|
566
|
+
`overlayStyle` let developers/AI own the visuals without replacing that lifecycle.
|
|
567
|
+
Do not use splash completion as a data-loading signal.
|
|
568
|
+
Story: `Patterns/Feedback/Loading lifecycle`.
|
|
569
|
+
|
|
570
|
+
The shared `useReducedMotion` hook makes one native preference query/subscription for
|
|
571
|
+
all active consumers and cancels stale queries when they leave. RN sheets, menus,
|
|
572
|
+
confirmation/edit dialogs, dropdowns, story viewer, accordion, dock and collection/promo
|
|
573
|
+
overlays follow the live setting; drag interactions remain available.
|
|
574
|
+
This does not certify arbitrary custom renderers or the optional native Sheet engine.
|
|
575
|
+
|
|
479
576
|
### Bottom Sheet and Dialogs
|
|
480
577
|
|
|
481
578
|
```tsx
|
|
@@ -525,8 +622,33 @@ import { Display } from "@sincpro/mobile-ui/Display";
|
|
|
525
622
|
<Display.Badge variant="success">Paid</Display.Badge>
|
|
526
623
|
<Display.Monetary amount={1500.00} variant="dataLarge" />
|
|
527
624
|
<Display.DataTable columns={cols} rows={rows} />
|
|
625
|
+
|
|
626
|
+
<Display.AmountSummary.Root currencySymbol="Bs">
|
|
627
|
+
<Display.AmountSummary.Row label="Subtotal" value={120} />
|
|
628
|
+
<Display.AmountSummary.Row label="Delivery" value={8.5} />
|
|
629
|
+
<Display.AmountSummary.Total label="Total" value={128.5} />
|
|
630
|
+
</Display.AmountSummary.Root>
|
|
528
631
|
```
|
|
529
632
|
|
|
633
|
+
`AmountSummary` formats and arranges amounts; the app supplies labels, calculations and
|
|
634
|
+
business states. `KeyValue copyable` remains the reusable copy affordance for identifiers.
|
|
635
|
+
`Actions.Dock` defaults to a fixed bottom action bar. Swipe up on its handle or press
|
|
636
|
+
“Más opciones” to reveal additional content above it; the primary action remains fixed.
|
|
637
|
+
The dedicated 48-point handle owns the touch from its start, so an ancestor scroll view
|
|
638
|
+
cannot steal it. Swipe up opens a native modal; swipe down on the panel handle closes it.
|
|
639
|
+
The expanded panel blocks and dims the underlying screen, while the primary action remains
|
|
640
|
+
outside its scrolling content. Back, accessible escape, the collapse button and backdrop
|
|
641
|
+
close it (`dismissOnBackdrop={false}` disables only backdrop dismissal).
|
|
642
|
+
Use `expanded` / `onExpandedChange` for host-controlled state, or `defaultExpanded` for
|
|
643
|
+
uncontrolled initial state. Wrap the app in `SafeAreaProvider`. Keep the collapsed fixed
|
|
644
|
+
bar outside scroll views, within the screen's safe area, and reserve space below list content.
|
|
645
|
+
Inside `FormViewV2.Footer`, set `placement="footer"` to reuse its sticky layout.
|
|
646
|
+
The panel fits its content up to `maxExpandedHeightRatio` (default `0.6`) and available screen
|
|
647
|
+
space; overflow scrolls and the modal avoids the keyboard.
|
|
648
|
+
|
|
649
|
+
This changes the Dock's former inline expansion behavior. See
|
|
650
|
+
[the migration guide](docs/MOBILE_UX_MIGRATION.md) before adopting it in core or host apps.
|
|
651
|
+
|
|
530
652
|
---
|
|
531
653
|
|
|
532
654
|
## Component Catalog
|
|
@@ -538,6 +660,7 @@ import { Display } from "@sincpro/mobile-ui/Display";
|
|
|
538
660
|
| `Actions.Button` | Primary action — gradient, outline, flat, link variants; optional confirm dialog |
|
|
539
661
|
| `Actions.IconButton` | Icon-only tap target — ghost, primary, secondary, tertiary variants |
|
|
540
662
|
| `Actions.FAB` | Floating Action Button — fixed position, optional label |
|
|
663
|
+
| `Actions.Dock` | Fixed primary action with swipe-expandable content and persistent CTA |
|
|
541
664
|
| `Actions.FilterButton` | Chip-style active/inactive toggle |
|
|
542
665
|
| `Actions.FilterButtonGroup` | Horizontal scroll container for filter chips |
|
|
543
666
|
|
|
@@ -548,6 +671,7 @@ import { Display } from "@sincpro/mobile-ui/Display";
|
|
|
548
671
|
| `Display.Badge` | Status badge — success, warning, danger, info, neutral |
|
|
549
672
|
| `Display.Card` | Surface container with optional shadow and padding |
|
|
550
673
|
| `Display.KeyValue` | Label + value row, optional clipboard copy |
|
|
674
|
+
| `Display.AmountSummary` | Composable monetary rows and total |
|
|
551
675
|
| `Display.Stat` | KPI cell — label, value, optional trend indicator |
|
|
552
676
|
| `Display.StatCard` | Card-wrapped KPI |
|
|
553
677
|
| `Display.Monetary` | Formatted monetary amount using mono typography |
|
|
@@ -566,7 +690,7 @@ import { Display } from "@sincpro/mobile-ui/Display";
|
|
|
566
690
|
| `Display.CopyableText` | Text with one-tap clipboard copy |
|
|
567
691
|
| `Display.Logo` | Brand logo (falls back to `setBranding` value) |
|
|
568
692
|
| `Display.Accordion` | Collapsible content section |
|
|
569
|
-
| `Display.Carousel` |
|
|
693
|
+
| `Display.Carousel` | Controlled short carousel with non-swipe alternatives |
|
|
570
694
|
| `Display.Story` / `StoryTray` / `StoryViewer` | Stories UI pattern |
|
|
571
695
|
| `Display.MediaCard` | Card with image or video embed |
|
|
572
696
|
| `Display.Pattern` | Decorative background pattern |
|
|
@@ -583,6 +707,7 @@ import { Display } from "@sincpro/mobile-ui/Display";
|
|
|
583
707
|
| `Form.FractionalQuantityInput` | Decimal quantity input |
|
|
584
708
|
| `Form.Select` | Dropdown select (modal-based) |
|
|
585
709
|
| `Form.Dropdown` | Inline dropdown menu |
|
|
710
|
+
| `Form.ErrorSummary` | Ordered validation errors linked to native field focus |
|
|
586
711
|
| `Form.RadioButton` | Single-select radio option |
|
|
587
712
|
| `Form.Checkbox` | Multi-select checkbox |
|
|
588
713
|
| `Form.Switch` | Toggle switch |
|
|
@@ -602,6 +727,7 @@ import { Display } from "@sincpro/mobile-ui/Display";
|
|
|
602
727
|
| ------------------------------- | -------------------------------- |
|
|
603
728
|
| `Feedback.Spinner` | Animated loading spinner |
|
|
604
729
|
| `Feedback.Loading` | Full-screen loading overlay |
|
|
730
|
+
| `Feedback.DomainSplashScreen` | Customizable real-load lifecycle |
|
|
605
731
|
| `Feedback.Skeleton` | Content placeholder shimmer |
|
|
606
732
|
| `Feedback.Progress` | Linear progress bar |
|
|
607
733
|
| `Feedback.ProgressCircle` | Circular progress indicator |
|
|
@@ -614,19 +740,20 @@ import { Display } from "@sincpro/mobile-ui/Display";
|
|
|
614
740
|
|
|
615
741
|
### Navigation
|
|
616
742
|
|
|
617
|
-
| Component | Description
|
|
618
|
-
| --------------------------------- |
|
|
619
|
-
| `Navigation.AppBar` | Top navigation bar with action slots
|
|
620
|
-
| `Navigation.BottomNav` | Bottom tab bar
|
|
621
|
-
| `Navigation.Header` | Screen header
|
|
622
|
-
| `Navigation.HeaderToolbar` | Header with left, center, right slots
|
|
623
|
-
| `Navigation.CollapsingHeader` | Header that collapses on scroll
|
|
624
|
-
| `Navigation.FloatingBar` | Floating action bar
|
|
625
|
-
| `Navigation.SearchBar` | Navigation-level search input
|
|
626
|
-
| `Navigation.
|
|
627
|
-
| `Navigation.
|
|
628
|
-
| `Navigation.
|
|
629
|
-
| `Navigation.
|
|
743
|
+
| Component | Description |
|
|
744
|
+
| --------------------------------- | ------------------------------------------------- |
|
|
745
|
+
| `Navigation.AppBar` | Top navigation bar with action slots |
|
|
746
|
+
| `Navigation.BottomNav` | Bottom tab bar |
|
|
747
|
+
| `Navigation.Header` | Screen header |
|
|
748
|
+
| `Navigation.HeaderToolbar` | Header with left, center, right slots |
|
|
749
|
+
| `Navigation.CollapsingHeader` | Header that collapses on scroll |
|
|
750
|
+
| `Navigation.FloatingBar` | Floating action bar |
|
|
751
|
+
| `Navigation.SearchBar` | Navigation-level search input |
|
|
752
|
+
| `Navigation.Search` | Compound field, filters, history and filter sheet |
|
|
753
|
+
| `Navigation.SegmentedControl` | Segmented control switcher |
|
|
754
|
+
| `Navigation.Tabs` | Tab navigation container |
|
|
755
|
+
| `Navigation.Steps` | Step indicator / breadcrumb |
|
|
756
|
+
| `Navigation.Menu` / `MenuSection` | Dropdown menu with sections |
|
|
630
757
|
|
|
631
758
|
### Dialog
|
|
632
759
|
|
|
Binary file
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Fontshare EULA
|
|
2
|
+
|
|
3
|
+
---—---------------------------------—------------------------------
|
|
4
|
+
Free Font - End User License Agreement (FF EULA)
|
|
5
|
+
---—---------------------------------—------------------------------
|
|
6
|
+
Notice to User
|
|
7
|
+
Indian Type Foundry designs, produces and distributes font software as digital fonts to end users worldwide. In addition to commercial fonts that are available for a fee, ITF also offers several fonts which can be used free of charge. The free fonts are distributed through a dedicated platform called www.fontshare.com (“Fontshare”) to end users worldwide. These free fonts are subject to this legally binding EULA between the Indian Type Foundry (“Indian Type Foundry” or “Licensor”) and you (“Licensee”).
|
|
8
|
+
You acknowledge that the Font Software and designs embodied therein are protected by the copyright, other intellectual property rights and industrial property rights and by international treaties. They are and remain at all times the intellectual property of the Indian Type Foundry.
|
|
9
|
+
In addition to direct download, Fontshare also offers these free fonts via Fonthsare API using a code. In this case, the Font Software is delivered directly from the servers used by Indian Type Foundry to the Licensee's website, without the Licensee having to download the Font Software.
|
|
10
|
+
By downloading, accessing the API, installing, storing, copying or using one of any Font Software, you agree to the following terms.
|
|
11
|
+
|
|
12
|
+
Definitions
|
|
13
|
+
“Font Software” refers to the set of computer files or programs released under this license that instructs your computer to display and/or print each letters, characters, typographic designs, ornament and so forth. Font Software includes all bitmap and vector representations of fonts and typographic representations and embellishments created by or derived from the Font Software.
|
|
14
|
+
“Original Version” refers to the Font Software as distributed by the Indian Type Foundry as the copyright holder.
|
|
15
|
+
“Derivative Work” refers to the pictorial representation of the font created by the Font Software, including typographic characters such as letters, numerals, ornaments, symbols, or punctuation and special characters.
|
|
16
|
+
|
|
17
|
+
01. Grant of License
|
|
18
|
+
You are hereby granted a non-exclusive, non-assignable, non-transferrable, terminable license to access, download and use the Font Software for your personal or commercial use for an unlimited period of time for free of charge.
|
|
19
|
+
You may use the font Software in any media (including Print, Web, Mobile, Digital, Apps, ePub, Broadcasting and OEM) at any scale, at any location worldwide.
|
|
20
|
+
You may use the Font Software to create logos and other graphic elements, images on any surface, vector files or other scalable drawings and static images.
|
|
21
|
+
You may use the Font Software on any number of devices (computer, tablet, phone). The number of output devices (Printers) is not restricted.
|
|
22
|
+
You may make only such reasonable number of back-up copies suitable to your permitted use.
|
|
23
|
+
You may but are not required to identify Indian Type Foundry Fonts in your work credits.
|
|
24
|
+
|
|
25
|
+
02. Limitations of usage
|
|
26
|
+
You may not modify, edit, adapt, translate, reverse engineer, decompile or disassemble, alter or otherwise copy the Font Software or the designs embodied therein in whole or in part, without the prior written consent of the Licensor.
|
|
27
|
+
The Fonts may not - beyond the permitted copies and the uses defined herein - be distributed, duplicated, loaned, resold or licensed in any way, whether by lending, donating or give otherwise to a person or entity. This includes the distribution of the Fonts by e-mail, on USB sticks, CD-ROMs, or other media, uploading them in a public server or making the fonts available on peer-to-peer networks. A passing on to external designers or service providers (design agencies, repro studios, printers, etc.) is also not permitted.
|
|
28
|
+
You are not allowed to transmit the Font Software over the Internet in font serving or for font replacement by means of technologies such as but not limited to EOT, Cufon, sIFR or similar technologies that may be developed in the future without the prior written consent of the Licensor.
|
|
29
|
+
|
|
30
|
+
03. Embedding
|
|
31
|
+
You may embed the Font Software in PDF and other digital documents provided that is done in a secured, read-only mode. It must be ensured beyond doubt that the recipient cannot use the Font Software to edit or to create new documents. The design data (PDFs) created in this way and under these created design data (PDFs) may be distributed in any number.
|
|
32
|
+
The extraction of the Font Software in whole or in part is prohibited.
|
|
33
|
+
|
|
34
|
+
04. Third party use, Commercial print service provider
|
|
35
|
+
You may include the Font Software in a non-editable electronic document solely for printing and display purposes and provide that electronic document to the commercial print service provider for the purpose of printing. If the print service needs to install the fonts, they too need to download the Font Software from the Licensor's website.
|
|
36
|
+
|
|
37
|
+
05. Derivative Work
|
|
38
|
+
You are allowed to make derivative works as far as you use them for your personal or commercial use. However, you cannot modify, make changes or reverse engineer the original font software provided to you. Any derivative works are the exclusive property of the Licensor and shall be subject to the terms and conditions of this EULA. Derivative works may not be sub-licensed, sold, leased, rented, loaned, or given away without the express written permission of the Licensor.
|
|
39
|
+
|
|
40
|
+
06. Warranty and Liability
|
|
41
|
+
BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, INDIAN TYPE FOUNDRY MAKES NO WARRANTIES, EXPRESS OR IMPLIED AS TO THE MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR OTHERWISE. THE FONT SOFTWARE WAS NOT MANUFACTURED FOR USE IN MANUFACTURING CONTROL DEVICES OR NAVIGATION DEVICES OR IN CIRCUMSTANCES THAT COULD RESULT IN ENVIRONMENTAL DAMAGE OR PERSONAL INJURY. WITHOUT LIMITING THE FOREGOING, INDIAN TYPE FOUNDRY SHALL IN NO EVENT BE LIABLE TO THE LICENSED USER OR ANY OTHER THIRD PARTY FOR ANY DIRECT, CONSEQUENTIAL OR INCIDENTAL DAMAGES, INCLUDING DAMAGES FROM LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION NOR FOR LOST PROFITS OR SAVINGS ARISING OUT OF THE USE OR INABILITY TO USE THE PRODUCT EVEN IF NOTIFIED IN ADVANCE, UNDER NO CIRCUMSTANCES SHALL INDIAN TYPE FOUNDRY’S LIABILITY EXCEED THE REPLACEMENT COST OF THE SOFTWARE.
|
|
42
|
+
IF LICENSEE CHOOSES TO ACCESS THE FONT SOFTWARE THROUGH A CODE (API), IT MAY HAVE A DIRECT IMPACT ON LICENSEE'S WEBSITE OR APPLICATIONS. INDIAN TYPE FOUNDRY IS NOT RESPONSIBLE OR LIABLE FOR ANY INTERRUPTION, MALFUNCTION, DOWNTIME OR OTHER FAILURE OF THE WEBSITE OR ITS API.
|
|
43
|
+
|
|
44
|
+
07. Updates, Maintenance and Support Services
|
|
45
|
+
Licensor will not provide you with any support services for the Software under this Agreement.
|
|
46
|
+
|
|
47
|
+
08. Termination
|
|
48
|
+
Any breach of the terms of this agreement shall be a cause for termination, provided that such breach is notified in writing to the Licensee by the Licensor and the Licensee failed to rectify the breach within 30 days of the receipt of such notification.
|
|
49
|
+
In the event of termination and without limitation of any remedies under law or equity, you must delete the Font Software and all copies thereof. Proof of this must be provided upon request of the Licensor.
|
|
50
|
+
We reserve the right to claim damages for the violation of the conditions.
|
|
51
|
+
|
|
52
|
+
09. Final Provisions
|
|
53
|
+
If individual provisions of this agreement are or become invalid, the validity of the remaining provisions shall remain unaffected. Invalid provisions shall be replaced by mutual agreement by such provisions that are suitable to achieve the desired economic purpose, taking into account the interests of both parties. The same shall apply mutatis mutandis to the filling of any gaps which may arise in this agreement.
|
|
54
|
+
This contract is subject to laws of the Republic of India. Place of performance and exclusive place of jurisdiction for all disputes between the parties arising out of or in connection with this contract is, as far as legally permissible, Ahmedabad, India.
|
|
55
|
+
-
|
|
56
|
+
Last Updated on 22 March 2021
|
|
57
|
+
Copyright 2021 Indian Type Foundry. All rights reserved.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -11,18 +11,18 @@ declare const button: import("tailwind-variants").TVReturnType<{
|
|
|
11
11
|
outlineDanger: "bg-transparent border border-danger";
|
|
12
12
|
outlineBlack: "bg-transparent border border-border-strong";
|
|
13
13
|
flat: "bg-text-primary/10";
|
|
14
|
-
link: "bg-transparent
|
|
14
|
+
link: "bg-transparent";
|
|
15
15
|
transparent: "bg-transparent border border-border-light";
|
|
16
16
|
};
|
|
17
17
|
size: {
|
|
18
|
-
small: "py-1.5 px-3 min-h-[
|
|
19
|
-
medium: "py-2.5 px-4 min-h-[
|
|
18
|
+
small: "py-1.5 px-3 min-h-[48px]";
|
|
19
|
+
medium: "py-2.5 px-4 min-h-[48px]";
|
|
20
20
|
large: "py-3 px-5 min-h-[48px]";
|
|
21
21
|
};
|
|
22
22
|
disabled: {
|
|
23
23
|
true: "opacity-70";
|
|
24
24
|
};
|
|
25
|
-
}, undefined, "rounded-md items-center justify-center flex-row min-h-[
|
|
25
|
+
}, undefined, "rounded-md items-center justify-center flex-row min-h-[48px]", {
|
|
26
26
|
variant: {
|
|
27
27
|
primary: "bg-accent";
|
|
28
28
|
accent: "bg-transparent";
|
|
@@ -32,12 +32,12 @@ declare const button: import("tailwind-variants").TVReturnType<{
|
|
|
32
32
|
outlineDanger: "bg-transparent border border-danger";
|
|
33
33
|
outlineBlack: "bg-transparent border border-border-strong";
|
|
34
34
|
flat: "bg-text-primary/10";
|
|
35
|
-
link: "bg-transparent
|
|
35
|
+
link: "bg-transparent";
|
|
36
36
|
transparent: "bg-transparent border border-border-light";
|
|
37
37
|
};
|
|
38
38
|
size: {
|
|
39
|
-
small: "py-1.5 px-3 min-h-[
|
|
40
|
-
medium: "py-2.5 px-4 min-h-[
|
|
39
|
+
small: "py-1.5 px-3 min-h-[48px]";
|
|
40
|
+
medium: "py-2.5 px-4 min-h-[48px]";
|
|
41
41
|
large: "py-3 px-5 min-h-[48px]";
|
|
42
42
|
};
|
|
43
43
|
disabled: {
|
|
@@ -53,12 +53,12 @@ declare const button: import("tailwind-variants").TVReturnType<{
|
|
|
53
53
|
outlineDanger: "bg-transparent border border-danger";
|
|
54
54
|
outlineBlack: "bg-transparent border border-border-strong";
|
|
55
55
|
flat: "bg-text-primary/10";
|
|
56
|
-
link: "bg-transparent
|
|
56
|
+
link: "bg-transparent";
|
|
57
57
|
transparent: "bg-transparent border border-border-light";
|
|
58
58
|
};
|
|
59
59
|
size: {
|
|
60
|
-
small: "py-1.5 px-3 min-h-[
|
|
61
|
-
medium: "py-2.5 px-4 min-h-[
|
|
60
|
+
small: "py-1.5 px-3 min-h-[48px]";
|
|
61
|
+
medium: "py-2.5 px-4 min-h-[48px]";
|
|
62
62
|
large: "py-3 px-5 min-h-[48px]";
|
|
63
63
|
};
|
|
64
64
|
disabled: {
|
|
@@ -138,7 +138,6 @@ export interface ButtonProps extends VariantProps<typeof button>, AccessibilityP
|
|
|
138
138
|
className?: string;
|
|
139
139
|
icon?: React.ReactNode;
|
|
140
140
|
testID?: string;
|
|
141
|
-
accessibleSizing?: boolean;
|
|
142
141
|
}
|
|
143
142
|
export interface ButtonIconProps extends VariantProps<typeof buttonIcon>, AccessibilityProps {
|
|
144
143
|
icon: string;
|
|
@@ -148,7 +147,6 @@ export interface ButtonIconProps extends VariantProps<typeof buttonIcon>, Access
|
|
|
148
147
|
disabled?: boolean;
|
|
149
148
|
className?: string;
|
|
150
149
|
testID?: string;
|
|
151
|
-
accessibleSizing?: boolean;
|
|
152
150
|
}
|
|
153
151
|
declare const ButtonWithIcon: React.FC<ButtonProps> & {
|
|
154
152
|
Icon: React.FC<ButtonIconProps>;
|
|
@@ -8,7 +8,7 @@ import { useCallback } from "react";
|
|
|
8
8
|
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
|
|
9
9
|
// ─── Style variants ───────────────────────────────────────────────────────────
|
|
10
10
|
const button = tv({
|
|
11
|
-
base: "rounded-md items-center justify-center flex-row min-h-[
|
|
11
|
+
base: "rounded-md items-center justify-center flex-row min-h-[48px]",
|
|
12
12
|
variants: {
|
|
13
13
|
variant: {
|
|
14
14
|
primary: "bg-accent",
|
|
@@ -19,12 +19,12 @@ const button = tv({
|
|
|
19
19
|
outlineDanger: "bg-transparent border border-danger",
|
|
20
20
|
outlineBlack: "bg-transparent border border-border-strong",
|
|
21
21
|
flat: "bg-text-primary/10",
|
|
22
|
-
link: "bg-transparent
|
|
22
|
+
link: "bg-transparent",
|
|
23
23
|
transparent: "bg-transparent border border-border-light",
|
|
24
24
|
},
|
|
25
25
|
size: {
|
|
26
|
-
small: "py-1.5 px-3 min-h-[
|
|
27
|
-
medium: "py-2.5 px-4 min-h-[
|
|
26
|
+
small: "py-1.5 px-3 min-h-[48px]",
|
|
27
|
+
medium: "py-2.5 px-4 min-h-[48px]",
|
|
28
28
|
large: "py-3 px-5 min-h-[48px]",
|
|
29
29
|
},
|
|
30
30
|
disabled: {
|
|
@@ -151,7 +151,7 @@ const textVariantMap = {
|
|
|
151
151
|
medium: "button",
|
|
152
152
|
large: "buttonLarge",
|
|
153
153
|
};
|
|
154
|
-
const Button = ({ title, onPress, confirm, variant = "primary", size = "medium", loading = false, disabled = false, className, icon, testID,
|
|
154
|
+
const Button = ({ title, onPress, confirm, variant = "primary", size = "medium", loading = false, disabled = false, className, icon, testID, accessibilityLabel, accessibilityState, ...accessibilityProps }) => {
|
|
155
155
|
const { show, hide } = useConfirmationContext();
|
|
156
156
|
const textColorClass = textColor({ variant: variant, disabled });
|
|
157
157
|
const loadingColorValue = getLoadingColor(variant);
|
|
@@ -179,18 +179,16 @@ const Button = ({ title, onPress, confirm, variant = "primary", size = "medium",
|
|
|
179
179
|
...accessibilityState,
|
|
180
180
|
disabled: disabled || loading,
|
|
181
181
|
busy: loading,
|
|
182
|
-
}, style:
|
|
183
|
-
? { minHeight: 48, minWidth: 48, justifyContent: "center" }
|
|
184
|
-
: undefined, testID: testID, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Typography.Text, { className: textColorClass, variant: textVariantValue, children: title })) }));
|
|
182
|
+
}, style: { minHeight: 48, minWidth: 48, justifyContent: "center" }, testID: testID, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Typography.Text, { allowFontScaling: true, className: textColorClass, style: { flexShrink: 1 }, variant: textVariantValue, children: title })) }));
|
|
185
183
|
}
|
|
186
184
|
const emphasize = variant === "primary" || variant === "cta";
|
|
187
185
|
return (_jsx(TouchableOpacity, { activeOpacity: 0.8, className: button({ variant, size, disabled, className }), disabled: disabled || loading, onPress: handlePress, ...accessibilityProps, accessibilityLabel: accessibilityLabel ?? title, accessibilityRole: "button", accessibilityState: {
|
|
188
186
|
...accessibilityState,
|
|
189
187
|
disabled: disabled || loading,
|
|
190
188
|
busy: loading,
|
|
191
|
-
}, style:
|
|
189
|
+
}, style: { minHeight: 48, minWidth: 48 }, testID: testID, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsxs(_Fragment, { children: [icon ? _jsx(View, { className: "mr-2", children: icon }) : null, _jsx(Typography.Text, { allowFontScaling: true, className: textColorClass, semibold: emphasize, style: { flexShrink: 1 }, variant: textVariantValue, children: title })] })) }));
|
|
192
190
|
};
|
|
193
|
-
const ButtonIcon = ({ icon, onPress, confirm, variant = "primary", size = "medium", loading = false, disabled = false, className, testID,
|
|
191
|
+
const ButtonIcon = ({ icon, onPress, confirm, variant = "primary", size = "medium", loading = false, disabled = false, className, testID, accessibilityState, ...accessibilityProps }) => {
|
|
194
192
|
const { show, hide } = useConfirmationContext();
|
|
195
193
|
const iconColorValue = getIconColor(variant, disabled);
|
|
196
194
|
const iconSizeValue = iconSizeMap[size] ?? 20;
|
|
@@ -217,7 +215,7 @@ const ButtonIcon = ({ icon, onPress, confirm, variant = "primary", size = "mediu
|
|
|
217
215
|
...accessibilityState,
|
|
218
216
|
disabled: disabled || loading,
|
|
219
217
|
busy: loading,
|
|
220
|
-
}, style:
|
|
218
|
+
}, style: { minHeight: 48, minWidth: 48 }, testID: testID, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Icon, { color: iconColorValue, name: icon, size: iconSizeValue })) }));
|
|
221
219
|
};
|
|
222
220
|
const ButtonWithIcon = Object.assign(Button, { Icon: ButtonIcon });
|
|
223
221
|
export default ButtonWithIcon;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export interface DockProps {
|
|
3
|
+
primaryAction: ReactNode;
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
summary?: ReactNode;
|
|
6
|
+
title?: string;
|
|
7
|
+
moreLabel?: string;
|
|
8
|
+
collapseLabel?: string;
|
|
9
|
+
backdropLabel?: string;
|
|
10
|
+
expanded?: boolean;
|
|
11
|
+
defaultExpanded?: boolean;
|
|
12
|
+
onExpandedChange?: (expanded: boolean) => void;
|
|
13
|
+
dismissOnBackdrop?: boolean;
|
|
14
|
+
maxExpandedHeightRatio?: number;
|
|
15
|
+
placement?: "fixed" | "footer";
|
|
16
|
+
className?: string;
|
|
17
|
+
testID?: string;
|
|
18
|
+
}
|
|
19
|
+
declare function Dock({ primaryAction, children, summary, title, moreLabel, collapseLabel, backdropLabel, expanded: controlledExpanded, defaultExpanded, onExpandedChange, dismissOnBackdrop, maxExpandedHeightRatio, placement, className, testID, }: DockProps): import("react").JSX.Element;
|
|
20
|
+
export default Dock;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "nativewind/jsx-runtime";
|
|
2
|
+
/* eslint-disable react-hooks/refs */
|
|
3
|
+
import Icon from "../Display/Display.Icon.js";
|
|
4
|
+
import Pressable from "../primitives/Pressable.js";
|
|
5
|
+
import { themeToVars, useTheme } from "../theme/index.js";
|
|
6
|
+
import { cn } from "../theme/tw.js";
|
|
7
|
+
import { Typography } from "../Typography/index.js";
|
|
8
|
+
import { useReducedMotion } from "../utils/useReducedMotion.js";
|
|
9
|
+
import { vars } from "nativewind";
|
|
10
|
+
import { useEffect, useRef, useState } from "react";
|
|
11
|
+
import { Animated, KeyboardAvoidingView, Modal, PanResponder, Platform, ScrollView, useWindowDimensions, View, } from "react-native";
|
|
12
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
13
|
+
function Dock({ primaryAction, children, summary, title = "Más opciones", moreLabel = "Más opciones", collapseLabel = "Ocultar opciones", backdropLabel = "Cerrar panel", expanded: controlledExpanded, defaultExpanded = false, onExpandedChange, dismissOnBackdrop = true, maxExpandedHeightRatio = 0.6, placement = "fixed", className, testID, }) {
|
|
14
|
+
const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
|
|
15
|
+
const expanded = controlledExpanded ?? internalExpanded;
|
|
16
|
+
const theme = useTheme();
|
|
17
|
+
const reducedMotion = useReducedMotion();
|
|
18
|
+
const insets = useSafeAreaInsets();
|
|
19
|
+
const { height } = useWindowDimensions();
|
|
20
|
+
const [barHeight, setBarHeight] = useState(0);
|
|
21
|
+
const translateY = useRef(new Animated.Value(0)).current;
|
|
22
|
+
const requestExpanded = (next) => {
|
|
23
|
+
if (next === expanded)
|
|
24
|
+
return;
|
|
25
|
+
if (controlledExpanded === undefined)
|
|
26
|
+
setInternalExpanded(next);
|
|
27
|
+
onExpandedChange?.(next);
|
|
28
|
+
};
|
|
29
|
+
const gestureRef = useRef({ expanded, requestExpanded, reducedMotion });
|
|
30
|
+
gestureRef.current = { expanded, requestExpanded, reducedMotion };
|
|
31
|
+
const resolvedRatio = Number.isFinite(maxExpandedHeightRatio)
|
|
32
|
+
? Math.min(0.9, Math.max(0.1, maxExpandedHeightRatio))
|
|
33
|
+
: 0.6;
|
|
34
|
+
const maxPanelHeight = Math.max(0, Math.min(height * resolvedRatio, height - insets.top - insets.bottom - barHeight));
|
|
35
|
+
const resetDrag = () => {
|
|
36
|
+
translateY.stopAnimation();
|
|
37
|
+
if (gestureRef.current.reducedMotion) {
|
|
38
|
+
translateY.setValue(0);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
Animated.spring(translateY, {
|
|
42
|
+
toValue: 0,
|
|
43
|
+
useNativeDriver: true,
|
|
44
|
+
damping: 24,
|
|
45
|
+
stiffness: 260,
|
|
46
|
+
}).start();
|
|
47
|
+
};
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
translateY.stopAnimation();
|
|
50
|
+
translateY.setValue(0);
|
|
51
|
+
return () => translateY.stopAnimation();
|
|
52
|
+
}, [expanded, reducedMotion, translateY]);
|
|
53
|
+
const [pan] = useState(() => PanResponder.create({
|
|
54
|
+
onStartShouldSetPanResponder: () => true,
|
|
55
|
+
onPanResponderGrant: () => translateY.stopAnimation(),
|
|
56
|
+
onPanResponderTerminationRequest: () => false,
|
|
57
|
+
onPanResponderMove: (_, gesture) => {
|
|
58
|
+
if (gestureRef.current.expanded)
|
|
59
|
+
translateY.setValue(Math.max(0, gesture.dy));
|
|
60
|
+
},
|
|
61
|
+
onPanResponderRelease: (_, gesture) => {
|
|
62
|
+
const isVertical = Math.abs(gesture.dy) > Math.abs(gesture.dx);
|
|
63
|
+
const shouldChange = gestureRef.current.expanded
|
|
64
|
+
? gesture.dy > 40 || gesture.vy > 0.5
|
|
65
|
+
: gesture.dy < -40 || gesture.vy < -0.5;
|
|
66
|
+
if (isVertical && shouldChange) {
|
|
67
|
+
gestureRef.current.requestExpanded(!gestureRef.current.expanded);
|
|
68
|
+
}
|
|
69
|
+
resetDrag();
|
|
70
|
+
},
|
|
71
|
+
onPanResponderTerminate: resetDrag,
|
|
72
|
+
}));
|
|
73
|
+
const handle = (_jsx(View, { className: "min-h-12 items-center justify-center", ...pan.panHandlers, testID: testID ? `${testID}-handle` : undefined, children: _jsx(View, { className: "h-1 w-10 rounded-full bg-border-strong" }) }));
|
|
74
|
+
const bar = (_jsxs(View, { className: cn("gap-3 border-t border-border-light bg-bg-card px-4 pb-2", className), onLayout: (event) => setBarHeight(event.nativeEvent.layout.height), testID: testID ? `${testID}-bar` : undefined, children: [!expanded ? handle : null, summary, _jsxs(View, { className: "flex-row items-center gap-3", children: [_jsxs(Pressable, { accessibilityLabel: expanded ? collapseLabel : moreLabel, accessibilityRole: "button", accessibilityState: { expanded }, className: "min-h-12 flex-1 flex-row items-center justify-center gap-1 rounded-lg bg-bg-muted px-3", onPress: () => requestExpanded(!expanded), testID: testID ? `${testID}-more` : undefined, children: [_jsx(Typography.Text, { className: "text-text-primary", variant: "buttonSmall", children: expanded ? collapseLabel : moreLabel }), _jsx(Icon, { color: theme.icon.secondary, name: expanded ? "chevron-down" : "chevron-up", size: 16 })] }), _jsx(View, { className: "flex-1", children: primaryAction })] })] }));
|
|
75
|
+
return (_jsxs(_Fragment, { children: [_jsx(View, { className: cn(placement === "fixed" && "absolute bottom-0 left-0 right-0 z-20"), style: expanded ? { height: barHeight } : undefined, testID: testID, children: !expanded ? bar : null }), _jsx(Modal, { animationType: "none", onRequestClose: () => requestExpanded(false), presentationStyle: "overFullScreen", testID: testID ? `${testID}-modal` : undefined, transparent: true, visible: expanded, children: _jsx(KeyboardAvoidingView, { behavior: Platform.OS === "ios" ? "padding" : "height", style: { flex: 1 }, testID: testID ? `${testID}-keyboard` : undefined, children: _jsxs(View, { accessibilityViewIsModal: true, style: { flex: 1, justifyContent: "flex-end" }, children: [_jsx(Pressable, { accessibilityLabel: backdropLabel, accessibilityRole: "button", className: "absolute inset-0 bg-black/50", disabled: !dismissOnBackdrop, onPress: dismissOnBackdrop ? () => requestExpanded(false) : undefined, testID: testID ? `${testID}-backdrop` : undefined }), _jsxs(View, { onAccessibilityEscape: () => requestExpanded(false), style: [
|
|
76
|
+
vars(themeToVars(theme)),
|
|
77
|
+
{
|
|
78
|
+
paddingBottom: insets.bottom,
|
|
79
|
+
backgroundColor: theme.bg.card,
|
|
80
|
+
maxHeight: height - insets.top,
|
|
81
|
+
flexShrink: 1,
|
|
82
|
+
},
|
|
83
|
+
], children: [_jsx(View, { style: { overflow: "hidden", flexShrink: 1 }, children: _jsxs(Animated.View, { style: {
|
|
84
|
+
maxHeight: maxPanelHeight,
|
|
85
|
+
flexShrink: 1,
|
|
86
|
+
transform: [{ translateY }],
|
|
87
|
+
}, testID: testID ? `${testID}-expanded-content` : undefined, children: [handle, _jsxs(ScrollView, { contentContainerStyle: { paddingHorizontal: 16, paddingBottom: 16 }, keyboardShouldPersistTaps: "handled", nestedScrollEnabled: true, style: { flexShrink: 1 }, testID: testID ? `${testID}-scroll` : undefined, children: [title ? (_jsx(Typography.Text, { accessibilityRole: "header", className: "pb-2 text-text-primary", semibold: true, variant: "subtitle", children: title })) : null, children] })] }) }), bar] })] }) }) })] }));
|
|
88
|
+
}
|
|
89
|
+
export default Dock;
|