Skip to content

feat: add DatePicker and DatePickerModal components#77

Merged
colomfernando merged 7 commits into
masterfrom
APPSRN-523-agregar-componente-date-picker-modal-a-ui-native
Jun 22, 2026
Merged

feat: add DatePicker and DatePickerModal components#77
colomfernando merged 7 commits into
masterfrom
APPSRN-523-agregar-componente-date-picker-modal-a-ui-native

Conversation

@GonzaFran

@GonzaFran GonzaFran commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Link al ticket

Descripción del requerimiento

@janiscommerce/ui-native no tenía un componente nativo para selección de fechas y horas. Las apps que necesitaban un date picker tenían que
resolver por su cuenta la dependencia y el wrapping, sin una interfaz consistente ni theming aplicado.

Descripción de la solución

Se incorporaron dos componentes que envuelven react-native-date-picker (módulo nativo, peerDependency):

  • DatePicker: picker inline, siempre visible. Declarativo: emite cambios en tiempo real vía onDateChange.
  • DatePickerModal: picker en modal. Control imperativo vía ref (ref.current.open() / ref.current.close()), con callbacks onConfirm y
    onCancel.

Ambos comparten props base (mode, minimumDate, maximumDate, locale, minuteInterval, timeZoneOffsetInMinutes, theme) resueltas en
la función pura getSharedProps (src/components/atoms/DatePicker/utils/index.ts), que también aplica el theming por plataforma: tinte del
palette de Janis (palette.primary.main y palette.grey[300]) en los botones y divisores de Android, y prop theme ('light' | 'dark' | 'auto') en iOS (limitación de la librería — en iOS no expone buttonColor/dividerColor).

getSharedProps incluye una validación: si minimumDate > maximumDate, lanza console.warn. El prop date={null} hace que el picker
arranque en "hoy" sin emitir cambios hasta que el usuario interactúe.

Se removió @react-native-community/datetimepicker de dependencies (era una dependencia huérfana sin uso real en el código). Se agregó
react-native-date-picker >=5.0.0 como peerDependency.

Ambos componentes se exportan desde src/index.ts. El README incluye instrucciones de instalación (peerDep + pod install + rebuild) y notas
sobre el comportamiento de date={null}, la zona horaria y las limitaciones de Storybook web.

Nivel de pruebas requerido

Documentación y ejemplos

MARCAR NIVEL DESCRIPCIÓN CONDICIONES
[ ] CRÍTICO: Cambios Mayores Se consideran cambios que afectan partes fundamentales del sistema o funcionalidades que impactan múltiples
módulos de la app. Generar APK para pruebas. Utilizar perfil regular. Realizar pruebas exhaustivas en todos los módulos afectados.
[ ] ALTO: Cambios moderados Cambios que afectan aspectos importantes pero no críticos de la aplicación. Se puede probar localmente,
salvo que surja necesidad de crear un APK. Utilizar perfil regular para las pruebas.
[X] MEDIO: Cambios menores Cambios menores que impactan una parte del flujo o ajustes que no modifican la funcionalidad general.
Validación localmente. Se puede usar perfil dev
[ ] BAJO: Ajustes Modificaciones pequeñas que no impactan el rendimiento o funcionalidad general de la app. Validación localmente. Se
puede usar perfil dev

¿Cómo se puede probar?

Caso a probar Resultado esperado Resultado obtenido Observaciones
Renderizar DatePicker con date={null} El wheel muestra la fecha de hoy, sin emitir onDateChange hasta que el usuario mueva el wheel
pendiente -
Mover el wheel del DatePicker onDateChange se llama con la fecha seleccionada pendiente -
Llamar ref.current.open() en DatePickerModal El modal se abre mostrando el picker pendiente -
Confirmar una fecha en DatePickerModal onConfirm recibe el Date seleccionado y el modal se cierra pendiente -
Cancelar o tocar fuera del modal onCancel se ejecuta (si fue pasada) y el modal se cierra pendiente -
Pasar minimumDate > maximumDate Se loguea un console.warn y el picker renderiza igual pendiente Validación en getSharedProps

Evidencias, pruebas de cómo funciona

  • pendiente (probado en device en desarrollo)

Link a la documentación

Documentación y
ejemplos

MARCAR NIVEL DESCRIPCIÓN CONDICIONES
[ ] CRÍTICO: Cambios Mayores Se consideran cambios que afectan partes fundamentales del sistema o funcionalidades
que impactan múltiples módulos de la app. Generar APK para pruebas. Utilizar perfil regular. Realizar pruebas
exhaustivas en todos los módulos afectados.
[ ] ALTO: Cambios moderados Cambios que afectan aspectos importantes pero no críticos de la aplicación. Se puede
probar localmente, salvo que surja necesidad de crear un APK. Utilizar perfil regular para las pruebas.
[X] MEDIO: Cambios menores Cambios menores que impactan una parte del flujo o ajustes que no modifican la
funcionalidad general. Validación localmente. Se puede usar perfil dev
[ ] BAJO: Ajustes Modificaciones pequeñas que no impactan el rendimiento o funcionalidad general de la app.
Validación localmente. Se puede usar perfil dev

¿Cómo se puede probar?

| Caso a probar | Resultado esperado | Resultado obtenido | Observaciones |
| [ ] | ALTO: Cambios moderados | Cambios que afectan aspectos importantes pero no críticos de la aplicación. | Se puede probar localmente, salvo que surja necesidad de crear un APK. Utilizar perfil regular
para las pruebas. |
| [X] | MEDIO: Cambios menores | Cambios menores que impactan una parte del flujo o ajustes que no modifican la funcionalidad general. | Validación localmente. Se puede usar perfil dev |
| [ ] | BAJO: Ajustes | Modificaciones pequeñas que no impactan el rendimiento o funcionalidad general de la app. | Validación localmente. Se puede usar perfil dev |

¿Cómo se puede probar?

Caso a probar Resultado esperado Resultado obtenido Observaciones
Renderizar DatePicker con date={null} El wheel muestra la fecha de hoy, sin emitir onDateChange hasta que el usuario mueva el wheel pendiente -
Mover el wheel del DatePicker onDateChange se llama con la fecha seleccionada pendiente -
Llamar ref.current.open() en DatePickerModal El modal se abre mostrando el picker pendiente -
Confirmar una fecha en DatePickerModal onConfirm recibe el Date seleccionado y el modal se cierra pendiente -
Cancelar o tocar fuera del modal onCancel se ejecuta (si fue pasada) y el modal se cierra pendiente -
Pasar minimumDate > maximumDate Se loguea un console.warn y el picker renderiza igual pendiente Validación en getSharedProps

Evidencias, pruebas de cómo funciona

  • pendiente (probado en device en desarrollo)

Link a la documentación

Datos extra a tener en cuenta

  • El componente es un módulo nativo. El consumidor debe instalar react-native-date-picker como peerDependency en el repo destino.
  • El componente no renderiza en Storybook web (no hay implementación react-native-web en la librería). La story incluida funciona solo en el Storybook on-device (Android/iOS).
  • Se removió @react-native-community/datetimepicker de dependencies (estaba como dependencia directa sin usar).

CHANGELOG:

### Added
- `DatePicker` inline component wrapping `react-native-date-picker` with Janis palette theming [APPSRN-523](https://janiscommerce.atlassian.net/browse/APPSRN-523)
- `DatePickerModal` component with imperative ref API (`open`/`close`) and `onConfirm`/`onCancel` callbacks [APPSRN-523](https://janiscommerce.atlassian.net/browse/APPSRN-523)

### Removed
- Removed orphan `@react-native-community/datetimepicker` direct dependency [APPSRN-523](https://janiscommerce.atlassian.net/browse/APPSRN-523)

Add DatePicker (inline) and DatePickerModal (modal, controlled imperatively via ref) wrapping react-native-date-picker, with platform-aware palette theming and full test coverage. Also initialize OpenSpec in the repo and add the add-date-picker change with its proposal, design, specs and tasks.
Comment thread src/index.ts Outdated
import Collapsible from 'atoms/Collapsible';
import Modal from 'atoms/Modal';
import DatePicker from 'atoms/DatePicker/DatePicker';
import DatePickerModal from 'atoms/DatePicker/DatePickerModal';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fijate aca @GonzaFran que estamos "rompiendo" un poco la convencion que canda componente tiene su index y se imorta todo desde la estructura interna del coomnponente.

Lo que podmeos hacer es que DAtePciker tenga su index como el resto de componenentes y dicho index tendria

export { default as DatePicker } from './DatePicker';
export { default as DatePickerModal } from './DatePickerModal';

en el index general podes importar como

import { DatePicker, DatePickerModal } from 'atoms/DatePicker';
````

testID,
}: SharedDatePickerProps): RNDatePickerProps => {
if (minimumDate && maximumDate && minimumDate > maximumDate) {
console.warn('DatePicker: `minimumDate` is greater than `maximumDate`.');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aca agregaria que solo loguee con isDev

// buttonColor and dividerColor are Android-only props.
const androidTint = Platform.select({
android: {buttonColor: palette.primary.main, dividerColor: palette.grey[300]},
default: undefined,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

por algun motivo definimos por default undefined y no {} que seria el tipo de dato que devuelte android? IOS necesita undefined?

@GonzaFran GonzaFran requested a review from colomfernando June 19, 2026 20:40
@GonzaFran

Copy link
Copy Markdown
Contributor Author

@colomfernando listo los cambios!

@coveralls

coveralls commented Jun 19, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 27980519294

Coverage remained the same at 100.0%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: 26 of 26 lines across 3 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 786
Covered Lines: 786
Line Coverage: 100.0%
Relevant Branches: 723
Covered Branches: 723
Branch Coverage: 100.0%
Branches in Coverage %: Yes
Coverage Strength: 13.33 hits per line

💛 - Coveralls

Gonzalo and others added 4 commits June 21, 2026 01:46
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…SRN-523-agregar-componente-date-picker-modal-a-ui-native
…tive' of github.com:janis-commerce/ui-native into APPSRN-523-agregar-componente-date-picker-modal-a-ui-native

@colomfernando colomfernando left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GonzaFran te deje comentarios

import {palette} from 'theme/palette';
import {isDevEnv} from 'utils/index';

export type SharedDatePickerProps = Pick<

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

te comento lo mismo que deje en el de nico, me parece que los types no son utils. Podriamos tener el archivo types.ts en el root de Datepicker @GonzaFran

@@ -0,0 +1,2 @@
export {default as DatePicker} from './DatePicker';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

este archivo puede ser mejor ts, porque no tiene sintaxis de jsx

});

return {
date: date ?? new Date(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aca tenemos un problema a revisar. LA funcion getSharedProps se termina usando en ambos componentes. Esto quiere decir que si por algun motivo se dispara un render, la funcion se ejecuta de vuelta y va a generar un nuevo date.

Una solucion es que en cada componente tengas un useState que se inicia con new Date y seria el fallback si date no viene. De esa manera te aseguras que el componente tiene un solo new Data cuando se monta

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

corregido! pero en vez de un useState se usó un useMemo, porque el valor para date vive por fuera del componente, es del consumidor.

useImperativeHandle(ref, () => ({
open: () => setIsVisible(true),
close: () => setIsVisible(false),
}));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aca podriamos pasar el tercer argumentos deps vacias [], con esto evitamos que en cada render tengamos una referencia nueva del useImperativeHandle dado que es estable y no depende de cambios

close: () => setIsVisible(false),
}));

const handleConfirm = (selectedDate: Date) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tanto handleConfirm como handleCAncel podrimaos usar useCallback con su dep de handleConfirm y hancledCancel. Obviamente que el consumidor tiene que memoizar los handlers cuando lo pase por props y como nosotros somos los consumidores tenemos mas control.

De esta manera evitamos 2 renders por los handlers

@GonzaFran GonzaFran Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@colomfernando estuve revisando esto con Claude para aplicar el cambio y encontré que el componente de date-picker que usamos internamente (el DatePicker de la librería) no está memoizado, así que agregarle un useCallback a estas funciones no le aporta nada ni evita re-renders. El date-picker original no compara referencias y se vuelve a renderizar cada vez que nuestro componente se re-renderiza, le pasemos funciones estables o no.
Lo que sí voy a agregar es memoizar nuestros componentes (los que expone ui-native) para evitar que se re-rendericen en cada render del consumidor

Forward onStateChange, expose dividerColor/buttonColor with palette fallback, move types to types.ts and resolve the date fallback via useMemo. Memoize both components with displayName and add the project CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@GonzaFran GonzaFran requested a review from colomfernando June 22, 2026 20:05
@colomfernando colomfernando merged commit f5c5fdf into master Jun 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants