# OptionWheel

> Organism — orn-ui

> ⚠️ Requiere <UIProvider>: Este componente (como todos los de orn-ui) debe renderizarse dentro de un ancestro <UIProvider>, o lanza un error en runtime. See https://orn-ui-docs.vercel.app/getting-started.md

> Corre en Expo SDK 54, 55, 56 y 57, y en React Native puro >=0.81 con react >=19.1. Sin módulos nativos — funciona en Expo Go, sin prebuild.

## Instalación

Instala solo este componente (copia el código fuente a tu proyecto, sin dependencia npm)
```bash
npx orn-ui add option-wheel
```

O instala el paquete completo e impórtalo
```tsx
pnpm add orn-ui
import { OptionWheel } from 'orn-ui/option-wheel';
```

Depende de: core (theme + icons), transition

## Variantes

### default — five rows, the centre one is the pick

```tsx
<SizeWheel label="Size" options={SIZES} />
```

### disabled options — the wheel slides off them on its own

```tsx
<SizeWheel label="In stock" options={STOCK} />
```

### unit and visibleCount={3} — a compact wheel

```tsx
<MinuteWheel />
```

### perspective={false} — size and fade, no tilt

```tsx
<FlatWheel />
```

### disabled — the whole wheel is frozen

```tsx
<LockedWheel />
```

### inside a Card, named for screen readers

```tsx
<InCard />
```

### variant='spotlight' — no band, the sharp row is the pick

```tsx
<PortfolioWheel />
```

## Código completo del demo

```tsx
function PortfolioWheel() {
  const [bucket, setBucket] = useState('stocks');
  const colors = useColors();
  const current = PORTFOLIO.find((option) => option.value === bucket);

  return (
    <View style={{ gap: 8 }}>
      <View
        style={{
          backgroundColor: colors.surface,
          borderRadius: 24,
          borderWidth: 1,
          borderColor: colors.border,
          overflow: 'hidden',
          paddingVertical: 12,
        }}
      >
        <OptionWheel
          accessibilityLabel="Portfolio bucket"
          options={PORTFOLIO}
          selectedValue={bucket}
          onSelect={setBucket}
          variant="spotlight"
          textColor={colors.text}
          curveRadius={CURVE}
          perspective={false}
          visibleCount={ROWS}
          itemHeight={ROW}
        />
      </View>
      <Body>Allocating to: {current?.label ?? '—'}</Body>
    </View>
  );
}

function SizeWheel({ label, options }: { label: string; options: OptionWheelOption<string>[] }) {
  const [size, setSize] = useState('m');
  const current = options.find((option) => option.value === size);

  return (
    <View style={{ gap: 8 }}>
      <OptionWheel label={label} options={options} selectedValue={size} onSelect={setSize} />
      <Body>Picked: {current?.label ?? '—'}</Body>
    </View>
  );
}

function MinuteWheel() {
  const [minutes, setMinutes] = useState(15);

  return (
    <View style={{ gap: 8 }}>
      <OptionWheel
        label="Remind me in"
        options={MINUTES}
        selectedValue={minutes}
        onSelect={setMinutes}
        unit="min"
        visibleCount={3}
      />
      <Body>Reminder in {minutes} minutes</Body>
    </View>
  );
}

function FlatWheel() {
  const [size, setSize] = useState('l');
  const current = SIZES.find((option) => option.value === size);

  return (
    <View style={{ gap: 8 }}>
      <OptionWheel
        label="No tilt"
        options={SIZES}
        selectedValue={size}
        onSelect={setSize}
        perspective={false}
      />
      <Body>Flat pick: {current?.label ?? '—'}</Body>
    </View>
  );
}

function LockedWheel() {
  const [size, setSize] = useState('m');
  const current = SIZES.find((option) => option.value === size);

  return (
    <View style={{ gap: 8 }}>
      <OptionWheel
        label="Locked"
        options={SIZES}
        selectedValue={size}
        onSelect={setSize}
        disabled
      />
      <Body>Locked on: {current?.label ?? '—'}</Body>
    </View>
  );
}

function InCard() {
  const [size, setSize] = useState('s');
  const current = SIZES.find((option) => option.value === size);

  return (
    <Card>
      <Subtitle>Bag size</Subtitle>
      <Body style={{ marginTop: 4, marginBottom: 12 }}>Ships free over 2 kg</Body>
      <OptionWheel
        accessibilityLabel="Bag size"
        options={SIZES}
        selectedValue={size}
        onSelect={setSize}
        visibleCount={3}
      />
      <Body style={{ marginTop: 8 }}>Bag: {current?.label ?? '—'}</Body>
    </Card>
  );
}

export function OptionWheelDemo() {
  const variants: VariantDef[] = [
    {
      label: 'default — five rows, the centre one is the pick',
      content: <SizeWheel label="Size" options={SIZES} />,
    },
    {
      label: 'disabled options — the wheel slides off them on its own',
      content: <SizeWheel label="In stock" options={STOCK} />,
    },
    {
      label: 'unit and visibleCount={3} — a compact wheel',
      content: <MinuteWheel />,
    },
    {
      label: 'perspective={false} — size and fade, no tilt',
      content: <FlatWheel />,
    },
    {
      label: 'disabled — the whole wheel is frozen',
      content: <LockedWheel />,
    },
    {
      label: 'inside a Card, named for screen readers',
      content: <InCard />,
    },
    {
      label: "variant='spotlight' — no band, the sharp row is the pick",
      content: <PortfolioWheel />,
    },
  ];
  return <VariantList variants={variants} />;
```

## Props

| Nombre | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `options` | `OptionWheelOption<T>[]` | — | El rango, en orden. Cada opción lleva una etiqueta, su valor, y puede estar deshabilitada. |
| `selectedValue` | `string \| number` | — | La opción de la ventana del centro. |
| `onSelect` | `(value: T) => void` | — | Avisa la opción en la que quedó la rueda. |
| `label?` | `string` | — | Título arriba de la rueda. |
| `accessibilityLabel?` | `string` | — | Obligatorio cuando no hay `label`: la rueda entera necesita un nombre. |
| `visibleCount?` | `number` | 5 | Filas en pantalla. Se fuerza al impar siguiente para que una de ellas sea el centro. |
| `itemHeight?` | `number` | 44 | Alto de cada fila. |
| `unit?` | `string` | — | Texto fijo a la derecha de la fila del centro ('kg', 'min'). |
| `perspective?` | `boolean` | true | Inclina las filas lejanas para sugerir un cilindro. |
| `variant?` | `'window' \| 'spotlight'` | window | 'window' marca la elección con una banda detrás de la fila del centro. 'spotlight' saca la banda y deja que las filas lejanas se apaguen. |
| `textColor?` | `string` | theme.colors.text | Color de las etiquetas. El fondo sobre el que se apoya la rueda lo pinta quien la compone — éste es el modo de que el texto siga legible. |
| `curveRadius?` | `number` | — | Radio, en píxeles, de la rueda sobre la que se apoyan las filas. Su centro está corrido hacia un lado, así que las filas se arquean hacia adentro a medida que se alejan del medio. |
| `curveFrom?` | `'left' \| 'right'` | left | De qué lado está el centro de la rueda. |
| `disabled?` | `boolean` | false | Impide girar la rueda. |
| `style?` | `StyleProp<ViewStyle>` | — | Estilo del contenedor. |
