mobile-design 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  Componentes Material Design 3 para React Native y React Native Web. El paquete integra el tema, las fuentes y los componentes sobre React Native Paper.
6
6
 
7
- [Instalación](#instalación-desde-npm) · [Primer componente](#primer-componente) · [Proveedor](#configurar-el-proveedor) · [Temas y colores](#temas-y-colores) · [Iconos](#tipografía-e-iconos) · [Componentes](#referencia-de-componentes)
7
+ [Instalación](#instalación-desde-npm) · [Dependencias](#para-qué-sirve-cada-dependencia) · [Primer componente](#primer-componente) · [Proveedor](#configurar-el-proveedor) · [Temas y colores](#temas-y-colores) · [Stack](#tema-del-stack) · [Tipografía e iconos](#tipografía-e-iconos) · [Índice de componentes](#índice-de-componentes)
8
8
 
9
- La referencia detallada de propiedades, configuraciones y ejemplos se incluye como `COMPONENTS.md` en el paquete. [Referencia completa de componentes en la versión 0.1.1](https://cdn.jsdelivr.net/npm/mobile-design@0.1.1/COMPONENTS.md)
9
+ La referencia detallada de propiedades y ejemplos viaja con el paquete. Puedes abrir `node_modules/mobile-design/COMPONENTS.md` para consultar la versión instalada. [Referencia en este código fuente](COMPONENTS.md) · [Referencia de la versión publicada](https://cdn.jsdelivr.net/npm/mobile-design@latest/COMPONENTS.md)
10
10
 
11
11
  ## Instalación desde npm
12
12
 
@@ -31,6 +31,34 @@ npx expo start --web
31
31
 
32
32
  `mobile-design` instala `@mobile-design/tokens` como dependencia e incluye Roboto y los símbolos MD3 que necesitan los componentes. Con estas versiones se verificaron TypeScript, la exportación de Metro en Android, iOS y web, y el renderizado de fuentes y símbolos en Chromium con dependencias públicas.
33
33
 
34
+ ### Para qué sirve cada dependencia
35
+
36
+ Los *peers* forman parte de la configuración de la app. Los comandos anteriores fijan las versiones probadas. En una app Expo, el SDK ya determina las versiones de `react` y `react-native`.
37
+
38
+ | Paquete | Dónde se necesita | Qué aporta |
39
+ | --- | --- | --- |
40
+ | `mobile-design` | Android, iOS y web | Componentes, proveedor MD3, Roboto y los símbolos incluidos. |
41
+ | `@mobile-design/tokens` | Se instala con `mobile-design` | Paletas clara y oscura, tamaños, tipografía y movimiento. Se puede instalar solo si tu app consume únicamente los valores. |
42
+ | `expo` | App Expo o proyecto React Native con módulos Expo configurados | Integra los módulos Expo como fuentes e imágenes. Metro procesa el TypeScript que distribuye el paquete. |
43
+ | `react` | Peer común | Estado, hooks y composición de componentes. |
44
+ | `react-native` | Peer común | Vistas, estilos, accesibilidad y eventos. En web, Metro resuelve los hosts mediante React Native Web. |
45
+ | `react-native-paper` | Peer común | Tema base, iconos por nombre, dirección de lectura, portal y primitivas como `Avatar` y `TouchableRipple`. |
46
+ | `expo-font` | Peer común | Registra Roboto y la fuente de símbolos. También carga las familias que aporta tu app. |
47
+ | `expo-image` | Peer común | Renderiza las imágenes de `Carousel` y `FullScreenCarousel`. |
48
+ | `react-native-gesture-handler` | Peer común | Gestos de arrastre y desplazamiento en hojas, sliders, listas y otros controles. Monta `GestureHandlerRootView` en la raíz. |
49
+ | `react-native-reanimated` | Peer común | Valores compartidos, transiciones de estado y animaciones de los componentes. |
50
+ | `react-native-worklets` | Peer común | Ejecuta callbacks de gestos y animación en UI y comunica resultados al estado de React. |
51
+ | `react-native-safe-area-context` | Peer común | Proporciona los bordes seguros para barras y superficies superpuestas. Monta `SafeAreaProvider` en la raíz. |
52
+ | `react-native-svg` | Peer común | Dibuja indicadores de progreso y las rutas SVG de símbolos disponibles antes de cargar la fuente. |
53
+ | `@gorhom/bottom-sheet` | Peer común | Motor de `BottomSheet` y sus listas, scroll y campos de texto coordinados con el teclado. |
54
+ | `react-dom` | Peer de web | Monta el árbol de React en el DOM. |
55
+ | `react-native-web` | Peer de web | Traduce las primitivas React Native a elementos y eventos del navegador. |
56
+ | `@expo/metro-runtime` | App Expo con Metro en web | Runtime para los bundles web de Expo. Se instala en la app. |
57
+ | `@mobile-design/icons` | Opcional en las tres plataformas | Catálogo completo de Material Symbols Rounded. Instala la versión que corresponda a `mobile-design`. |
58
+ | `@expo/vector-icons` | Opcional en las tres plataformas | Proveedor alternativo si tu app elige, por ejemplo, Ionicons. |
59
+
60
+ ### Elegir la entrada del paquete
61
+
34
62
  | Entrada | Cuándo usarla |
35
63
  | --- | --- |
36
64
  | `mobile-design/react-native` | Android e iOS. |
@@ -89,18 +117,18 @@ export default function App() {
89
117
 
90
118
  Monta un `MobileDesignProvider` en la raíz para compartir el tema, la dirección de lectura y los portales. `GestureHandlerRootView` da soporte a los gestos y `SafeAreaProvider` permite a las barras y hojas respetar los bordes de la pantalla. Mantén ambos por fuera del proveedor.
91
119
 
92
- | Propiedad | Valores y comportamiento |
93
- | --- | --- |
94
- | `children` | Pantallas y componentes que consumen el tema y el host de `Portal`. |
95
- | `colorScheme` | `'light'` o `'dark'`. Si se omite, el proveedor sigue el esquema del sistema y usa claro cuando el sistema no declara modo oscuro. |
96
- | `theme` | Tema parcial de Paper. Fusiona por rol objetos como `colors`, `fonts`, `shapes`, `elevation` y `motion`. `theme.dark` explícito prevalece sobre `colorScheme`. |
97
- | `fontFamily` | Nombre de una familia registrada. Por defecto usa `'Roboto'`. Aplica la familia a la escala tipográfica y puedes ajustar un estilo concreto con `theme.fonts`. |
98
- | `languageHeight` | `'small'`, `'medium'`, `'large'` o `'extraLarge'`. El valor inicial es `'small'`. Cambia el interlineado de los estilos MD3 base y enfatizados. |
99
- | `motion` | `'expressive'` o `'standard'`. El valor inicial es `'expressive'`. Selecciona el perfil de los resortes espaciales. |
100
- | `reduceMotion` | `'auto'`, `'on'` u `'off'`. El valor inicial es `'auto'` y sigue la preferencia del sistema. |
101
- | `direction` | `'ltr'` o `'rtl'`. Si se omite, Paper usa la dirección del entorno. |
102
- | `settings.icon` | Renderizador para iconos pedidos por nombre. Por defecto usa los Material Symbols del paquete base. |
103
- | `settings.rippleEffectEnabled` | Habilita el ripple en los controles que consultan este ajuste. Paper lo habilita de forma predeterminada. |
120
+ | Propiedad | Valores y comportamiento | Ejemplo |
121
+ | --- | --- | --- |
122
+ | `children` | Pantallas y componentes que consumen el tema y el host de `Portal`. | `<MobileDesignProvider><Button>Guardar</Button></MobileDesignProvider>` |
123
+ | `colorScheme` | `'light'` o `'dark'`. Si se omite, el proveedor sigue el esquema del sistema y usa claro cuando el sistema no declara modo oscuro. | `colorScheme="dark"` para una vista previa fija. |
124
+ | `theme` | Tema parcial de Paper. Fusiona por rol objetos como `colors`, `fonts`, `shapes`, `elevation` y `motion`. `theme.dark` explícito prevalece sobre `colorScheme`. | `theme={{ fonts: { titleLarge: { fontWeight: '700' } } }}` para enfatizar los títulos grandes. |
125
+ | `fontFamily` | Nombre de una familia registrada. Por defecto usa `'Roboto'`. Aplica la familia a la escala tipográfica y puedes ajustar un estilo concreto con `theme.fonts`. | `fontFamily="Inter"` después de registrar Inter. |
126
+ | `languageHeight` | `'small'`, `'medium'`, `'large'` o `'extraLarge'`. El valor inicial es `'small'`. Cambia el interlineado de los estilos MD3 base y enfatizados. | `languageHeight="large"` para texto que requiera más altura de línea. |
127
+ | `motion` | `'expressive'` o `'standard'`. El valor inicial es `'expressive'`. Selecciona el perfil de los resortes espaciales. | `motion="standard"` para usar el perfil espacial estándar. |
128
+ | `reduceMotion` | `'auto'`, `'on'` u `'off'`. El valor inicial es `'auto'` y sigue la preferencia del sistema. | `reduceMotion="on"` para habilitar la reducción en una vista previa. |
129
+ | `direction` | `'ltr'` o `'rtl'`. Si se omite, Paper usa la dirección del entorno. | `direction="rtl"` para una interfaz en árabe. |
130
+ | `settings.icon` | Renderizador para iconos pedidos por nombre. Por defecto usa los Material Symbols del paquete base. | `settings={{ icon: renderMaterialIcon }}` con el renderizador de la colección opcional. |
131
+ | `settings.rippleEffectEnabled` | Habilita el ripple en los controles que consultan este ajuste. Paper lo habilita de forma predeterminada. | `settings={{ rippleEffectEnabled: false }}` para desactivar el efecto en esos controles. |
104
132
 
105
133
  El proveedor carga las fuentes predeterminadas que necesita el tema o el renderizador elegido. Antes de la primera carga completa puede mostrar el árbol vacío. Registra las caras y pesos de una fuente personalizada en tu app con `expo-font` antes de pasar su `fontFamily`.
106
134
 
@@ -223,39 +251,76 @@ export function createBrandTheme(mode: 'light' | 'dark') {
223
251
 
224
252
  Pasa `theme={createBrandTheme(mode)}` al proveedor, donde `mode` viene del sistema o de la preferencia guardada por tu app. Para ofrecer varios temas de marca, conserva un mapa de roles por marca y modo y usa la marca activa al construir el tema. `createMobileDesignColors` adapta roles existentes y espera un registro completo de tokens. En particular, `surfaceDisabled`, `onSurfaceDisabled` y `backdrop` son roles planos de compatibilidad que el adaptador de Paper no incorpora.
225
253
 
226
- En el workspace de desarrollo existe el comando `pnpm theme:generate '#246B57'`. Genera las dos paletas mediante Material Color Utilities y escribe el módulo de colores, sus tipos y el bloque CSS generado del workspace. `pnpm theme:generate md3-baseline` restaura la paleta MD3 publicada y `pnpm theme:generate neutral` genera el preset neutro. Estos comandos corresponden al código fuente del workspace. En una app que instala el paquete usa los roles de tokens, el exportador externo y las APIs de tema anteriores.
254
+ En el workspace de desarrollo existe el comando `pnpm theme:generate '#246B57'`. Genera las dos paletas mediante Material Color Utilities y escribe el módulo de colores y sus tipos dentro de @mobile-design/tokens. `pnpm theme:generate md3-baseline` restaura la paleta MD3 publicada y `pnpm theme:generate neutral` genera el preset neutro. Estos comandos corresponden al código fuente del workspace. En una app que instala el paquete usa los roles de tokens, el exportador externo y las APIs de tema anteriores.
255
+
256
+ ### Tema del stack
257
+
258
+ ```ts
259
+ import { DarkTheme, DefaultTheme } from '@react-navigation/native';
260
+ import { createMobileDesignTheme } from 'mobile-design/react-native';
261
+ import { adaptNavigationTheme } from 'react-native-paper';
262
+
263
+ export const { LightTheme: navLight, DarkTheme: navDark } = adaptNavigationTheme({
264
+ reactNavigationLight: DefaultTheme,
265
+ reactNavigationDark: DarkTheme,
266
+ materialLight: createMobileDesignTheme({ colorScheme: 'light' }),
267
+ materialDark: createMobileDesignTheme({ colorScheme: 'dark' })
268
+ });
269
+ ```
270
+
271
+ Dentro de `MobileDesignProvider`, usa `useMobileTheme().dark` para elegir el tema de `NavigationContainer`. Con `headerShown: false`, tu `TopAppBar` usa los colores y Roboto del proveedor. [Stack completo](examples/navigation-stack.tsx) con tres pantallas y un `ScreenLayout` reutilizable. Usa `<ScreenLayout title="Perfil">…</ScreenLayout>` para componer otra pantalla.
227
272
 
228
273
  ## Tipografía e iconos
229
274
 
230
- Roboto es la tipografía predeterminada. El proveedor usa un subconjunto de Material Symbols Rounded que cubre los controles y ejemplos de integración. El proveedor carga las fuentes predeterminadas que utiliza el tema o el renderizador activo. Para elegir otra tipografía, registra su familia y los pesos que utilices en tu app y pasa, por ejemplo, `fontFamily="Inter"`.
275
+ Para **Roboto y los símbolos incluidos**, basta `MobileDesignProvider`. El proveedor carga esas fuentes cuando se usan.
276
+
277
+ ### Incrustar fuentes con Expo
278
+
279
+ Usa el plugin si necesitas una fuente incrustada en Android o iOS, disponible desde el arranque. Añade la entrada a `expo.plugins` en `app.json` y recompila. Las rutas parten de la raíz de la app.
280
+
281
+ ```json
282
+ {
283
+ "expo": {
284
+ "plugins": [["expo-font", {
285
+ "android": { "fonts": [{
286
+ "fontFamily": "Inter",
287
+ "fontDefinitions": [
288
+ { "path": "./assets/fonts/Inter-Regular.ttf", "weight": 400 },
289
+ { "path": "./assets/fonts/Inter-Bold.ttf", "weight": 700 }
290
+ ]
291
+ }] },
292
+ "ios": { "fonts": ["./assets/fonts/Inter-Regular.ttf", "./assets/fonts/Inter-Bold.ttf"] }
293
+ }]]
294
+ }
295
+ }
296
+ ```
297
+
298
+ Selecciona `fontFamily="Inter"` en el proveedor. En iOS usa el nombre interno de la familia. Incluye los pesos y estilos que necesites.
299
+
300
+ ### Cargar una fuente propia al iniciar
231
301
 
232
- Agrupa las caras de una misma familia para que Expo pueda elegir el peso correcto. Coloca los archivos de tu fuente en la app y adapta las rutas del ejemplo. Monta `CustomTypeface` en el lugar que ocupa el proveedor en el primer ejemplo.
302
+ Usa `useFonts` para cargar la familia en ejecución, también en web.
233
303
 
234
304
  ```tsx
235
305
  import { useFonts } from 'expo-font';
236
306
  import { MobileDesignProvider, Text } from 'mobile-design/react-native';
237
307
 
238
- const fonts = [
239
- {
240
- fontFamily: 'Inter',
241
- fontDefinitions: [
242
- { path: require('./assets/fonts/Inter-Regular.ttf'), weight: 400 },
243
- { path: require('./assets/fonts/Inter-Bold.ttf'), weight: 700 },
244
- ],
245
- },
246
- ] satisfies Parameters<typeof useFonts>[0];
247
-
248
308
  export function CustomTypeface() {
249
- const [ready, error] = useFonts(fonts);
250
- if (error) throw error;
251
- if (!ready) return null;
252
-
253
- return (
254
- <MobileDesignProvider fontFamily="Inter">
255
- <Text variant="bodyLarge">Contenido</Text>
256
- <Text variant="titleLarge" style={{ fontWeight: '700' }}>Encabezado</Text>
257
- </MobileDesignProvider>
258
- );
309
+ const [ready, error] = useFonts([{
310
+ fontFamily: 'Inter',
311
+ fontDefinitions: [
312
+ { path: require('./assets/fonts/Inter-Regular.ttf'), weight: 400 },
313
+ { path: require('./assets/fonts/Inter-Bold.ttf'), weight: 700 },
314
+ ],
315
+ }]);
316
+ if (error) throw error;
317
+ if (!ready) return null;
318
+ return (
319
+ <MobileDesignProvider fontFamily="Inter">
320
+ <Text variant="bodyLarge">Contenido</Text>
321
+ <Text variant="titleLarge" style={{ fontWeight: '700' }}>Encabezado</Text>
322
+ </MobileDesignProvider>
323
+ );
259
324
  }
260
325
  ```
261
326
 
@@ -292,12 +357,16 @@ Los ejemplos se montan bajo el proveedor de la instalación. Importan desde `mob
292
357
 
293
358
  El archivo `node_modules/mobile-design/COMPONENTS.md` amplía la tabla de cada familia, las piezas relacionadas y los ejemplos completos.
294
359
 
295
- - [Acciones y botones](#acciones-y-botones). Button, IconButton, ButtonGroup, SegmentedButtons, SplitButton, FAB, FAB.Extended, FAB.Menu.
296
- - [Campos y selección](#campos-y-selección). TextField, Select, Autocomplete, Checkbox, RadioButton, Switch, Slider y RangeSlider, DatePicker y DockedDatePicker, TimePicker.
297
- - [Navegación y búsqueda](#navegación-y-búsqueda). TopAppBar, NavigationBar, NavigationRail, NavigationDrawer, Tabs y TabsPager, Searchbar y SearchView, Toolbar.
298
- - [Contenido y tipografía](#contenido-y-tipografía). Text, Badge y BadgedBox, Card, Carousel, Chip, Divider, List.
299
- - [Superficies y ayuda](#superficies-y-ayuda). Menu, Dialog y FullScreenDialog, BottomSheet, SideSheet, Snackbar, Tooltip y RichTooltip.
300
- - [Carga y progreso](#carga-y-progreso). LoadingIndicator y PullToRefresh, ProgressIndicator.
360
+ ## Índice de componentes
361
+
362
+ | Familia | Fichas |
363
+ | --- | --- |
364
+ | [Acciones y botones](#acciones-y-botones) | [`Button`](#button), [`IconButton`](#iconbutton), [`ButtonGroup`](#buttongroup), [`SegmentedButtons`](#segmentedbuttons), [`SplitButton`](#splitbutton), [`FAB`](#fab), [`FAB.Extended`](#fabextended), [`FAB.Menu`](#fabmenu) |
365
+ | [Campos y selección](#campos-y-selección) | [`TextField`](#textfield), [`Select`](#select), [`Autocomplete`](#autocomplete), [`Checkbox`](#checkbox), [`RadioButton`](#radiobutton), [`Switch`](#switch), [`Slider`](#slider-y-rangeslider), [`RangeSlider`](#slider-y-rangeslider), [`DatePicker`](#datepicker-y-dockeddatepicker), [`DockedDatePicker`](#datepicker-y-dockeddatepicker), [`TimePicker`](#timepicker) |
366
+ | [Navegación y búsqueda](#navegación-y-búsqueda) | [`TopAppBar`](#topappbar), [`NavigationBar`](#navigationbar), [`NavigationRail`](#navigationrail), [`NavigationDrawer`](#navigationdrawer), [`Tabs`](#tabs-y-tabspager), [`TabsPager`](#tabs-y-tabspager), [`Searchbar`](#searchbar-y-searchview), [`SearchView`](#searchbar-y-searchview), [`Toolbar`](#toolbar) |
367
+ | [Contenido y tipografía](#contenido-y-tipografía) | [`Text`](#text), [`Badge`](#badge-y-badgedbox), [`BadgedBox`](#badge-y-badgedbox), [`Card`](#card), [`Carousel`](#carousel), [`Chip`](#chip), [`Divider`](#divider), [`List`](#list) |
368
+ | [Superficies y ayuda](#superficies-y-ayuda) | [`Menu`](#menu), [`Dialog`](#dialog-y-fullscreendialog), [`FullScreenDialog`](#dialog-y-fullscreendialog), [`BottomSheet`](#bottomsheet), [`SideSheet`](#sidesheet), [`Snackbar`](#snackbar), [`Tooltip`](#tooltip-y-richtooltip), [`RichTooltip`](#tooltip-y-richtooltip) |
369
+ | [Carga y progreso](#carga-y-progreso) | [`LoadingIndicator`](#loadingindicator-y-pulltorefresh), [`PullToRefresh`](#loadingindicator-y-pulltorefresh), [`ProgressIndicator`](#progressindicator) |
301
370
 
302
371
  ### Acciones y botones
303
372
 
@@ -361,7 +430,7 @@ El componente usa `mode` y `sizeClass`. `mode="contained"` aplica Filled, `conta
361
430
 
362
431
  Reúne acciones relacionadas o alternativas de selección. Standard redistribuye el espacio al pulsar. Connected mantiene el grupo unido.
363
432
 
364
- Cada ítem puede tener icono y `selectedIcon`. `maxVisibleItems` limita cuántos se muestran y el resto aparece en el menú. Con selección múltiple el valor es un arreglo de claves.
433
+ Cada ítem puede tener `icon`. Con `showSelectedIcon`, el ítem seleccionado muestra una marca de verificación. `maxVisibleItems` limita cuántos se muestran y el resto aparece en el menú. Con selección múltiple el valor es un arreglo de claves.
365
434
 
366
435
  | Propiedad | Tipo | Inicial | Uso |
367
436
  | --- | --- | --- | --- |
@@ -461,58 +530,6 @@ Controla expanded y ciérralo desde onDismiss. Trigger define el FAB y su nombre
461
530
  | `expanded` · requerida | `boolean` | — | Estado controlado de expansión. |
462
531
  | `onDismiss` · requerida | `() => void` | — | Solicita cerrar la superficie. |
463
532
 
464
- **Ejemplo**
465
-
466
- ```tsx
467
- import { useState } from 'react';
468
- import { View } from 'react-native';
469
- import { FAB, Text } from 'mobile-design/react-native';
470
-
471
- export default function Example() {
472
- const [feedback, setFeedback] = useState('');
473
- const [expanded, setExpanded] = useState(false);
474
-
475
- return (
476
- <View style={{ gap: 16 }}>
477
- <View
478
- style={{
479
- minHeight: 280,
480
- justifyContent: 'flex-end',
481
- alignItems: 'flex-end',
482
- }}
483
- >
484
- <FAB.Menu
485
- presentation="expressive"
486
- expanded={expanded}
487
- onDismiss={() => setExpanded(false)}
488
- trigger={{
489
- icon: 'plus',
490
- size: 'medium',
491
- accessibilityLabel: 'Crear',
492
- onPress: () => setExpanded(true),
493
- }}
494
- items={[
495
- {
496
- key: 'note',
497
- label: 'Nota',
498
- icon: 'pencil-outline',
499
- onPress: () => setFeedback('Nueva nota'),
500
- },
501
- {
502
- key: 'folder',
503
- label: 'Carpeta',
504
- icon: 'folder-outline',
505
- onPress: () => setFeedback('Nueva carpeta'),
506
- },
507
- ]}
508
- />
509
- </View>
510
- <Text accessibilityLiveRegion="polite">{feedback}</Text>
511
- </View>
512
- );
513
- }
514
- ```
515
-
516
533
  ### Campos y selección
517
534
 
518
535
  #### TextField
@@ -525,6 +542,7 @@ El contador muestra la longitud. `maxLength` establece el límite. El ref `TextF
525
542
  | --- | --- | --- | --- |
526
543
  | `value` · requerida | `string` | — | Valor actual controlado por la pantalla. |
527
544
  | `onChangeText` · requerida | `(value: string) => void` | — | Comunica el nuevo texto para actualizar el valor controlado. |
545
+ | `size` | `"standard" \| "compact"` | `'standard'` | Altura base de una línea de 56 dp con `standard` o 48 dp con `compact`, en Filled y Outlined. Respeta el escalado del texto. |
528
546
  | `variant` | `"filled" \| "outlined"` | — | Tratamiento visual o modalidad del componente. |
529
547
  | `error` | `boolean` | — | Activa el tratamiento visual y accesible de error. |
530
548
 
@@ -539,6 +557,7 @@ export default function Example() {
539
557
 
540
558
  return (
541
559
  <TextField
560
+ size="compact"
542
561
  variant="outlined"
543
562
  label="Nombre de la colección"
544
563
  value={name}
@@ -844,61 +863,6 @@ Para deslizar entre paneles comparte `value`, `position` y la solicitud de selec
844
863
  | `onValueChange` · requerida | `(key: string) => void` | — | Entrega el siguiente valor para actualizar el estado del consumidor. |
845
864
  | `variant` | `"primary" \| "secondary"` | `'primary'` | Tratamiento visual o modalidad del componente. |
846
865
 
847
- **Ejemplo**
848
-
849
- ```tsx
850
- import { useState } from 'react';
851
- import { View } from 'react-native';
852
- import { useSharedValue } from 'react-native-reanimated';
853
- import { Tabs, TabsPager, Text } from 'mobile-design/react-native';
854
-
855
- export default function Example() {
856
- const [value, setValue] = useState('files');
857
- const [request, setRequest] = useState(0);
858
- const position = useSharedValue(0);
859
- const items = [
860
- {
861
- key: 'files',
862
- label: 'Archivos',
863
- icon: 'folder',
864
- nativeID: 'library-files-tab',
865
- panelID: 'library-files-panel',
866
- },
867
- {
868
- key: 'recent',
869
- label: 'Recientes',
870
- icon: 'schedule',
871
- nativeID: 'library-recent-tab',
872
- panelID: 'library-recent-panel',
873
- },
874
- ];
875
-
876
- return (
877
- <View style={{ flex: 1 }}>
878
- <Tabs
879
- items={items}
880
- value={value}
881
- onValueChange={setValue}
882
- position={position}
883
- onTabPress={() => setRequest((value) => value + 1)}
884
- />
885
- <TabsPager
886
- items={items}
887
- value={value}
888
- onValueChange={setValue}
889
- position={position}
890
- selectionRequest={request}
891
- renderPage={(item) => (
892
- <View style={{ padding: 24 }}>
893
- <Text>{item.label}</Text>
894
- </View>
895
- )}
896
- />
897
- </View>
898
- );
899
- }
900
- ```
901
-
902
866
  #### Searchbar y SearchView
903
867
 
904
868
  Conecta una barra de búsqueda con una vista que presenta consulta, sugerencias y resultados.
@@ -1065,37 +1029,6 @@ Usa `List.Accordion` con `title`, `expanded` y `onPress`. Configura las acciones
1065
1029
  | `variant` | `"expressive" \| "baseline"` | `'expressive'` | Tratamiento visual o modalidad del componente. |
1066
1030
  | `selectionMode` | `"single" \| "multiple"` | — | Define si la selección es inexistente, única o múltiple. |
1067
1031
 
1068
- **Ejemplo**
1069
-
1070
- ```tsx
1071
- import { useState } from 'react';
1072
- import { View } from 'react-native';
1073
- import { List, IconButton, Text } from 'mobile-design/react-native';
1074
-
1075
- export default function Example() {
1076
- const [feedback, setFeedback] = useState('');
1077
-
1078
- return (
1079
- <View style={{ gap: 16 }}>
1080
- <List.Item
1081
- title="Informe trimestral"
1082
- description="Documento compartido"
1083
- lines={2}
1084
- onPress={() => setFeedback('Detalle del documento')}
1085
- trailing={
1086
- <IconButton
1087
- icon="bookmark-outline"
1088
- accessibilityLabel="Guardar Informe trimestral"
1089
- onPress={() => setFeedback('Guardado Informe trimestral')}
1090
- />
1091
- }
1092
- />
1093
- <Text accessibilityLiveRegion="polite">{feedback}</Text>
1094
- </View>
1095
- );
1096
- }
1097
- ```
1098
-
1099
1032
  ### Superficies y ayuda
1100
1033
 
1101
1034
  #### Menu
@@ -1113,48 +1046,6 @@ Controla visible y onDismiss y proporciona un anchor que abra el menú.
1113
1046
  | `onDismiss` | `(() => void)` | — | Solicita cerrar la superficie. |
1114
1047
  | `onClosed` | `(() => void)` | — | Avisa cuando concluye la salida de la superficie. |
1115
1048
 
1116
- **Ejemplo**
1117
-
1118
- ```tsx
1119
- import { useState } from 'react';
1120
- import { View } from 'react-native';
1121
- import { Menu, Button, Text } from 'mobile-design/react-native';
1122
-
1123
- export default function Example() {
1124
- const [feedback, setFeedback] = useState('');
1125
- const [visible, setVisible] = useState(false);
1126
-
1127
- return (
1128
- <View style={{ gap: 16 }}>
1129
- <Menu
1130
- visible={visible}
1131
- onDismiss={() => setVisible(false)}
1132
- anchor={
1133
- <Button
1134
- variant="tonal"
1135
- onPress={() => setVisible(true)}
1136
- >
1137
- Opciones
1138
- </Button>
1139
- }
1140
- >
1141
- <Menu.Item
1142
- title="Guardar"
1143
- leadingIcon="bookmark-outline"
1144
- onPress={() => setFeedback('Guardado')}
1145
- />
1146
- <Menu.Item
1147
- title="Enviar"
1148
- leadingIcon="send-outline"
1149
- onPress={() => setFeedback('Enviar')}
1150
- />
1151
- </Menu>
1152
- <Text accessibilityLiveRegion="polite">{feedback}</Text>
1153
- </View>
1154
- );
1155
- }
1156
- ```
1157
-
1158
1049
  #### Dialog y FullScreenDialog
1159
1050
 
1160
1051
  Presenta información o una tarea que necesita atención antes de volver al contenido anterior.
@@ -1183,47 +1074,6 @@ Monta `GestureHandlerRootView` como se muestra en el ejemplo de instalación. Us
1183
1074
  | `variant` | `"standard" \| "modal"` | `'modal'` | `standard` comparte el layout y `modal` presenta una hoja superpuesta. |
1184
1075
  | `snapPoints` | `(string \| number)[]` | — | Posiciones de reposo de la hoja. Admite puntos en dp o porcentajes según el tipo. |
1185
1076
 
1186
- **Ejemplo**
1187
-
1188
- ```tsx
1189
- import { useState } from 'react';
1190
- import {
1191
- BottomSheet,
1192
- BottomSheetScrollView,
1193
- Text,
1194
- Button,
1195
- } from 'mobile-design/react-native';
1196
-
1197
- export default function Example() {
1198
- const [open, setOpen] = useState(false);
1199
-
1200
- return (
1201
- <>
1202
- <Button onPress={() => setOpen(true)}>Ver detalles</Button>
1203
- <BottomSheet
1204
- open={open}
1205
- onOpenChange={setOpen}
1206
- variant="modal"
1207
- scrollable
1208
- snapPoints={['50%', '85%']}
1209
- accessibilityLabel="Detalles de la colección"
1210
- >
1211
- <BottomSheetScrollView contentContainerStyle={{ padding: 24 }}>
1212
- <Text variant="headlineSmall">Tu colección</Text>
1213
- <Text>Información adicional de la colección guardada.</Text>
1214
- <Button
1215
- variant="text"
1216
- onPress={() => setOpen(false)}
1217
- >
1218
- Cerrar
1219
- </Button>
1220
- </BottomSheetScrollView>
1221
- </BottomSheet>
1222
- </>
1223
- );
1224
- }
1225
- ```
1226
-
1227
1077
  #### SideSheet
1228
1078
 
1229
1079
  Muestra una tarea complementaria desde un borde lateral, como parte del layout o en una superficie modal.
@@ -0,0 +1,132 @@
1
+ import { mobileDesignTokens } from '@mobile-design/tokens';
2
+ import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native';
3
+ import { createNativeStackNavigator, type NativeStackScreenProps } from '@react-navigation/native-stack';
4
+ import type { PropsWithChildren } from 'react';
5
+ import { ScrollView, StatusBar, StyleSheet, View } from 'react-native';
6
+ import { GestureHandlerRootView } from 'react-native-gesture-handler';
7
+ import { adaptNavigationTheme } from 'react-native-paper';
8
+ import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
9
+ import { Button, MobileDesignProvider, Text, TopAppBar, createMobileDesignTheme, useMobileTheme } from 'mobile-design/react-native';
10
+
11
+ type RootStackParamList = {
12
+ SignIn: undefined;
13
+ SignUp: undefined;
14
+ ForgotPassword: undefined;
15
+ };
16
+
17
+ type ScreenProps<Route extends keyof RootStackParamList> = NativeStackScreenProps<RootStackParamList, Route>;
18
+
19
+ const Stack = createNativeStackNavigator<RootStackParamList>();
20
+
21
+ const { LightTheme: navigationLightTheme, DarkTheme: navigationDarkTheme } = adaptNavigationTheme({
22
+ reactNavigationLight: DefaultTheme,
23
+ reactNavigationDark: DarkTheme,
24
+ materialLight: createMobileDesignTheme({ colorScheme: 'light', fontFamily: 'Roboto' }),
25
+ materialDark: createMobileDesignTheme({ colorScheme: 'dark', fontFamily: 'Roboto' }),
26
+ });
27
+
28
+ type ScreenLayoutProps = PropsWithChildren<{
29
+ title: string;
30
+ heading?: string;
31
+ description?: string;
32
+ onBack?: () => void;
33
+ scroll?: boolean;
34
+ }>;
35
+
36
+ export function ScreenLayout({ title, heading, description, onBack, scroll = true, children }: ScreenLayoutProps) {
37
+ const { colors } = useMobileTheme();
38
+ const content = (
39
+ <>
40
+ {heading ? <Text variant="headlineSmall" accessibilityRole="header">{heading}</Text> : null}
41
+ {description ? <Text variant="bodyMedium" style={{ color: colors.onSurfaceVariant }}>{description}</Text> : null}
42
+ {children}
43
+ </>
44
+ );
45
+
46
+ return (
47
+ <View style={[styles.fill, { backgroundColor: colors.surface }]}>
48
+ <TopAppBar
49
+ title={title}
50
+ navigationIcon={onBack ? 'arrow_back' : undefined}
51
+ navigationLabel="Volver"
52
+ onNavigationPress={onBack}
53
+ />
54
+ <SafeAreaView style={styles.fill} edges={['left', 'right', 'bottom']}>
55
+ {scroll ? <ScrollView contentContainerStyle={styles.content}>{content}</ScrollView> : content}
56
+ </SafeAreaView>
57
+ </View>
58
+ );
59
+ }
60
+
61
+ function SignInScreen({ navigation }: ScreenProps<'SignIn'>) {
62
+ return (
63
+ <ScreenLayout title="Iniciar sesión" heading="Bienvenido" description="Elige cómo continuar con tu cuenta.">
64
+ <Button variant="filled" onPress={() => navigation.navigate('SignUp')}>Crear cuenta</Button>
65
+ <Button variant="text" onPress={() => navigation.navigate('ForgotPassword')}>Olvidé mi contraseña</Button>
66
+ </ScreenLayout>
67
+ );
68
+ }
69
+
70
+ function SignUpScreen({ navigation }: ScreenProps<'SignUp'>) {
71
+ return (
72
+ <ScreenLayout
73
+ title="Crear cuenta"
74
+ description="Aquí puedes integrar los pasos de registro de tu app."
75
+ onBack={() => navigation.goBack()}
76
+ />
77
+ );
78
+ }
79
+
80
+ function ForgotPasswordScreen({ navigation }: ScreenProps<'ForgotPassword'>) {
81
+ return (
82
+ <ScreenLayout
83
+ title="Recuperar contraseña"
84
+ description="Aquí puedes integrar la solicitud del código y el cambio de contraseña."
85
+ onBack={() => navigation.goBack()}
86
+ />
87
+ );
88
+ }
89
+
90
+ function RootNavigator() {
91
+ const { animation, colors, dark } = useMobileTheme();
92
+ const navigationTheme = dark ? navigationDarkTheme : navigationLightTheme;
93
+
94
+ return (
95
+ <NavigationContainer theme={navigationTheme}>
96
+ <StatusBar barStyle={dark ? 'light-content' : 'dark-content'} />
97
+ <Stack.Navigator
98
+ initialRouteName="SignIn"
99
+ screenOptions={{
100
+ headerShown: false,
101
+ animation: animation.scale === 0 ? 'none' : 'default',
102
+ contentStyle: { backgroundColor: colors.surface },
103
+ }}
104
+ >
105
+ <Stack.Screen name="SignIn" component={SignInScreen} />
106
+ <Stack.Screen name="SignUp" component={SignUpScreen} />
107
+ <Stack.Screen name="ForgotPassword" component={ForgotPasswordScreen} />
108
+ </Stack.Navigator>
109
+ </NavigationContainer>
110
+ );
111
+ }
112
+
113
+ export default function App() {
114
+ return (
115
+ <GestureHandlerRootView style={styles.fill}>
116
+ <SafeAreaProvider>
117
+ <MobileDesignProvider fontFamily="Roboto">
118
+ <RootNavigator />
119
+ </MobileDesignProvider>
120
+ </SafeAreaProvider>
121
+ </GestureHandlerRootView>
122
+ );
123
+ }
124
+
125
+ const styles = StyleSheet.create({
126
+ fill: { flex: 1 },
127
+ content: {
128
+ gap: mobileDesignTokens.spacing.small,
129
+ paddingHorizontal: mobileDesignTokens.spacing.medium,
130
+ paddingVertical: mobileDesignTokens.spacing.large,
131
+ },
132
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobile-design",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Material Design 3 components for React Native and React Native Web, built on React Native Paper",
5
5
  "keywords": [
6
6
  "material-design",
@@ -32,13 +32,14 @@
32
32
  "assets/logo.svg",
33
33
  "README.md",
34
34
  "COMPONENTS.md",
35
+ "examples/navigation-stack.tsx",
35
36
  "LICENSE",
36
37
  "NOTICE",
37
38
  "LICENSE-Apache-2.0.txt"
38
39
  ],
39
40
  "sideEffects": false,
40
41
  "dependencies": {
41
- "@mobile-design/tokens": "0.1.1"
42
+ "@mobile-design/tokens": "0.1.3"
42
43
  },
43
44
  "peerDependencies": {
44
45
  "react": ">=19.2.0",
@@ -62,5 +63,10 @@
62
63
  "react-native-web": {
63
64
  "optional": true
64
65
  }
66
+ },
67
+ "repository": {
68
+ "type": "git",
69
+ "url": "https://github.com/nicolas-rm/mobile-design.git",
70
+ "directory": "library/packages/mobile-design"
65
71
  }
66
72
  }