@sincpro/mobile-ui 1.0.1 → 2.0.0
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 +701 -231
- package/dist/Actions/Actions.Button.d.ts +15 -12
- package/dist/Actions/Actions.Button.js +25 -14
- package/dist/Actions/Actions.Dock.d.ts +20 -0
- package/dist/Actions/Actions.Dock.js +89 -0
- package/dist/Actions/Actions.FAB.d.ts +2 -1
- package/dist/Actions/Actions.FAB.js +5 -3
- package/dist/Actions/Actions.FilterButton.d.ts +10 -2
- package/dist/Actions/Actions.FilterButton.js +21 -6
- package/dist/Actions/Actions.IconButton.d.ts +4 -2
- package/dist/Actions/Actions.IconButton.js +7 -3
- package/dist/Actions/index.d.ts +6 -2
- package/dist/Actions/index.js +4 -1
- package/dist/Dialog/BottomSheet/BottomSheet.Root.js +22 -34
- package/dist/Dialog/Dialog.ActionSheet.d.ts +2 -1
- package/dist/Dialog/Dialog.ActionSheet.js +17 -21
- package/dist/Dialog/Dialog.Confirmation.js +8 -6
- package/dist/Dialog/Dialog.EditValue.js +3 -1
- package/dist/Dialog/Dialog.NativeSheet.d.ts +10 -0
- package/dist/Dialog/Dialog.NativeSheet.js +21 -0
- package/dist/Dialog/Dialog.Sheet.d.ts +5 -1
- package/dist/Dialog/Dialog.Sheet.js +30 -31
- 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.CopyableText.d.ts +9 -2
- package/dist/Display/Display.CopyableText.js +75 -7
- package/dist/Display/Display.ExpoImage.d.ts +8 -0
- package/dist/Display/Display.ExpoImage.js +15 -0
- package/dist/Display/Display.Icon.js +2 -1
- package/dist/Display/Display.Image.d.ts +10 -6
- package/dist/Display/Display.Image.js +39 -6
- package/dist/Display/Display.KeyValue.d.ts +11 -4
- package/dist/Display/Display.KeyValue.js +6 -4
- package/dist/Display/Display.ListGroup.d.ts +23 -0
- package/dist/Display/Display.ListGroup.js +17 -0
- package/dist/Display/Display.ListItem.d.ts +15 -2
- package/dist/Display/Display.ListItem.js +25 -8
- 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.SectionHeader.d.ts +11 -1
- package/dist/Display/Display.SectionHeader.js +11 -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/Display.SwipeableRow.d.ts +3 -1
- package/dist/Display/Display.SwipeableRow.js +3 -3
- package/dist/Display/index.d.ts +13 -0
- package/dist/Display/index.js +6 -0
- package/dist/Feedback/Feedback.Banner.js +1 -1
- package/dist/Feedback/Feedback.DomainSplashScreen.d.ts +13 -4
- package/dist/Feedback/Feedback.DomainSplashScreen.js +68 -53
- package/dist/Feedback/Feedback.EmptyState.d.ts +2 -2
- package/dist/Feedback/Feedback.EmptyState.js +3 -2
- package/dist/Feedback/Feedback.Error.d.ts +2 -1
- package/dist/Feedback/Feedback.Error.js +4 -3
- 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 +3 -1
- package/dist/Form/Form.Checkbox.js +2 -2
- package/dist/Form/Form.DatePicker.js +3 -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.FilterButton.d.ts +1 -1
- package/dist/Form/Form.Input.d.ts +4 -3
- package/dist/Form/Form.Input.js +2 -2
- package/dist/Form/Form.KeyboardForm.d.ts +11 -0
- package/dist/Form/Form.KeyboardForm.js +9 -0
- package/dist/Form/Form.KeyboardToolbar.d.ts +21 -0
- package/dist/Form/Form.KeyboardToolbar.js +70 -0
- package/dist/Form/Form.Select.d.ts +2 -1
- package/dist/Form/Form.Select.js +26 -26
- package/dist/Form/index.d.ts +9 -0
- package/dist/Form/index.js +6 -0
- package/dist/Navigation/Navigation.AppBar.js +8 -6
- package/dist/Navigation/Navigation.BottomNav.js +10 -5
- package/dist/Navigation/Navigation.Header.d.ts +2 -2
- package/dist/Navigation/Navigation.Menu.js +10 -3
- package/dist/Navigation/Navigation.RowItem.d.ts +1 -1
- package/dist/Navigation/Navigation.RowItem.js +2 -2
- package/dist/Navigation/Navigation.Search.d.ts +41 -0
- package/dist/Navigation/Navigation.Search.js +47 -0
- package/dist/Navigation/Navigation.SearchBar.d.ts +5 -1
- package/dist/Navigation/Navigation.SearchBar.js +25 -7
- package/dist/Navigation/Navigation.SearchSuggestions.d.ts +14 -0
- package/dist/Navigation/Navigation.SearchSuggestions.js +7 -0
- package/dist/Navigation/Navigation.SegmentedControl.d.ts +11 -1
- package/dist/Navigation/Navigation.SegmentedControl.js +29 -15
- package/dist/Navigation/index.d.ts +13 -0
- package/dist/Navigation/index.js +4 -0
- package/dist/Typography/Typography.Heading.js +1 -1
- package/dist/Typography/Typography.SelectableText.d.ts +10 -0
- package/dist/Typography/Typography.SelectableText.js +16 -0
- package/dist/Typography/Typography.Text.js +4 -20
- package/dist/Typography/index.d.ts +3 -0
- package/dist/Typography/index.js +3 -0
- package/dist/branding.d.ts +3 -2
- package/dist/layouts/AuthContainer.d.ts +4 -1
- package/dist/layouts/AuthContainer.js +3 -3
- package/dist/primitives/BaseButton.d.ts +15 -12
- package/dist/primitives/BaseButton.js +21 -9
- package/dist/theme/typography.d.ts +16 -1
- package/dist/theme/typography.js +19 -0
- package/dist/utils/criteria.d.ts +56 -0
- package/dist/utils/criteria.js +99 -0
- 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.d.ts +4 -0
- package/dist/utils/reorder.js +29 -0
- 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 +6 -3
- package/dist/views/AuthFormView.js +2 -2
- package/dist/views/FormViewV2.d.ts +9 -4
- package/dist/views/FormViewV2.js +35 -16
- package/dist/views/ListViewV2.context.d.ts +3 -1
- package/dist/views/ListViewV2.context.js +41 -21
- package/dist/views/ListViewV2.d.ts +18 -7
- package/dist/views/ListViewV2.js +69 -35
- package/dist/views/types/IListView.js +6 -5
- package/dist/widgets/CollectionOptions.d.ts +15 -0
- package/dist/widgets/CollectionOptions.js +129 -0
- package/dist/widgets/FilterBar.d.ts +21 -0
- package/dist/widgets/FilterBar.js +14 -0
- package/dist/widgets/FilterBuilder.d.ts +10 -0
- package/dist/widgets/FilterBuilder.js +115 -0
- package/dist/widgets/HomeHeader.d.ts +2 -2
- package/dist/widgets/PromoModal.js +3 -1
- package/dist/widgets/QueryChoice.d.ts +10 -0
- package/dist/widgets/QueryChoice.js +13 -0
- package/dist/widgets/ReorderableList.d.ts +51 -0
- package/dist/widgets/ReorderableList.js +138 -0
- package/dist/widgets/ScreenHeader.d.ts +2 -2
- package/dist/widgets/index.d.ts +4 -0
- package/dist/widgets/index.js +3 -0
- package/package.json +18 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @sincpro/mobile-ui
|
|
2
2
|
|
|
3
|
-
**Sincpro Mobile Design System** — the UI contract between design and engineering for React Native / Expo SDK
|
|
3
|
+
**Sincpro Mobile Design System** — the UI contract between design and engineering for React Native / Expo SDK 58 applications.
|
|
4
4
|
|
|
5
5
|
This package is maintained by Sincpro as part of the mobile framework ecosystem. Anyone can read the source and use the components. What Sincpro provides is the commitment behind it: stable APIs, predictable behavior, long-term support, and a team that ensures this keeps working as platforms evolve. Build on it knowing it will still be there.
|
|
6
6
|
|
|
@@ -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:
|
|
@@ -20,18 +20,14 @@ Wire the Tailwind preset, then import by category:
|
|
|
20
20
|
// tailwind.config.js
|
|
21
21
|
module.exports = {
|
|
22
22
|
presets: [require("@sincpro/mobile-ui/tailwind.preset")],
|
|
23
|
-
content: [
|
|
24
|
-
"./App.tsx",
|
|
25
|
-
"./src/**/*.{ts,tsx}",
|
|
26
|
-
"./node_modules/@sincpro/mobile-ui/**/*.js",
|
|
27
|
-
],
|
|
23
|
+
content: ["./App.tsx", "./src/**/*.{ts,tsx}", "./node_modules/@sincpro/mobile-ui/**/*.js"],
|
|
28
24
|
};
|
|
29
25
|
```
|
|
30
26
|
|
|
31
27
|
```tsx
|
|
32
28
|
import { Actions } from "@sincpro/mobile-ui/Actions";
|
|
33
29
|
import { Display } from "@sincpro/mobile-ui/Display";
|
|
34
|
-
import { Form }
|
|
30
|
+
import { Form } from "@sincpro/mobile-ui/Form";
|
|
35
31
|
import { useToast, ToastProvider } from "@sincpro/mobile-ui/Feedback";
|
|
36
32
|
|
|
37
33
|
export default function InvoiceScreen() {
|
|
@@ -39,10 +35,8 @@ export default function InvoiceScreen() {
|
|
|
39
35
|
return (
|
|
40
36
|
<Display.Card>
|
|
41
37
|
<Display.KeyValue label="Invoice #" value="INV-0042" copyable />
|
|
42
|
-
<Display.Monetary
|
|
43
|
-
<Actions.Button onPress={() => toast.success("Invoice sent")}
|
|
44
|
-
Send Invoice
|
|
45
|
-
</Actions.Button>
|
|
38
|
+
<Display.Monetary value={1500.0} variant="dataLarge" />
|
|
39
|
+
<Actions.Button onPress={() => toast.success("Invoice sent")} title="Send Invoice" />
|
|
46
40
|
</Display.Card>
|
|
47
41
|
);
|
|
48
42
|
}
|
|
@@ -81,6 +75,385 @@ Without a design system, every developer makes local decisions: which shade of g
|
|
|
81
75
|
|
|
82
76
|
## Philosophy
|
|
83
77
|
|
|
78
|
+
**Composable behavior, customizable presentation.** Sincpro defaults are a starting
|
|
79
|
+
point, not a mandatory visual style. Preserve existing appearances while exposing public
|
|
80
|
+
APIs for fonts, sizes, shapes, content and composition. See the
|
|
81
|
+
[permanent design-system manifesto](docs/MANIFESTO.md) for the goal, acceptance criteria
|
|
82
|
+
and current limitations; full customizability is an objective, not a completed audit.
|
|
83
|
+
|
|
84
|
+
The [UX/UI evolution plan](docs/UX-UI-PLAN.md) tracks the rollout. The first native pilot
|
|
85
|
+
is in Storybook under `Patterns/Records/Detail`: list, detail, copy, edit, cancel and
|
|
86
|
+
recoverable error. It uses fictional data and local state, not persistence or a backend.
|
|
87
|
+
|
|
88
|
+
### Accessible interaction
|
|
89
|
+
|
|
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.
|
|
107
|
+
- Supply `accessibilityLabel` for icon-only actions. Loading buttons expose busy/disabled
|
|
108
|
+
state and retain their accessible name.
|
|
109
|
+
- `Display.CopyableText` uses a familiar copy icon: tap a short value to copy it.
|
|
110
|
+
A transient checkmark confirms success without adding a “Copiado” block or shifting
|
|
111
|
+
layout; screen readers receive an announcement. Use `selectable` for longer text:
|
|
112
|
+
native text selection stays independent of the 48-point icon action that copies the
|
|
113
|
+
full value. Failures stay explicit and retryable. It accepts `disabled`, `testID`,
|
|
114
|
+
`accessibilityLabel`, `onCopied` and `onCopyError`. Copy targets intentionally reserve 48 points.
|
|
115
|
+
- `Display.KeyValue copyable` enables copying string/number values (enforced by its type).
|
|
116
|
+
Use `align="stacked"` for long content or `align="auto"` to stack below 360 points or
|
|
117
|
+
above a 1.3 font scale. Existing `align="row"` remains unchanged; React nodes are
|
|
118
|
+
display-only.
|
|
119
|
+
- `KeyValue selectable` also supports native text selection, with or without copying.
|
|
120
|
+
Choose the mode by the purpose of the field, not an automatic character-count threshold.
|
|
121
|
+
- `Typography.SelectableText` provides read-only native range selection without opening
|
|
122
|
+
the keyboard. It preserves typography/tokens and backs the longer-text modes above.
|
|
123
|
+
On iOS, `Text selectable` alone only exposes full-paragraph copying, not range handles.
|
|
124
|
+
- Headings and section titles expose native header semantics.
|
|
125
|
+
- Checkbox/Select announce control state and use accessible targets without an opt-in.
|
|
126
|
+
- Sheet supports accessible escape and `showCloseButton`; `SwipeableRow visibleActions`
|
|
127
|
+
supplies a non-gesture alternative. Keep explicit labels for essential actions.
|
|
128
|
+
- `Dialog/NativeSheet` is an optional Expo 58 native presentation (`@expo/ui` peer);
|
|
129
|
+
import it by subpath, not the root barrel. Use short action sheets for choices and a
|
|
130
|
+
sheet for a scoped task; do not stack sheets. Mobile-task stories demonstrate native
|
|
131
|
+
summaries, cancelable selection, filters applied only on confirmation and archive/undo.
|
|
132
|
+
- Form/List view refresh always clears the busy state on failure and accepts
|
|
133
|
+
`onRefreshError` for application recovery without discarding content.
|
|
134
|
+
- AppBar, BottomNav and SegmentedControl use accessible sizing; banners expose status
|
|
135
|
+
semantics, and confirmation defaults use explicit verbs.
|
|
136
|
+
|
|
137
|
+
The [audit and validation report](docs/UX-AUDIT.md) records scope, evidence and device limits.
|
|
138
|
+
|
|
139
|
+
### Clean Sincpro grouping and customization
|
|
140
|
+
|
|
141
|
+
`Display.ListGroup` groups related settings or a short list on one surface, with a title,
|
|
142
|
+
optional description/footer/action and hairline separators only between rows. Choose
|
|
143
|
+
`surface="plain"` for grouping by proximity instead of a rounded container.
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
<Display.ListGroup
|
|
147
|
+
title="Cuenta"
|
|
148
|
+
footer="Puedes cambiar estas preferencias en cualquier momento."
|
|
149
|
+
contentClassName="rounded-lg bg-bg-muted"
|
|
150
|
+
>
|
|
151
|
+
<Display.ListItem
|
|
152
|
+
variant="mobile"
|
|
153
|
+
title="Idioma"
|
|
154
|
+
value="Español"
|
|
155
|
+
chevron
|
|
156
|
+
onPress={openLanguageChoices}
|
|
157
|
+
/>
|
|
158
|
+
<Display.ListItem
|
|
159
|
+
variant="mobile"
|
|
160
|
+
title="Avisos importantes"
|
|
161
|
+
trailing={notificationControl}
|
|
162
|
+
/>
|
|
163
|
+
</Display.ListGroup>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- `ListItem variant="mobile"` opts into wrapping, a 48-point target,
|
|
167
|
+
tonal row press feedback and adaptive accessories. `layout="auto"` stacks values/controls
|
|
168
|
+
below the text on narrow screens or enlarged text; use `layout="stacked"` for narrow
|
|
169
|
+
embedded containers. Navigation values are also exposed as native accessibility values.
|
|
170
|
+
- New mobile rows default to `density="compact"`: regular 14-point titles/values,
|
|
171
|
+
13-point supporting text and 8-point vertical padding. `density="comfortable"` restores
|
|
172
|
+
the earlier PR's 17/16-point reading presentation and 12-point padding.
|
|
173
|
+
Legacy rows (`variant="default"`) keep their original appearance.
|
|
174
|
+
- Put a toggle/checkbox in a **non-pressable** row; do not nest a second action inside a
|
|
175
|
+
pressable row. The leading/trailing slots remain consumer-owned.
|
|
176
|
+
- `Typography.Text variant="bodyReadable"` and `Typography.SelectableText` with the same
|
|
177
|
+
variant opt into 17 points while retaining `configureFonts` and system font scaling.
|
|
178
|
+
The 14-point `body` and existing ListItem appearance remain unchanged.
|
|
179
|
+
- Customize per instance with `className`, `contentClassName`, slots, surface and separators.
|
|
180
|
+
Rows also accept `style`, `titleTextProps`, `subtitleTextProps` and `valueTextProps`.
|
|
181
|
+
Text props accept typography variants, weights, font families and native style arrays.
|
|
182
|
+
`ListGroup.headerProps` forwards header styling/text props; `footerTextProps` controls
|
|
183
|
+
string footers. Default groups use a 16-point section heading and 13-point description/footer.
|
|
184
|
+
Group `style`, `contentStyle` and `separatorStyle` control spacing, surface and dividers.
|
|
185
|
+
Headers expose `style`, `contentStyle`, `actionStyle` and `actionTextProps` as well.
|
|
186
|
+
For example, `titleTextProps={{ style: { fontFamily: "MyLoadedFont", fontSize: 15 } }}`
|
|
187
|
+
changes only one row, without copying its focus, accessibility or press behavior.
|
|
188
|
+
Customize branding centrally with `buildThemeFromBrand`/`extendTheme`, `setActiveTheme`
|
|
189
|
+
and `themeToVars` on the host, plus `configureFonts` for already-loaded font families.
|
|
190
|
+
Do not introduce a competing palette or hardcode colors in every screen.
|
|
191
|
+
- This group is not virtualized: use FlatList/SectionList for large or unbounded collections.
|
|
192
|
+
|
|
193
|
+
Stories: `Patterns/Forms/Settings` shows Sincpro defaults, long content, customized
|
|
194
|
+
surfaces and text roles. `Foundations/Typography/CompactHierarchy` and
|
|
195
|
+
`ComfortableHierarchy` show the same content at two densities, not two mandatory screens.
|
|
196
|
+
Customization is not a complete visual approval.
|
|
197
|
+
|
|
198
|
+
### Compose collections, not a prescribed screen
|
|
199
|
+
|
|
200
|
+
`Patterns/Collections/Browsing` shows the same local data as rows, cards and
|
|
201
|
+
long-label variants. It combines icons, bounded 56-point artwork, readable text, metrics,
|
|
202
|
+
independent open/save actions and a native detail sheet. These are recipes to adapt,
|
|
203
|
+
not new domain-specific screen components or a web toolbar squeezed onto a phone.
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
import { FilterBar } from "@sincpro/mobile-ui/widgets";
|
|
207
|
+
|
|
208
|
+
<FilterBar
|
|
209
|
+
filters={filterOptions}
|
|
210
|
+
onFilterPress={toggleFilter}
|
|
211
|
+
onClearFilters={clearFilters}
|
|
212
|
+
onOpenFilters={openFilterSheet}
|
|
213
|
+
resultsLabel={resultsLabel}
|
|
214
|
+
search={{
|
|
215
|
+
value: query,
|
|
216
|
+
onChangeText: setQuery,
|
|
217
|
+
accessibilityLabel: "Buscar recursos",
|
|
218
|
+
clearAccessibilityLabel: "Limpiar búsqueda",
|
|
219
|
+
}}
|
|
220
|
+
/>;
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Each filter supplies `id`, `label`, `selected` and optional `disabled`. State, matching,
|
|
224
|
+
result counts and advanced-filter drafts belong to the host. Clearing filters does not
|
|
225
|
+
silently clear the search query. Search, advanced action, result label and clear action
|
|
226
|
+
are optional; customize labels, `className`, theme and `layout="scroll|wrap|auto"`.
|
|
227
|
+
Auto wraps above font scale 1.3, otherwise it scrolls horizontally. Quick chips apply
|
|
228
|
+
immediately; the recipe's advanced sheet applies only on confirmation.
|
|
229
|
+
|
|
230
|
+
`Actions.FilterButton variant="mobile"` adds readable wrapping labels and 48-point targets.
|
|
231
|
+
`FilterButtonGroup layout="scroll|wrap"` supports both layouts. `Navigation.SearchBar`
|
|
232
|
+
uses accessible sizing by default in v2, with no legacy sizing flag.
|
|
233
|
+
When placing these controls in a host ScrollView, use `keyboardShouldPersistTaps="handled"`
|
|
234
|
+
so a filter can act on the first tap while the keyboard is open.
|
|
235
|
+
Use virtualized lists for production-sized collections.
|
|
236
|
+
|
|
237
|
+
Dependencies follow the latest stable version compatible with Expo 58, not unbounded
|
|
238
|
+
upgrades. Validate with `CI=1 npx expo install --check` in addition to the quality gates.
|
|
239
|
+
|
|
240
|
+
### Controlled reordering without a closed row design
|
|
241
|
+
|
|
242
|
+
Optional import: `@sincpro/mobile-ui/widgets/ReorderableList`. Install
|
|
243
|
+
`react-native-sortables@^1.10.1` in the host; it uses the host's Gesture Handler 3.3+,
|
|
244
|
+
Reanimated 4.7+ and Worklets setup. Wrap the app in `GestureHandlerRootView`.
|
|
245
|
+
The adapter is deliberately **not** in the generic widgets/root barrels: applications
|
|
246
|
+
that do not use drag-and-drop do not need to resolve this optional peer.
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
import ReorderableList from "@sincpro/mobile-ui/widgets/ReorderableList";
|
|
250
|
+
|
|
251
|
+
<ReorderableList
|
|
252
|
+
items={sections}
|
|
253
|
+
keyExtractor={(item) => item.id}
|
|
254
|
+
getItemLabel={(item) => item.name}
|
|
255
|
+
onChange={(next) => setSections(next)}
|
|
256
|
+
renderItem={({ item }) => <MySectionContent section={item} />}
|
|
257
|
+
itemStyle={{ borderRadius: 16, padding: 8 }}
|
|
258
|
+
/>;
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- A one-column, **non-virtualized short-list** adapter with variable-height content.
|
|
262
|
+
It does not own screen scrolling, persistence, confirmation or domain rules.
|
|
263
|
+
Do not use it as an unbounded feed, kanban board, file drop zone or cross-app drag API.
|
|
264
|
+
- Only the dedicated handle starts drag. Default arrows and the handle's native
|
|
265
|
+
increment/decrement actions offer alternatives; content can retain buttons, inputs
|
|
266
|
+
or any custom layout. Drag/drop uses insertion, not swapping.
|
|
267
|
+
- `onChange(next, { key, fromIndex, toIndex, source })` delivers an immutable order
|
|
268
|
+
(`source` is `drag` or `button`). Update controlled state synchronously; persist later,
|
|
269
|
+
showing save failures/retry in the host. A drop in the original position emits no change.
|
|
270
|
+
Draft/apply/cancel/undo can be built by owning that state, without replacing the gesture.
|
|
271
|
+
- Keys must be unique non-empty strings, stable across renders, never array indices.
|
|
272
|
+
Content updates with the same keys preserve current objects. Insert/remove/external
|
|
273
|
+
reorder or disabling **during** drag cancels the gesture, retains host data and shows
|
|
274
|
+
an accessible error. `onReorderRejected` reports `data-changed` or `disabled`.
|
|
275
|
+
The gesture engine remounts on rejection; keep persistent item state in the host.
|
|
276
|
+
- `renderItem` controls content. `renderControls` receives `dragHandle`, movement
|
|
277
|
+
callbacks and boundary flags; keep that handle and an accessible non-drag alternative
|
|
278
|
+
when replacing controls. `renderHandleContent` replaces **noninteractive** handle
|
|
279
|
+
contents only, keeping the gesture, accessible name and adjustable actions.
|
|
280
|
+
- Root `className`/`style`, `itemStyle`, `contentStyle`, `controlsStyle`, `handleStyle`
|
|
281
|
+
and `actionStyle` accept local layout/shape/spacing. Child content owns typography.
|
|
282
|
+
`labels` localizes controls, announcements and conflict errors.
|
|
283
|
+
- `sortableProps` forwards engine options such as activation delay, row gap, decorations,
|
|
284
|
+
animation settings and autoscroll, while the adapter owns identity, single-column
|
|
285
|
+
insertion and controlled callbacks. Provide a stable Reanimated `scrollableRef` to
|
|
286
|
+
the **actual Animated.ScrollView** for autoscroll; do not nest another scroll container
|
|
287
|
+
or attach the ref to a different view. Native scroll props/ref belong to the host.
|
|
288
|
+
Custom motion, targets and colors still require accessibility validation.
|
|
289
|
+
|
|
290
|
+
`Patterns/Collections/Reordering` demonstrates default, custom content/controls, long
|
|
291
|
+
rows and an external-update conflict. Only this recipe uses the new dependency;
|
|
292
|
+
existing form/list/query templates keep their behavior.
|
|
293
|
+
|
|
294
|
+
### Keyboard-aware forms and field navigation
|
|
295
|
+
|
|
296
|
+
Expo 58 does not automatically make arbitrary form layouts keyboard-safe.
|
|
297
|
+
The host must include `react-native-keyboard-controller` and its `KeyboardProvider` at
|
|
298
|
+
the app root. Use a development build when the runtime does not include that native module.
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
<Form.KeyboardForm contentContainerStyle={{ padding: 16, gap: 16 }}>
|
|
302
|
+
{fields}
|
|
303
|
+
</Form.KeyboardForm>
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
`KeyboardForm` owns the scroll and scopes editable inputs with `KeyboardToolbar.Group`.
|
|
307
|
+
Its toolbar provides previous/next/dismiss actions, including phone/number keyboards.
|
|
308
|
+
The scroll reserves the measured toolbar height plus 24 points (normally 72 total);
|
|
309
|
+
it grows with enlarged text. Override `bottomOffset` for additional accessories.
|
|
310
|
+
`toolbar={false}` keeps automatic avoidance without the toolbar.
|
|
311
|
+
Customize `toolbarLabels` or compose `Form.KeyboardToolbar` with an existing aware scroll,
|
|
312
|
+
using `onHeightChange` to reserve its measured height. The toolbar compensates for space
|
|
313
|
+
below its parent, such as a safe area or form footer, instead of floating over the caret.
|
|
314
|
+
The default toolbar uses compact icons; `toolbarProps={{ showDoneLabel: true }}` opts into
|
|
315
|
+
a text dismiss action. `toolbarProps` also accepts `className`, `style`, `actionStyle`,
|
|
316
|
+
`labelStyle`, `iconSize` and `renderActionContent(action, { label, disabled })`.
|
|
317
|
+
The content slot replaces only non-interactive content: the toolbar retains focus actions,
|
|
318
|
+
accessible labels and disabled states. `toolbarLabels` remains supported;
|
|
319
|
+
`toolbarProps` takes precedence. Font size/family and shape can be changed without
|
|
320
|
+
reimplementing keyboard behavior.
|
|
321
|
+
Do not nest this component inside another vertical ScrollView.
|
|
322
|
+
|
|
323
|
+
For existing templates, opt into `FormViewV2.Content keyboardToolbar` or
|
|
324
|
+
`AuthFormView keyboardToolbar` / `AuthContainer keyboardToolbar`; defaults stay unchanged.
|
|
325
|
+
`Form.Input` forwards its native ref for explicit return-key navigation:
|
|
326
|
+
`onSubmitEditing={() => nextRef.current?.focus()}` with `submitBehavior="submit"`.
|
|
327
|
+
`KeyboardForm` forwards its ref to the native aware scroll for methods such as
|
|
328
|
+
`assureFocusedInputVisible()` after validation/layout changes.
|
|
329
|
+
The three templates accept `keyboardFormProps` (excluding `children` and `toolbar`) for
|
|
330
|
+
the same ref, offsets, toolbar customization, scroll callbacks and content styling.
|
|
331
|
+
`keyboardToolbar` remains the explicit opt-in; omitting it preserves the original
|
|
332
|
+
template layout without a toolbar.
|
|
333
|
+
Multiline Return remains a newline; dismissing the keyboard does not submit the form.
|
|
334
|
+
Story: `Patterns/Forms/Keyboard navigation`.
|
|
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
|
+
|
|
345
|
+
`ListViewV2.Content` accepts native `keyboardShouldPersistTaps` and `keyboardDismissMode`
|
|
346
|
+
across populated, empty and error states. Handled row/retry actions receive the first tap
|
|
347
|
+
while search has focus (`"handled"` by default); pass `"never"` for the old dismiss-first
|
|
348
|
+
behavior. Drag-dismiss remains opt-in (`keyboardDismissMode="on-drag"`).
|
|
349
|
+
This changes interaction, not the list's typography or layout.
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
<FormViewV2.Content
|
|
353
|
+
keyboardToolbar
|
|
354
|
+
keyboardFormProps={{
|
|
355
|
+
ref: scrollRef,
|
|
356
|
+
toolbarProps: {
|
|
357
|
+
style: { borderRadius: 16 },
|
|
358
|
+
iconSize: 18,
|
|
359
|
+
showDoneLabel: true,
|
|
360
|
+
labelStyle: { fontFamily: "MyFont", fontSize: 13 },
|
|
361
|
+
},
|
|
362
|
+
}}
|
|
363
|
+
>
|
|
364
|
+
{fields}
|
|
365
|
+
</FormViewV2.Content>
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Default list rows now honor the template's `onPressItem` and `readonly` settings;
|
|
369
|
+
direct `Navigation.RowItem` usage can still supply the legacy callback on `item`.
|
|
370
|
+
|
|
371
|
+
### Structured queries, not just quick chips
|
|
372
|
+
|
|
373
|
+
`widgets.FilterBuilder` edits a controlled `CriteriaGroup`: nested `all`/`any`/`none`
|
|
374
|
+
groups and configured conditions. Supply fields with their label, type, permitted
|
|
375
|
+
operators and arity (`none`, `one`, `range`); choice values are explicit. Numbers remain
|
|
376
|
+
editable strings until validated/committed, date fields use the native picker, and invalid
|
|
377
|
+
conditions produce visible errors. The mobile editor opens one group at a time instead of
|
|
378
|
+
squeezing an arbitrarily indented desktop tree into the screen.
|
|
379
|
+
|
|
380
|
+
`widgets.CollectionOptions` is a full-screen, keyboard-aware workspace:
|
|
381
|
+
|
|
382
|
+
```tsx
|
|
383
|
+
<CollectionOptions
|
|
384
|
+
visible={open}
|
|
385
|
+
value={query}
|
|
386
|
+
fields={filterFields}
|
|
387
|
+
sortFields={sortFields}
|
|
388
|
+
groupFields={groupFields}
|
|
389
|
+
savedQueries={savedQueries}
|
|
390
|
+
onApply={applyQuery}
|
|
391
|
+
onClose={closeOptions}
|
|
392
|
+
onSave={saveQuery}
|
|
393
|
+
/>
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
`CollectionQuery` contains `search`, `filter`, ordered `sort` entries and ordered
|
|
397
|
+
`groupBy` field IDs. Order and grouping priorities have explicit move/remove actions;
|
|
398
|
+
dragging is not required. Loading a saved configuration changes the draft only;
|
|
399
|
+
Apply commits all dimensions together, Cancel discards the draft. Save errors keep the
|
|
400
|
+
draft/name for retry. The host owns saving/loading and closes the workspace after Apply.
|
|
401
|
+
|
|
402
|
+
The DS does not fetch models, evaluate filters, sort a page as if it were the full dataset,
|
|
403
|
+
persist favorites or translate this tree into a backend query language. Relational paths,
|
|
404
|
+
remote model discovery and a JSON editor are not implemented here. The web builder is a
|
|
405
|
+
semantic reference, not a component copied into mobile.
|
|
406
|
+
|
|
407
|
+
`Navigation.SearchBar suggestions={{ items, onSelect, loading, error, onRetry }}` shows
|
|
408
|
+
host-supplied autocomplete while focused. Choosing a suggestion calls `onSelect` without
|
|
409
|
+
silently rewriting the query. A host may complete a title or create a field-specific
|
|
410
|
+
condition; it owns matching, request cancellation and stale responses.
|
|
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
|
+
|
|
437
|
+
Story: `Patterns/Collections/Query configuration` exercises actual local filtering,
|
|
438
|
+
multiple sorting priorities, grouping levels and saving/loading configurations.
|
|
439
|
+
|
|
440
|
+
### Native E2E
|
|
441
|
+
|
|
442
|
+
With Maestro installed and an iOS simulator running Expo Go, start an isolated test server:
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
EXPO_PUBLIC_STORYBOOK_TEST=1 npx expo start --port 8082
|
|
446
|
+
make storybook-e2e
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
This disables Storybook's on-device chrome for tests without changing the normal server.
|
|
450
|
+
The flows verify copy, edit, cancel, failed-save recovery and long-content/dark interaction.
|
|
451
|
+
`STORYBOOK_URL` can override the default `exp://127.0.0.1:8082`.
|
|
452
|
+
Maestro analytics are disabled by the Makefile target. These flows target Expo Go on iOS;
|
|
453
|
+
Android needs a connected device and its appropriate app ID/host configuration.
|
|
454
|
+
Tests stop/relaunch Expo Go and switch stories; run them on a dedicated test simulator.
|
|
455
|
+
Enlarged-text screenshots are accessibility stress tests, not the default typography.
|
|
456
|
+
|
|
84
457
|
### Maintained for the long term
|
|
85
458
|
|
|
86
459
|
Enterprise software lives for years. The screens built with this DS will be in production long after the initial project is delivered. Sincpro's commitment is to maintain API stability, patch what breaks on platform upgrades, and evolve the system without forcing rewrites on consumers.
|
|
@@ -131,10 +504,10 @@ const { control } = useForm<{ email: string; amount: number }>();
|
|
|
131
504
|
Queued toast system with promise support — covers the full loading → success / error lifecycle:
|
|
132
505
|
|
|
133
506
|
```tsx
|
|
134
|
-
import { ToastProvider, useToast } from "@sincpro/mobile-ui/Feedback";
|
|
507
|
+
import { Toast, ToastProvider, useToast } from "@sincpro/mobile-ui/Feedback";
|
|
135
508
|
|
|
136
509
|
// 1. Wrap the app once (createAppShell does this automatically)
|
|
137
|
-
<ToastProvider position="top">{children}</ToastProvider
|
|
510
|
+
<ToastProvider position="top">{children}</ToastProvider>;
|
|
138
511
|
|
|
139
512
|
// 2. Call from anywhere in the tree
|
|
140
513
|
const toast = useToast();
|
|
@@ -142,10 +515,64 @@ toast.success("Invoice saved");
|
|
|
142
515
|
toast.promise(sendInvoice(), {
|
|
143
516
|
loading: "Sending…",
|
|
144
517
|
success: "Invoice sent",
|
|
145
|
-
error:
|
|
518
|
+
error: "Failed to send",
|
|
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",
|
|
146
535
|
});
|
|
147
536
|
```
|
|
148
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
|
+
|
|
149
576
|
### Bottom Sheet and Dialogs
|
|
150
577
|
|
|
151
578
|
```tsx
|
|
@@ -155,7 +582,7 @@ import { ConfirmationProvider, useConfirmationContext } from "@sincpro/mobile-ui
|
|
|
155
582
|
// Controlled sheet — built on RN Modal, no native peer deps
|
|
156
583
|
<Sheet visible={open} onClose={() => setOpen(false)} title="Options">
|
|
157
584
|
<Actions.Button onPress={confirm}>Confirm</Actions.Button>
|
|
158
|
-
</Sheet
|
|
585
|
+
</Sheet>;
|
|
159
586
|
|
|
160
587
|
// Imperative confirmation
|
|
161
588
|
const { confirm } = useConfirmationContext();
|
|
@@ -168,10 +595,16 @@ const agreed = await confirm({ title: "Delete invoice?", message: "This cannot b
|
|
|
168
595
|
import { Wizard, useWizard } from "@sincpro/mobile-ui/views/Wizard";
|
|
169
596
|
|
|
170
597
|
<Wizard initialStep="customer">
|
|
171
|
-
<Wizard.Step id="customer"
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
598
|
+
<Wizard.Step id="customer">
|
|
599
|
+
<CustomerStep />
|
|
600
|
+
</Wizard.Step>
|
|
601
|
+
<Wizard.Step id="items">
|
|
602
|
+
<ItemsStep />
|
|
603
|
+
</Wizard.Step>
|
|
604
|
+
<Wizard.Step id="review">
|
|
605
|
+
<ReviewStep />
|
|
606
|
+
</Wizard.Step>
|
|
607
|
+
</Wizard>;
|
|
175
608
|
|
|
176
609
|
// Inside any step
|
|
177
610
|
const { go, back } = useWizard();
|
|
@@ -189,171 +622,201 @@ import { Display } from "@sincpro/mobile-ui/Display";
|
|
|
189
622
|
<Display.Badge variant="success">Paid</Display.Badge>
|
|
190
623
|
<Display.Monetary amount={1500.00} variant="dataLarge" />
|
|
191
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>
|
|
192
631
|
```
|
|
193
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
|
+
|
|
194
652
|
---
|
|
195
653
|
|
|
196
654
|
## Component Catalog
|
|
197
655
|
|
|
198
656
|
### Actions
|
|
199
657
|
|
|
200
|
-
| Component
|
|
201
|
-
|
|
202
|
-
| `Actions.Button`
|
|
203
|
-
| `Actions.IconButton`
|
|
204
|
-
| `Actions.FAB`
|
|
205
|
-
| `Actions.
|
|
206
|
-
| `Actions.
|
|
658
|
+
| Component | Description |
|
|
659
|
+
| --------------------------- | -------------------------------------------------------------------------------- |
|
|
660
|
+
| `Actions.Button` | Primary action — gradient, outline, flat, link variants; optional confirm dialog |
|
|
661
|
+
| `Actions.IconButton` | Icon-only tap target — ghost, primary, secondary, tertiary variants |
|
|
662
|
+
| `Actions.FAB` | Floating Action Button — fixed position, optional label |
|
|
663
|
+
| `Actions.Dock` | Fixed primary action with swipe-expandable content and persistent CTA |
|
|
664
|
+
| `Actions.FilterButton` | Chip-style active/inactive toggle |
|
|
665
|
+
| `Actions.FilterButtonGroup` | Horizontal scroll container for filter chips |
|
|
207
666
|
|
|
208
667
|
### Display
|
|
209
668
|
|
|
210
|
-
| Component
|
|
211
|
-
|
|
212
|
-
| `Display.Badge`
|
|
213
|
-
| `Display.Card`
|
|
214
|
-
| `Display.KeyValue`
|
|
215
|
-
| `Display.
|
|
216
|
-
| `Display.
|
|
217
|
-
| `Display.
|
|
218
|
-
| `Display.
|
|
219
|
-
| `Display.
|
|
220
|
-
| `Display.
|
|
221
|
-
| `Display.
|
|
222
|
-
| `Display.
|
|
223
|
-
| `Display.
|
|
224
|
-
| `Display.
|
|
225
|
-
| `Display.
|
|
226
|
-
| `Display.
|
|
227
|
-
| `Display.
|
|
228
|
-
| `Display.
|
|
229
|
-
| `Display.
|
|
230
|
-
| `Display.
|
|
231
|
-
| `Display.
|
|
232
|
-
| `Display.
|
|
233
|
-
| `Display.
|
|
234
|
-
| `Display.
|
|
235
|
-
| `Display.
|
|
236
|
-
| `Display.
|
|
669
|
+
| Component | Description |
|
|
670
|
+
| --------------------------------------------- | ------------------------------------------------------ |
|
|
671
|
+
| `Display.Badge` | Status badge — success, warning, danger, info, neutral |
|
|
672
|
+
| `Display.Card` | Surface container with optional shadow and padding |
|
|
673
|
+
| `Display.KeyValue` | Label + value row, optional clipboard copy |
|
|
674
|
+
| `Display.AmountSummary` | Composable monetary rows and total |
|
|
675
|
+
| `Display.Stat` | KPI cell — label, value, optional trend indicator |
|
|
676
|
+
| `Display.StatCard` | Card-wrapped KPI |
|
|
677
|
+
| `Display.Monetary` | Formatted monetary amount using mono typography |
|
|
678
|
+
| `Display.PriceTag` | Currency-formatted price with size options |
|
|
679
|
+
| `Display.DataTable` | Scrollable table for structured data |
|
|
680
|
+
| `Display.ListItem` | Row with icon, label, sublabel, optional action |
|
|
681
|
+
| `Display.Chip` | Compact label tag |
|
|
682
|
+
| `Display.Avatar` / `AvatarGroup` | User avatar, stacked group |
|
|
683
|
+
| `Display.Icon` | Vector icon wrapper (`@expo/vector-icons`) |
|
|
684
|
+
| `Display.StatusDot` | Colored presence / state indicator |
|
|
685
|
+
| `Display.Divider` | Horizontal separator |
|
|
686
|
+
| `Display.GradientSurface` | Container with linear gradient background |
|
|
687
|
+
| `Display.Timeline` | Vertical event timeline |
|
|
688
|
+
| `Display.SwipeableRow` | Row with swipe-to-reveal actions |
|
|
689
|
+
| `Display.Tooltip` | Overlay tooltip |
|
|
690
|
+
| `Display.CopyableText` | Text with one-tap clipboard copy |
|
|
691
|
+
| `Display.Logo` | Brand logo (falls back to `setBranding` value) |
|
|
692
|
+
| `Display.Accordion` | Collapsible content section |
|
|
693
|
+
| `Display.Carousel` | Controlled short carousel with non-swipe alternatives |
|
|
694
|
+
| `Display.Story` / `StoryTray` / `StoryViewer` | Stories UI pattern |
|
|
695
|
+
| `Display.MediaCard` | Card with image or video embed |
|
|
696
|
+
| `Display.Pattern` | Decorative background pattern |
|
|
237
697
|
|
|
238
698
|
### Form
|
|
239
699
|
|
|
240
|
-
| Component
|
|
241
|
-
|
|
242
|
-
| `Form.TextInput`
|
|
243
|
-
| `Form.PasswordInput`
|
|
244
|
-
| `Form.PinInput`
|
|
245
|
-
| `Form.MonetaryInput`
|
|
246
|
-
| `Form.QuantitySelector`
|
|
247
|
-
| `Form.FractionalQuantityInput`
|
|
248
|
-
| `Form.Select`
|
|
249
|
-
| `Form.Dropdown`
|
|
250
|
-
| `Form.
|
|
251
|
-
| `Form.
|
|
252
|
-
| `Form.
|
|
253
|
-
| `Form.
|
|
254
|
-
| `Form.
|
|
255
|
-
| `Form.
|
|
256
|
-
| `Form.
|
|
257
|
-
| `Form.
|
|
258
|
-
| `Form.
|
|
259
|
-
| `Form.
|
|
260
|
-
| `Form.
|
|
700
|
+
| Component | Description |
|
|
701
|
+
| --------------------------------------- | ------------------------------------------------------------------- |
|
|
702
|
+
| `Form.TextInput` | Text field with label, error, and helper text |
|
|
703
|
+
| `Form.PasswordInput` | Text input with show/hide toggle |
|
|
704
|
+
| `Form.PinInput` | PIN code entry |
|
|
705
|
+
| `Form.MonetaryInput` | Currency input with formatting |
|
|
706
|
+
| `Form.QuantitySelector` | Integer increment / decrement |
|
|
707
|
+
| `Form.FractionalQuantityInput` | Decimal quantity input |
|
|
708
|
+
| `Form.Select` | Dropdown select (modal-based) |
|
|
709
|
+
| `Form.Dropdown` | Inline dropdown menu |
|
|
710
|
+
| `Form.ErrorSummary` | Ordered validation errors linked to native field focus |
|
|
711
|
+
| `Form.RadioButton` | Single-select radio option |
|
|
712
|
+
| `Form.Checkbox` | Multi-select checkbox |
|
|
713
|
+
| `Form.Switch` | Toggle switch |
|
|
714
|
+
| `Form.Slider` | Range slider (`@react-native-community/slider` peer) |
|
|
715
|
+
| `Form.DatePicker` | Date picker (`@react-native-community/datetimepicker` peer) |
|
|
716
|
+
| `Form.SearchBar` | Search input with clear button |
|
|
717
|
+
| `Form.ControlledInput` | `TextInput` + react-hook-form |
|
|
718
|
+
| `Form.ControlledRadioButton` | `RadioButton` + react-hook-form |
|
|
719
|
+
| `Form.ControlledCheckbox` | `Checkbox` + react-hook-form |
|
|
720
|
+
| `Form.ControlledDropdown` | `Dropdown` + react-hook-form |
|
|
721
|
+
| `Form.ControlledMonetaryInput` | `MonetaryInput` + react-hook-form |
|
|
261
722
|
| `Actions.Button` / `IconButton` / `FAB` | Action components (preferred over deprecated `Form.Button` aliases) |
|
|
262
723
|
|
|
263
724
|
### Feedback
|
|
264
725
|
|
|
265
|
-
| Component
|
|
266
|
-
|
|
267
|
-
| `Feedback.Spinner`
|
|
268
|
-
| `Feedback.Loading`
|
|
269
|
-
| `Feedback.
|
|
270
|
-
| `Feedback.
|
|
271
|
-
| `Feedback.
|
|
272
|
-
| `Feedback.
|
|
273
|
-
| `Feedback.
|
|
274
|
-
| `Feedback.
|
|
275
|
-
| `Feedback.
|
|
276
|
-
| `
|
|
277
|
-
| `
|
|
726
|
+
| Component | Description |
|
|
727
|
+
| ------------------------------- | -------------------------------- |
|
|
728
|
+
| `Feedback.Spinner` | Animated loading spinner |
|
|
729
|
+
| `Feedback.Loading` | Full-screen loading overlay |
|
|
730
|
+
| `Feedback.DomainSplashScreen` | Customizable real-load lifecycle |
|
|
731
|
+
| `Feedback.Skeleton` | Content placeholder shimmer |
|
|
732
|
+
| `Feedback.Progress` | Linear progress bar |
|
|
733
|
+
| `Feedback.ProgressCircle` | Circular progress indicator |
|
|
734
|
+
| `Feedback.Banner` | Contextual notification banner |
|
|
735
|
+
| `Feedback.Empty` / `EmptyState` | Empty list or page placeholder |
|
|
736
|
+
| `Feedback.Error` | Error state with optional retry |
|
|
737
|
+
| `Feedback.PermissionCard` | Permission request UI |
|
|
738
|
+
| `ToastProvider` / `useToast` | Queued toast notification system |
|
|
739
|
+
| `ErrorBoundary` | React error boundary wrapper |
|
|
278
740
|
|
|
279
741
|
### Navigation
|
|
280
742
|
|
|
281
|
-
| Component
|
|
282
|
-
|
|
283
|
-
| `Navigation.AppBar`
|
|
284
|
-
| `Navigation.BottomNav`
|
|
285
|
-
| `Navigation.Header`
|
|
286
|
-
| `Navigation.HeaderToolbar`
|
|
287
|
-
| `Navigation.CollapsingHeader`
|
|
288
|
-
| `Navigation.FloatingBar`
|
|
289
|
-
| `Navigation.SearchBar`
|
|
290
|
-
| `Navigation.
|
|
291
|
-
| `Navigation.
|
|
292
|
-
| `Navigation.
|
|
293
|
-
| `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 |
|
|
294
757
|
|
|
295
758
|
### Dialog
|
|
296
759
|
|
|
297
|
-
| Component
|
|
298
|
-
|
|
299
|
-
| `Dialog.BottomSheet`
|
|
300
|
-
| `Dialog.ActionSheet`
|
|
301
|
-
| `Dialog.Confirmation`
|
|
302
|
-
| `Dialog.EditValue`
|
|
303
|
-
| `Sheet` (direct import)
|
|
304
|
-
| `ConfirmationProvider` / `useConfirmationContext` | Imperative confirmation API
|
|
760
|
+
| Component | Description |
|
|
761
|
+
| ------------------------------------------------- | --------------------------------------------- |
|
|
762
|
+
| `Dialog.BottomSheet` | Bottom drawer (RN Modal, no native peer deps) |
|
|
763
|
+
| `Dialog.ActionSheet` | Action sheet with options list |
|
|
764
|
+
| `Dialog.Confirmation` | Confirm / cancel modal |
|
|
765
|
+
| `Dialog.EditValue` | Inline edit value modal |
|
|
766
|
+
| `Sheet` (direct import) | Controlled sheet via `Dialog/Dialog.Sheet` |
|
|
767
|
+
| `ConfirmationProvider` / `useConfirmationContext` | Imperative confirmation API |
|
|
305
768
|
|
|
306
769
|
### Typography
|
|
307
770
|
|
|
308
|
-
| Component
|
|
309
|
-
|
|
310
|
-
| `Typography.Text`
|
|
311
|
-
| `Typography.Heading` | Heading shorthand
|
|
771
|
+
| Component | Description |
|
|
772
|
+
| -------------------- | ---------------------------------------------------------------- |
|
|
773
|
+
| `Typography.Text` | Main text component — accepts `variant` prop from the full scale |
|
|
774
|
+
| `Typography.Heading` | Heading shorthand |
|
|
312
775
|
|
|
313
776
|
### Primitives
|
|
314
777
|
|
|
315
|
-
| Component
|
|
316
|
-
|
|
317
|
-
| `Box`
|
|
318
|
-
| `Row`
|
|
319
|
-
| `Stack`
|
|
320
|
-
| `Spacer`
|
|
321
|
-
| `Pressable`
|
|
322
|
-
| `AspectRatio` | Aspect-ratio container
|
|
778
|
+
| Component | Description |
|
|
779
|
+
| ------------- | --------------------------- |
|
|
780
|
+
| `Box` | NativeWind layout container |
|
|
781
|
+
| `Row` | Horizontal flex |
|
|
782
|
+
| `Stack` | Vertical flex |
|
|
783
|
+
| `Spacer` | Flexible space filler |
|
|
784
|
+
| `Pressable` | Animated press target |
|
|
785
|
+
| `AspectRatio` | Aspect-ratio container |
|
|
323
786
|
|
|
324
787
|
### Layouts
|
|
325
788
|
|
|
326
|
-
| Component
|
|
327
|
-
|
|
328
|
-
| `Container`
|
|
329
|
-
| `ScrollContainer`
|
|
330
|
-
| `GradientContainer`
|
|
331
|
-
| `PlainLayout`
|
|
789
|
+
| Component | Description |
|
|
790
|
+
| -------------------- | ------------------------------------- |
|
|
791
|
+
| `Container` | Padded layout container |
|
|
792
|
+
| `ScrollContainer` | Scrollable layout wrapper |
|
|
793
|
+
| `GradientContainer` | Container with gradient background |
|
|
794
|
+
| `PlainLayout` | Unstyled layout for custom screens |
|
|
332
795
|
| `TabNavigatorLayout` | Layout for tab-based screen structure |
|
|
333
796
|
|
|
334
797
|
### Views
|
|
335
798
|
|
|
336
|
-
| Component
|
|
337
|
-
|
|
338
|
-
| `ListViewV2`
|
|
339
|
-
| `FormViewV2`
|
|
340
|
-
| `AuthFormView`
|
|
799
|
+
| Component | Description |
|
|
800
|
+
| ---------------------- | ------------------------------------------------- |
|
|
801
|
+
| `ListViewV2` | Paginated list with filtering and empty states |
|
|
802
|
+
| `FormViewV2` | Dynamic form view with field management |
|
|
803
|
+
| `AuthFormView` | Authentication form layout |
|
|
341
804
|
| `Wizard` / `useWizard` | Multi-step wizard with guards and lifecycle hooks |
|
|
342
|
-
| `AppSplashView`
|
|
805
|
+
| `AppSplashView` | Branded splash screen with logo and loading state |
|
|
343
806
|
|
|
344
807
|
### Widgets
|
|
345
808
|
|
|
346
|
-
| Component
|
|
347
|
-
|
|
348
|
-
| `ScreenHeader`
|
|
349
|
-
| `MenuGrid` / `MenuCard` | Grid and card-based navigation menus
|
|
350
|
-
| `QuickActions`
|
|
351
|
-
| `PromoBanner`
|
|
352
|
-
| `PromoModal`
|
|
353
|
-
| `HomeHeader`
|
|
354
|
-
| `GeoPermissionCard`
|
|
355
|
-
| `TimeZoneSelector`
|
|
356
|
-
| `JSONViewer`
|
|
809
|
+
| Component | Description |
|
|
810
|
+
| ----------------------- | ----------------------------------------- |
|
|
811
|
+
| `ScreenHeader` | Composite screen header widget |
|
|
812
|
+
| `MenuGrid` / `MenuCard` | Grid and card-based navigation menus |
|
|
813
|
+
| `QuickActions` | Quick action icon grid |
|
|
814
|
+
| `PromoBanner` | Swipeable promotional carousel |
|
|
815
|
+
| `PromoModal` | Promotional modal overlay |
|
|
816
|
+
| `HomeHeader` | Home screen hero header |
|
|
817
|
+
| `GeoPermissionCard` | Geolocation permission request card |
|
|
818
|
+
| `TimeZoneSelector` | Timezone picker widget |
|
|
819
|
+
| `JSONViewer` | Collapsible JSON tree (debug / dev tools) |
|
|
357
820
|
|
|
358
821
|
---
|
|
359
822
|
|
|
@@ -374,7 +837,7 @@ setActiveTheme(MY_DARK_THEME);
|
|
|
374
837
|
|
|
375
838
|
// Inject CSS variables into a subtree
|
|
376
839
|
const themeStyle = vars(themeToVars(MY_DARK_THEME));
|
|
377
|
-
<View style={themeStyle}>{children}</View
|
|
840
|
+
<View style={themeStyle}>{children}</View>;
|
|
378
841
|
```
|
|
379
842
|
|
|
380
843
|
### Reading the theme in components
|
|
@@ -384,7 +847,9 @@ import { useTheme } from "@sincpro/mobile-ui/theme";
|
|
|
384
847
|
|
|
385
848
|
function PriceCard() {
|
|
386
849
|
const theme = useTheme();
|
|
387
|
-
return
|
|
850
|
+
return (
|
|
851
|
+
<View style={{ backgroundColor: theme.bg.card, borderColor: theme.border.default }} />
|
|
852
|
+
);
|
|
388
853
|
}
|
|
389
854
|
```
|
|
390
855
|
|
|
@@ -395,33 +860,33 @@ import { extendTheme } from "@sincpro/mobile-ui/theme";
|
|
|
395
860
|
|
|
396
861
|
// Deep-merges on top of the DS base theme
|
|
397
862
|
const MY_THEME = extendTheme({
|
|
398
|
-
accent:
|
|
399
|
-
primary:
|
|
863
|
+
accent: "#00C357",
|
|
864
|
+
primary: "#0B0B0B",
|
|
400
865
|
secondary: "#6B6963",
|
|
401
866
|
bg: {
|
|
402
867
|
page: "#FAFAFA",
|
|
403
868
|
card: "#FFFFFF",
|
|
404
869
|
},
|
|
405
870
|
text: {
|
|
406
|
-
accent: "#006E2E",
|
|
871
|
+
accent: "#006E2E", // text on accent-tinted surfaces
|
|
407
872
|
},
|
|
408
873
|
});
|
|
409
874
|
```
|
|
410
875
|
|
|
411
876
|
### ThemeTokens reference
|
|
412
877
|
|
|
413
|
-
| Group
|
|
414
|
-
|
|
415
|
-
| `primary / secondary / accent`
|
|
416
|
-
| `bg.*`
|
|
417
|
-
| `text.*`
|
|
418
|
-
| `icon.*`
|
|
419
|
-
| `border.*`
|
|
420
|
-
| `success / warning / danger / info` | Semantic state colors
|
|
421
|
-
| `*Light`
|
|
422
|
-
| `ring / input`
|
|
423
|
-
| `gradient.*`
|
|
424
|
-
| `shadow.*`
|
|
878
|
+
| Group | Keys |
|
|
879
|
+
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
880
|
+
| `primary / secondary / accent` | Main brand color scales |
|
|
881
|
+
| `bg.*` | `page, card, muted, accent, hover, disabled, popover` |
|
|
882
|
+
| `text.*` | `primary, secondary, tertiary, muted, accent, inverse, disabled, onPrimary, onSecondary, onAccent, onDanger, onSuccess` |
|
|
883
|
+
| `icon.*` | `primary, secondary, tertiary, inverse, disabled` |
|
|
884
|
+
| `border.*` | `default, light, strong, focus` |
|
|
885
|
+
| `success / warning / danger / info` | Semantic state colors |
|
|
886
|
+
| `*Light` | Tinted backgrounds for each state |
|
|
887
|
+
| `ring / input` | Focus ring and input border |
|
|
888
|
+
| `gradient.*` | `primary, accent` — arrays of color stops |
|
|
889
|
+
| `shadow.*` | `sm, md, lg` — React Native shadow objects |
|
|
425
890
|
|
|
426
891
|
---
|
|
427
892
|
|
|
@@ -445,16 +910,16 @@ import { Typography } from "@sincpro/mobile-ui/Typography";
|
|
|
445
910
|
<Typography.Text variant="button">Action Label</Typography.Text>
|
|
446
911
|
```
|
|
447
912
|
|
|
448
|
-
| Group
|
|
449
|
-
|
|
450
|
-
| Display
|
|
451
|
-
| Headings
|
|
452
|
-
| Body
|
|
453
|
-
| Labels
|
|
454
|
-
| Data (mono) | `data` `dataLarge`
|
|
455
|
-
| Meta (mono) | `overline` `caption` `captionSmall`
|
|
456
|
-
| Buttons
|
|
457
|
-
| Legacy
|
|
913
|
+
| Group | Variants |
|
|
914
|
+
| ----------- | ------------------------------------ |
|
|
915
|
+
| Display | `display` |
|
|
916
|
+
| Headings | `h1` `h2` `h3` `h4` `h5` `h6` |
|
|
917
|
+
| Body | `bodyLarge` `body` `bodySmall` |
|
|
918
|
+
| Labels | `label` `labelSmall` |
|
|
919
|
+
| Data (mono) | `data` `dataLarge` |
|
|
920
|
+
| Meta (mono) | `overline` `caption` `captionSmall` |
|
|
921
|
+
| Buttons | `button` `buttonSmall` `buttonLarge` |
|
|
922
|
+
| Legacy | `title` `subtitle` |
|
|
458
923
|
|
|
459
924
|
### FontRole
|
|
460
925
|
|
|
@@ -475,9 +940,9 @@ import { configureFonts, resetFonts } from "@sincpro/mobile-ui/theme/typography"
|
|
|
475
940
|
|
|
476
941
|
configureFonts({
|
|
477
942
|
titleRegular: "Satoshi-Regular",
|
|
478
|
-
titleMedium:
|
|
479
|
-
title:
|
|
480
|
-
titleBlack:
|
|
943
|
+
titleMedium: "Satoshi-Medium",
|
|
944
|
+
title: "Satoshi-Bold",
|
|
945
|
+
titleBlack: "Satoshi-Black",
|
|
481
946
|
});
|
|
482
947
|
|
|
483
948
|
// Reset to defaults — useful in tests and Storybook stories
|
|
@@ -486,15 +951,15 @@ resetFonts();
|
|
|
486
951
|
|
|
487
952
|
### Font size scale
|
|
488
953
|
|
|
489
|
-
| Token
|
|
490
|
-
|
|
491
|
-
| `xs`
|
|
492
|
-
| `sm`
|
|
493
|
-
| `base` | 14
|
|
494
|
-
| `lg`
|
|
495
|
-
| `xl`
|
|
496
|
-
| `2xl`
|
|
497
|
-
| `3xl`
|
|
954
|
+
| Token | `px` | Token | `px` |
|
|
955
|
+
| ------ | ---- | ----- | ---- |
|
|
956
|
+
| `xs` | 10 | `4xl` | 28 |
|
|
957
|
+
| `sm` | 13 | `5xl` | 32 |
|
|
958
|
+
| `base` | 14 | `6xl` | 36 |
|
|
959
|
+
| `lg` | 16 | `7xl` | 48 |
|
|
960
|
+
| `xl` | 18 | `8xl` | 64 |
|
|
961
|
+
| `2xl` | 20 | | |
|
|
962
|
+
| `3xl` | 24 | | |
|
|
498
963
|
|
|
499
964
|
### Custom text styles
|
|
500
965
|
|
|
@@ -506,7 +971,7 @@ const errorLabel = createTextStyle("labelSmall", { color: "#FF4D1F" });
|
|
|
506
971
|
|
|
507
972
|
// Fully custom
|
|
508
973
|
const amountStyle = createCustomTextStyle("4xl", "monoMedium", {
|
|
509
|
-
lineHeight:
|
|
974
|
+
lineHeight: "none",
|
|
510
975
|
letterSpacing: "tight",
|
|
511
976
|
});
|
|
512
977
|
```
|
|
@@ -521,22 +986,22 @@ Centralized animation constants so motion feels consistent across every screen
|
|
|
521
986
|
import { duration, easing, spring } from "@sincpro/mobile-ui/tokens";
|
|
522
987
|
|
|
523
988
|
// Durations (ms)
|
|
524
|
-
duration.instant
|
|
525
|
-
duration.fast
|
|
526
|
-
duration.normal
|
|
527
|
-
duration.slow
|
|
528
|
-
duration.slower
|
|
989
|
+
duration.instant; // 0 — immediate state swap, no transition
|
|
990
|
+
duration.fast; // 120 — micro-interactions (button press, toggle)
|
|
991
|
+
duration.normal; // 200 — standard transitions (modal, drawer)
|
|
992
|
+
duration.slow; // 320 — large layout shifts
|
|
993
|
+
duration.slower; // 480 — onboarding, emphasis animations
|
|
529
994
|
|
|
530
995
|
// Bezier easing curves
|
|
531
|
-
easing.standard
|
|
532
|
-
easing.decelerate
|
|
533
|
-
easing.accelerate
|
|
534
|
-
easing.emphasized
|
|
996
|
+
easing.standard; // general purpose
|
|
997
|
+
easing.decelerate; // elements entering the screen
|
|
998
|
+
easing.accelerate; // elements leaving the screen
|
|
999
|
+
easing.emphasized; // expressive, attention-drawing transitions
|
|
535
1000
|
|
|
536
1001
|
// Reanimated spring presets
|
|
537
|
-
spring.default
|
|
538
|
-
spring.gentle
|
|
539
|
-
spring.snappy
|
|
1002
|
+
spring.default; // balanced bounce
|
|
1003
|
+
spring.gentle; // smooth, no overshoot
|
|
1004
|
+
spring.snappy; // fast with slight bounce
|
|
540
1005
|
```
|
|
541
1006
|
|
|
542
1007
|
Usage with Reanimated:
|
|
@@ -546,7 +1011,7 @@ import { withTiming, withSpring } from "react-native-reanimated";
|
|
|
546
1011
|
import { duration, easing, spring } from "@sincpro/mobile-ui/tokens";
|
|
547
1012
|
|
|
548
1013
|
const opacity = withTiming(1, { duration: duration.normal, easing: easing.decelerate });
|
|
549
|
-
const scale
|
|
1014
|
+
const scale = withSpring(1, spring.snappy);
|
|
550
1015
|
```
|
|
551
1016
|
|
|
552
1017
|
---
|
|
@@ -577,19 +1042,15 @@ The preset ships the full token class set (`bg-primary`, `text-text-secondary`,
|
|
|
577
1042
|
```js
|
|
578
1043
|
module.exports = {
|
|
579
1044
|
presets: [require("@sincpro/mobile-ui/tailwind.preset")],
|
|
580
|
-
content: [
|
|
581
|
-
"./App.tsx",
|
|
582
|
-
"./src/**/*.{ts,tsx}",
|
|
583
|
-
"./node_modules/@sincpro/mobile-ui/**/*.js",
|
|
584
|
-
],
|
|
1045
|
+
content: ["./App.tsx", "./src/**/*.{ts,tsx}", "./node_modules/@sincpro/mobile-ui/**/*.js"],
|
|
585
1046
|
theme: {
|
|
586
1047
|
extend: {
|
|
587
1048
|
// Override font families to match loaded font names
|
|
588
1049
|
fontFamily: {
|
|
589
|
-
light:
|
|
590
|
-
regular:
|
|
591
|
-
medium:
|
|
592
|
-
semibold:
|
|
1050
|
+
light: ["Inter_300Light"],
|
|
1051
|
+
regular: ["Inter_400Regular"],
|
|
1052
|
+
medium: ["Inter_500Medium"],
|
|
1053
|
+
semibold: ["Inter_600SemiBold"],
|
|
593
1054
|
extrabold: ["Inter_800ExtraBold"],
|
|
594
1055
|
},
|
|
595
1056
|
},
|
|
@@ -614,13 +1075,13 @@ import "@sincpro/mobile-ui/theme/globals.css";
|
|
|
614
1075
|
Best tree-shaking; makes the component's category visible at the call site:
|
|
615
1076
|
|
|
616
1077
|
```tsx
|
|
617
|
-
import { Display }
|
|
618
|
-
import { Form }
|
|
619
|
-
import { Feedback }
|
|
1078
|
+
import { Display } from "@sincpro/mobile-ui/Display";
|
|
1079
|
+
import { Form } from "@sincpro/mobile-ui/Form";
|
|
1080
|
+
import { Feedback } from "@sincpro/mobile-ui/Feedback";
|
|
620
1081
|
import { Navigation } from "@sincpro/mobile-ui/Navigation";
|
|
621
|
-
import { Dialog }
|
|
1082
|
+
import { Dialog } from "@sincpro/mobile-ui/Dialog";
|
|
622
1083
|
import { Typography } from "@sincpro/mobile-ui/Typography";
|
|
623
|
-
import { Actions }
|
|
1084
|
+
import { Actions } from "@sincpro/mobile-ui/Actions";
|
|
624
1085
|
```
|
|
625
1086
|
|
|
626
1087
|
### Theme and tokens
|
|
@@ -636,18 +1097,18 @@ For components outside a namespace:
|
|
|
636
1097
|
|
|
637
1098
|
```tsx
|
|
638
1099
|
import Sheet from "@sincpro/mobile-ui/Dialog/Dialog.Sheet";
|
|
639
|
-
import { ConfirmationProvider }
|
|
640
|
-
import { ToastProvider, useToast }
|
|
641
|
-
import { Wizard, useWizard }
|
|
1100
|
+
import { ConfirmationProvider } from "@sincpro/mobile-ui/Dialog";
|
|
1101
|
+
import { ToastProvider, useToast } from "@sincpro/mobile-ui/Feedback";
|
|
1102
|
+
import { Wizard, useWizard } from "@sincpro/mobile-ui/views/Wizard";
|
|
642
1103
|
```
|
|
643
1104
|
|
|
644
1105
|
### Layout primitives
|
|
645
1106
|
|
|
646
1107
|
```tsx
|
|
647
|
-
import { Box, Row, Stack, Spacer }
|
|
648
|
-
import Container
|
|
649
|
-
import PlainLayout
|
|
650
|
-
import TabNavigatorLayout
|
|
1108
|
+
import { Box, Row, Stack, Spacer } from "@sincpro/mobile-ui/primitives";
|
|
1109
|
+
import Container from "@sincpro/mobile-ui/layouts/Container";
|
|
1110
|
+
import PlainLayout from "@sincpro/mobile-ui/layouts/PlainLayout";
|
|
1111
|
+
import TabNavigatorLayout from "@sincpro/mobile-ui/layouts/TabNavigatorLayout";
|
|
651
1112
|
```
|
|
652
1113
|
|
|
653
1114
|
---
|
|
@@ -663,26 +1124,35 @@ make storybook-android # expo run:android
|
|
|
663
1124
|
|
|
664
1125
|
Stories are organized into four groups:
|
|
665
1126
|
|
|
666
|
-
| Group
|
|
667
|
-
|
|
668
|
-
| **Foundations** | Tokens, typography scale, color palette, motion
|
|
669
|
-
| **Components**
|
|
670
|
-
| **Patterns**
|
|
671
|
-
| **Legacy**
|
|
1127
|
+
| Group | Contents |
|
|
1128
|
+
| --------------- | ------------------------------------------------------- |
|
|
1129
|
+
| **Foundations** | Tokens, typography scale, color palette, motion |
|
|
1130
|
+
| **Components** | All compound component namespaces |
|
|
1131
|
+
| **Patterns** | Composed examples (list screen, form screen, auth flow) |
|
|
1132
|
+
| **Legacy** | Deprecated components kept for backwards compatibility |
|
|
672
1133
|
|
|
673
1134
|
Stories live in `.rnstorybook/stories/**/*.stories.tsx`. The Storybook harness is excluded from the published package (`files: ["dist", ...]`) and never lands in consumer bundles.
|
|
674
1135
|
|
|
1136
|
+
UX is a cross-cutting quality, not another catalog category. Component states live with
|
|
1137
|
+
their component (`Actions/Button`, `Data Display/CopyableText`, `Inputs/Select`, `Feedback`);
|
|
1138
|
+
task compositions live under named `Patterns/Forms`, `Patterns/Records`,
|
|
1139
|
+
`Patterns/Collections`, `Patterns/Views` and `Patterns/Profiles`.
|
|
1140
|
+
The former `Patterns/UX`, top-level `Views` and `Templates` story IDs changed;
|
|
1141
|
+
package exports did not. The taxonomy is regression-tested.
|
|
1142
|
+
Stories that own a full-screen scroll set `parameters.ownsScroll = true`; the canvas does
|
|
1143
|
+
not wrap them in a second vertical scroll. Other canvases use keyboard-aware scrolling.
|
|
1144
|
+
|
|
675
1145
|
---
|
|
676
1146
|
|
|
677
1147
|
## Development
|
|
678
1148
|
|
|
679
|
-
| Command
|
|
680
|
-
|
|
681
|
-
| `make build`
|
|
682
|
-
| `make format`
|
|
683
|
-
| `make verify-format` | CI gate — runs `format`, fails if any file changed
|
|
684
|
-
| `make storybook`
|
|
685
|
-
| `make publish`
|
|
1149
|
+
| Command | What it does |
|
|
1150
|
+
| -------------------- | ------------------------------------------------------ |
|
|
1151
|
+
| `make build` | `tsc` + `tsc-alias` → `dist/` (JS + type declarations) |
|
|
1152
|
+
| `make format` | Auto-fix: ESLint + Prettier + type-check |
|
|
1153
|
+
| `make verify-format` | CI gate — runs `format`, fails if any file changed |
|
|
1154
|
+
| `make storybook` | Start Expo dev server for Storybook |
|
|
1155
|
+
| `make publish` | Bump version + `npm publish` |
|
|
686
1156
|
|
|
687
1157
|
After building, copy the dist to consumers in the local monorepo (no symlinks setup):
|
|
688
1158
|
|