# @agroshine/ags-mobile-ui-kit

[![npm version](https://img.shields.io/npm/v/@agroshine/ags-mobile-ui-kit.svg)](https://www.npmjs.com/package/@agroshine/ags-mobile-ui-kit)
[![license: MIT](https://img.shields.io/npm/l/@agroshine/ags-mobile-ui-kit.svg)](./LICENSE)

Librería de componentes **React Native** reutilizables de AgroShine. Es el
espejo móvil de [`@agroshine/ags-web-ui-kit`](../../Web/ags-web-ui-kit): misma
paleta, mismos tokens semánticos, misma filosofía AgroShine — adaptada al
stack RN.

---

## Tabla de contenidos

- [Stack](#stack)
- [Instalación](#instalación)
- [Setup en una app Expo / React Native](#setup-en-una-app-expo--react-native)
- [Setup en Storybook (web)](#setup-en-storybook-web)
- [Estructura del paquete](#estructura-del-paquete)
- [API pública](#api-pública)
  - [Atoms](#atoms)
  - [Molecules](#molecules)
  - [Organisms](#organisms)
  - [UI primitives](#ui-primitives-rn-primitives-style)
  - [Lib utils](#lib-utils)
- [Sistema de iconos](#sistema-de-iconos)
- [Toasts (Toaster)](#toasts-toaster)
- [Tokens y paleta](#tokens-y-paleta)
- [Scripts](#scripts)
- [Compatibilidad](#compatibilidad)
- [Publicación a npm](#publicación-a-npm)
- [Contribuir](#contribuir)
- [Licencia](#licencia)

---

## Stack

- **NativeWind 4** (sintaxis Tailwind v3) — clases utilitarias en React Native.
- **React Native Reusables** (`@rn-primitives/*`) — primitives accesibles
  (Radix-style) para RN.
- **lucide-react-native** — iconografía vectorial.
- **jotai** — estado del `Toaster`.
- **react-native-builder-bob 0.40** — empaqueta a CJS, ESM y `.d.ts`.
- **TypeScript** estricto, ESLint flat config, Prettier.

---

## Instalación

```bash
# pnpm
pnpm add @agroshine/ags-mobile-ui-kit

# yarn
yarn add @agroshine/ags-mobile-ui-kit

# npm
npm install @agroshine/ags-mobile-ui-kit
```

### Peer dependencies

El kit declara como peers (no las trae él mismo) las libs que tu app ya
debería estar usando. Instala las que falten:

```bash
pnpm add nativewind tailwindcss@^3 tailwind-merge clsx \
  class-variance-authority lucide-react-native \
  react-native-gesture-handler react-native-reanimated \
  react-native-safe-area-context react-native-svg \
  @rn-primitives/slot
```

`@rn-primitives/*` adicionales (`accordion`, `dialog`, `popover`, `select`,
`tabs`, `tooltip`, etc.) son **opcionales** — solo instala los que use el
componente que vayas a consumir.

`jotai` es opcional pero **requerido si vas a usar el `Toaster`** o el
`toastStore` del kit.

---

## Setup en una app Expo / React Native

### 1. `tailwind.config.js`

Asegúrate de incluir el dist del kit en `content` y aplicar tanto el preset
de NativeWind como el del kit:

```js
module.exports = {
  content: [
    './App.{js,jsx,ts,tsx}',
    './index.{js,jsx,ts,tsx}',
    './app/**/*.{js,jsx,ts,tsx}',
    './node_modules/@agroshine/ags-mobile-ui-kit/**/*.{js,ts,jsx,tsx}',
  ],
  presets: [
    require('nativewind/preset'),
    require('@agroshine/ags-mobile-ui-kit/tailwind.preset'),
  ],
};
```

### 2. `metro.config.js`

```js
const { getDefaultConfig } = require('expo/metro-config');
const { withNativeWind } = require('nativewind/metro');

const config = getDefaultConfig(__dirname);

module.exports = withNativeWind(config, {
  input: './global.css', // tu propio CSS app
});
```

### 3. `global.css` de la app

Re-exporta los tokens del kit (o cópialos) para que las variables HSL
estén disponibles:

```css
@import '@agroshine/ags-mobile-ui-kit/styles/global.css';

/* Y/o agrega tus propios overrides debajo. */
```

### 4. `babel.config.js`

```js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: [
      ['babel-preset-expo', { jsxImportSource: 'nativewind' }],
      'nativewind/babel',
    ],
  };
};
```

### 5. Provider raíz (opcional pero recomendado)

Si vas a usar `Toaster`, monta `SafeAreaProvider` en la raíz:

```tsx
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { Toaster } from '@agroshine/ags-mobile-ui-kit';

export default function App() {
  return (
    <SafeAreaProvider>
      {/* tu app */}
      <Toaster position="bottom" />
    </SafeAreaProvider>
  );
}
```

---

## Setup en Storybook (web)

El kit incluye configuración de Storybook 8 (React + Vite) en
`.storybook/` que renderiza componentes RN en el browser vía
`react-native-web`. Para arrancarlo localmente sobre el repo del kit:

```bash
pnpm storybook        # http://localhost:6006
pnpm build-storybook  # static build
```

Detalles del setup (alias `react-native` → `react-native-web`, stub para
`react-native-safe-area-context`, NativeWind via PostCSS) viven en
`.storybook/main.ts` y `.storybook/preview.tsx`. Si añades organisms que
toquen APIs nativas adicionales (TurboModules, codegen), recuerda
stubearlas en `.storybook/mocks/` y excluirlas de `optimizeDeps`.

---

## Estructura del paquete

```
src/
├── atoms/          Componentes opinionados AgroShine (Button, Input, …)
├── molecules/      Composiciones (FormField, Select, EntityCard, …)
├── organisms/      Composiciones de alto nivel (Layout, Drawer, Toaster, …)
├── ui/             Primitives rn-primitives-style (button, switch, …)
├── hooks/          Hooks compartidos (placeholder — en desarrollo)
├── lib/            Utilidades (cn, tailwindColors, toastStore)
├── foundation/     Catálogo visual (vacío — en desarrollo)
└── styles/
    └── global.css  Directivas Tailwind + tokens semánticos HSL
```

### Subpath exports

| Import path                                         | Contenido                       |
| --------------------------------------------------- | ------------------------------- |
| `@agroshine/ags-mobile-ui-kit`                      | Barrel raíz (atoms + molecules + organisms + lib + hooks) |
| `@agroshine/ags-mobile-ui-kit/atoms`                | Solo atoms                      |
| `@agroshine/ags-mobile-ui-kit/molecules`            | Solo molecules                  |
| `@agroshine/ags-mobile-ui-kit/organisms`            | Solo organisms                  |
| `@agroshine/ags-mobile-ui-kit/ui`                   | Primitives rn-primitives-style  |
| `@agroshine/ags-mobile-ui-kit/hooks`                | Hooks                           |
| `@agroshine/ags-mobile-ui-kit/lib`                  | `cn`, `tailwindColors`, toast atoms |
| `@agroshine/ags-mobile-ui-kit/tailwind.preset`      | Preset de Tailwind              |
| `@agroshine/ags-mobile-ui-kit/styles/global.css`    | Stylesheet base + tokens HSL    |

> Los primitives bajo `/ui` **no** se re-exportan desde el barrel raíz
> para evitar colisión con sus wrappers opinionados.

---

## API pública

### Atoms

| Componente   | Props clave                                                                 |
| ------------ | --------------------------------------------------------------------------- |
| `Button`     | `variant` (`primary` · `secondary` · `outline` · `ghost` · `danger` · `success` · `link`), `size` (`sm`/`md`/`lg`), `loading`, `disabled`, `fullWidth` |
| `IconButton` | Igual que `Button` + `icon: IconName`                                       |
| `Input`      | `label`, `helperText`, `error`, `containerClassName`, etc. (extiende `TextInputProps`) |
| `TextArea`   | Igual que `Input` + multi-línea                                             |
| `Badge`      | `tone` (`default` · `secondary` · `success` · `warning` · `danger` · `outline` · `muted`) |
| `Card`       | Wrapper + `CardHeader`, `CardContent`, `CardFooter`, `CardTitle`, `CardDescription` |
| `Divider`    | `orientation` (`horizontal`/`vertical`)                                     |
| `Icon`       | `name: IconName`, `size`, `className` (color por Tailwind)                  |
| `Snackbar`   | `variant` (`default` · `success` · `info` · `warning` · `error`), `message`, `actionLabel`, `onActionPress` |
| `Spinner`    | Wrapper de `ActivityIndicator`                                              |
| `Text`       | `variant` (`default` · `h1`..`h4` · `p` · `blockquote` · `code` · `lead` · `large` · `small` · `muted`) |

```tsx
import { Button, Input, Badge } from '@agroshine/ags-mobile-ui-kit';

<Button variant="primary" loading={isSubmitting} onPress={save}>
  Guardar
</Button>

<Input label="Correo" error={!!errors.email} helperText={errors.email} />

<Badge tone="success">Sincronizado</Badge>
```

### Molecules

| Componente             | Descripción                                                  |
| ---------------------- | ------------------------------------------------------------ |
| `ActionSheet`          | Bottom sheet modal con acciones                              |
| `ConfirmActionSheet`   | `ActionSheet` con CTA destructiva + cancelar                 |
| `CardButton`           | Card táctil con icono + label                                |
| `EmptyState`           | Estado vacío (icono + título + descripción + CTA)            |
| `EntityCard`           | Card para listas (avatar/icon + título + subtítulo + actions) |
| `FormField`            | Wrapper para inputs (`label`, `required`, `error`, `helperText`) |
| `ObservationCard`      | Card de observación con estado (`pending`/`synced`/`error`)  |
| `SearchInput`          | `Input` con icono de búsqueda + clear                        |
| `Select`               | Dropdown con búsqueda opcional (`options`, `searchable`)     |
| `SyncStatusBadge`      | Badge con estado de sync                                     |
| `TechnicalSheetButton` | Botón que abre el `TechnicalSheet` organism                  |

```tsx
import { FormField, Select } from '@agroshine/ags-mobile-ui-kit';

<FormField label="País" required error={errors.country}>
  <Select
    value={country}
    options={[
      { label: 'Chile', value: 'CL' },
      { label: 'Perú', value: 'PE' },
    ]}
    onSelect={setCountry}
    searchable
  />
</FormField>
```

### Organisms

| Componente        | Descripción                                                |
| ----------------- | ---------------------------------------------------------- |
| `Layout`          | Contenedor de pantalla (safe area + scroll opcional)       |
| `ScreenHeader`    | Header con back + título + acciones                        |
| `Breadcrumbs`     | Migas de pan                                               |
| `BottomMenu`      | Menú inferior con acciones (observación, ficha, crear, etc.) |
| `Drawer`          | Drawer lateral / modal sheet                               |
| `TechnicalSheet`  | Ficha técnica de Block/Ship (datos de cultivo)             |
| `Toaster`         | Stack de toasts global (consume `toastStore`)              |
| `QueryState`      | Renderiza loading / error / empty / data para queries      |

### UI primitives (rn-primitives-style)

Wrappers shadcn-style, sin opinión de marca. Útiles para construir
componentes nuevos:

`badge` · `button` · `card` · `icon` · `input` · `separator` · `skeleton`
· `text` · `textarea`

```tsx
import { Button, buttonVariants } from '@agroshine/ags-mobile-ui-kit/ui';

// con variants para construir tus propios componentes
<Pressable className={buttonVariants({ variant: 'outline', size: 'lg' })}>
  <Text>Custom</Text>
</Pressable>
```

### Lib utils

```ts
import { cn, addToastAtom, dismissToastAtom, toastsAtom }
  from '@agroshine/ags-mobile-ui-kit/lib';

// cn: clsx + tailwind-merge
<View className={cn('p-4', isActive && 'bg-primary-50', extraClass)} />
```

---

## Sistema de iconos

El atom `Icon` mapea nombres semánticos a iconos de `lucide-react-native`.
**Las pantallas y componentes propios deben usar `Icon` y nunca importar
`lucide-react-native` directamente** (esto centraliza el catálogo y permite
sustituir el set de iconos en el futuro).

```tsx
import { Icon } from '@agroshine/ags-mobile-ui-kit';

<Icon name="checkCircle" size={20} className="text-success" />
```

Nombres disponibles (extracto):
`alert` · `archive` · `arrowLeft` · `arrowRight` · `check` · `checkCircle` ·
`checkSquare` · `chevronDown` · `chevronLeft` · `chevronRight` ·
`chevronUp` · `circle` · `add` · `edit` · `eye` · `eyeOff` · `fileText` ·
`filter` · `home` · `info` · `list` · `listChecks` · `logout` · `menu` ·
`more` · `pencil` · `plus` · `refresh` · `save` · `search` · `send` ·
`settings` · `square` · `trash` · `upload` · `close`

El catálogo completo está en `src/atoms/Icon/icons.ts`. Si pasas un nombre
desconocido y `__DEV__` está activo verás un warning en consola; el render
cae a `menu` como fallback seguro.

---

## Toasts (Toaster)

El `Toaster` es un organism global respaldado por átomos de Jotai
(`toastStore`). Mismo enfoque shadcn/sonner pero RN-native.

```tsx
// App.tsx
import { Toaster } from '@agroshine/ags-mobile-ui-kit';

<Toaster position="bottom" visibleToasts={3} />
```

```tsx
// Cualquier pantalla
import { useSetAtom } from 'jotai';
import { addToastAtom } from '@agroshine/ags-mobile-ui-kit/lib';

const showToast = useSetAtom(addToastAtom);

showToast({
  message: 'Observación guardada',
  variant: 'success',
  duration: 4000, // opcional, default 4000
  actionLabel: 'Deshacer', // opcional
  onActionPress: () => undo(),
});
```

Variantes (heredadas de `Snackbar`): `default` · `success` · `info` ·
`warning` · `error`.

---

## Tokens y paleta

La paleta de marca (`default` · `primary` · `secondary` · `success` ·
`warning` · `danger` · `grey`, escala completa 50→900 + `DEFAULT` +
`foreground`, además de `content1`-`content4`, `focus`, `overlay`) es
**idéntica** a la del web kit (`ags-web-ui-kit/src/styles/tokens.css`).
Vive en `tailwind.preset.cjs` con sintaxis Tailwind v3 porque NativeWind 4
aún no soporta el `@theme` de v4.

Los **tokens semánticos shadcn-compatibles** (`--background`, `--border`,
`--ring`, `--muted`, `--accent`, `--popover`, `--card`, `--destructive`,
`--input`, `--radius`) viven en `src/styles/global.css` como variables HSL.
El soporte de `.dark:root` está activo — para alternarlo, agrega la clase
`dark` a la raíz desde tu app.

Acceso programático desde JS (útil para `style={}` o componentes que no
aceptan `className`):

```ts
import { tailwindColors } from '@agroshine/ags-mobile-ui-kit/lib';

<ActivityIndicator color={tailwindColors.primary.DEFAULT} />
```

---

## Scripts

| Comando                | Descripción                                                |
| ---------------------- | ---------------------------------------------------------- |
| `pnpm build`           | Compila con `react-native-builder-bob` (CJS + ESM + d.ts)  |
| `pnpm clean`           | Borra `lib/`                                               |
| `pnpm typecheck`       | `tsc --noEmit`                                             |
| `pnpm lint`            | ESLint sobre `src/`                                        |
| `pnpm lint:fix`        | ESLint con `--fix`                                         |
| `pnpm format`          | Prettier sobre `src/`                                      |
| `pnpm format:check`    | Prettier en modo check                                     |
| `pnpm storybook`       | Levanta Storybook web (vite + RN-Web) en `:6006`           |
| `pnpm build-storybook` | Build estático de Storybook                                |
| `pnpm commit`          | Commitizen interactivo (cz-customizable)                   |
| `pnpm release`         | semantic-release                                           |

---

## Compatibilidad

- **Node** ≥ 20
- **pnpm** ≥ 10 (es el package manager del kit; las apps consumidoras
  pueden usar yarn/npm)
- **React** 18 o 19
- **React Native** ≥ 0.73 (probado con 0.81.x)
- **Expo SDK** 50+ (NativeWind 4 requiere Reanimated 3+)
- **NativeWind** 4.x
- **Tailwind CSS** 3.4+

> ⚠️ El kit se construye y publica con `react-native-builder-bob 0.40`.
> Los tipos viven en `lib/typescript/{commonjs,module}/src/**`. Si forkeas
> el kit y cambias `bob`, verifica que el campo `exports.<sub>.import.types`
> y `.require.types` siga apuntando a archivos existentes (corre
> `pnpm build && npm pack --dry-run`).

---

## Publicación a npm

El paquete está bajo el scope público `@agroshine`. Para publicar:

```bash
pnpm typecheck    # debe pasar
pnpm lint         # debe pasar
pnpm build        # genera lib/
npm pack --dry-run  # revisa contenido del tarball

# bump de versión (semantic-release lo hace automático en CI):
npm version patch | minor | major

npm publish --access public
```

La automatización vía `pnpm release` usa `semantic-release` con changelog,
git tag, npm publish y GitHub release.

---

## Contribuir

1. Crea una rama desde `main`.
2. `pnpm install` (Node ≥ 20, pnpm ≥ 10).
3. Añade tu componente bajo el nivel correcto (`atoms` / `molecules` /
   `organisms`) con su `index.ts`, `Component.tsx`, `Component.types.ts` y
   `Component.stories.tsx`.
4. Re-exporta desde el barrel del nivel.
5. `pnpm typecheck && pnpm lint && pnpm build`.
6. `pnpm commit` para un commit Conventional via Commitizen.
7. Abre PR.

Convenciones:

- **Nunca importes** `lucide-react-native` directo desde un componente del
  kit — usa el atom `Icon` y agrega el icono al catálogo si hace falta.
- **Nunca importes desde pantallas de las apps** — el kit es self-contained.
- **Estilos solo vía `className`** (NativeWind). Evita `style={{ ... }}`
  salvo para valores no expresables en Tailwind.
- **Props con `Props` suffix** en sus types files (`ButtonProps`).
- Si tu componente necesita un primitive nuevo, agrégalo a `src/ui/`
  primero y luego envuélvelo en el atom/molecule/organism.

---

## Licencia

[MIT](./LICENSE) © AgroShine
