npm package: @ray-js/loop-picker
Description: 循环滚动选择器
Latest version: 1.0.1

# @ray-js/loop-picker

[简体中文](./README-zh_CN.md) | English

> A powerful loop picker component for MiniApp development, supporting single/multi-column selection, loop scrolling, custom styles, and time-related types.

## NPM Package
[![latest](https://img.shields.io/npm/v/@ray-js/loop-picker/latest.svg)](https://www.npmjs.com/package/@ray-js/loop-picker) [![download](https://img.shields.io/npm/dt/@ray-js/loop-picker.svg)](https://www.npmjs.com/package/@ray-js/loop-picker)

**NPM Package:** [@ray-js/loop-picker](https://www.npmjs.com/package/@ray-js/loop-picker)

## Installation

### npm Installation

```bash
npm install @ray-js/loop-picker
// or
$ yarn add @ray-js/loop-picker
```

## Quick Start

### Basic Usage

```tsx
import React, { useState } from 'react';
import LoopPicker from '@ray-js/loop-picker';

function BasicExample() {
  const [columns] = useState(['Option 1', 'Option 2', 'Option 3']);

  const onChange = (e) => {
    const { value, index } = e.detail;
    console.log('Selection changed:', value, index);
  };

  return (
    <LoopPicker 
      columns={columns} 
      onChange={onChange} 
    />
  );
}
```

### Loop Scrolling

Enable loop scrolling for infinite scroll scenarios:

```tsx
import React, { useState } from 'react';
import LoopPicker from '@ray-js/loop-picker';

function LoopExample() {
  const [loopColumns] = useState(Array.from({ length: 60 }, (_, i) => i));

  return (
    <LoopPicker 
      loop
      columns={loopColumns}
      title="Loop Picker"
      showToolbar
    />
  );
}
```

### Time Types

The component supports built-in time-related types:

#### Date Picker

```tsx
import React from 'react';
import LoopPicker from '@ray-js/loop-picker';

function DateExample() {
  const onDateChange = (e) => {
    console.log('Date changed:', e.detail.value);
  };

  return (
    <LoopPicker 
      type="date" 
      title="Select Date" 
      onChange={onDateChange} 
    />
  );
}
```

#### Time Picker (24-hour format)

```tsx
import React from 'react';
import LoopPicker from '@ray-js/loop-picker';

function TimeExample() {
  const onTimeChange = (e) => {
    console.log('Time changed:', e.detail.value);
  };

  return (
    <LoopPicker 
      type="time" 
      title="Select Time" 
      onChange={onTimeChange} 
    />
  );
}
```

#### DateTime Picker

```tsx
import React from 'react';
import LoopPicker from '@ray-js/loop-picker';

function DateTimeExample() {
  const onDatetimeChange = (e) => {
    console.log('DateTime changed:', e.detail.value);
  };

  return (
    <LoopPicker 
      type="datetime" 
      title="Select Date & Time" 
      onChange={onDatetimeChange} 
    />
  );
}
```

### Loop Scrolling + Time Selection

```tsx
import React from 'react';
import LoopPicker from '@ray-js/loop-picker';

function LoopTimeExample() {

  return (
    <LoopPicker 
      loop
      type="time" 
      title="Loop Time Selection"
      showToolbar
    />
  );
}
```

### Disabled and Loading States

```tsx
import React, { useState } from 'react';
import LoopPicker from '@ray-js/loop-picker';

function DisabledAndLoadingExample() {
  const [columns] = useState(['Option 1', 'Option 2', 'Option 3']);

  return (
    <>
      {/* Disabled state */}
      <LoopPicker 
        disabled
        columns={columns}
        title="Disabled State"
      />

      {/* Loading state */}
      <LoopPicker 
        loading
        columns={columns}
        title="Loading..."
      />
    </>
  );
}
```

### Dynamic Data Modification

```tsx
import React, { useState, useRef } from 'react';
import LoopPicker from '@ray-js/loop-picker';

function DynamicExample() {
  const [dynamicColumns, setDynamicColumns] = useState(['A', 'B', 'C']);
  const [currentSet, setCurrentSet] = useState(0);
  const pickerRef = useRef(null);

  const sets = [
    ['A', 'B', 'C'],
    ['Option 1', 'Option 2', 'Option 3', 'Option 4'],
    ['Apple', 'Banana', 'Orange', 'Grape', 'Strawberry']
  ];

  const changeColumns = () => {
    const nextSet = (currentSet + 1) % sets.length;
    setCurrentSet(nextSet);
    setDynamicColumns(sets[nextSet]);
  };

  const setPickerValue = () => {
    if (pickerRef.current) {
      // Set selected value
      pickerRef.current.setValues(['B']);
      // Or set selected index
      // pickerRef.current.setIndexes([1]);
    }
  };

  const onDynamicChange = (e) => {
    console.log('Dynamic selection changed:', e.detail.value);
  };

  return (
    <>
      <LoopPicker 
        ref={pickerRef}
        columns={dynamicColumns}
        title="Dynamic Data"
        showToolbar
        onChange={onDynamicChange}
      />
      
      <button onClick={changeColumns}>Switch Data</button>
      <button onClick={setPickerValue}>Set Value</button>
    </>
  );
}
```

### Multi-column Selection

```tsx
import React, { useState } from 'react';
import LoopPicker from '@ray-js/loop-picker';

function MultiColumnExample() {
  const [dateColumns] = useState([
    {
      values: ['2020', '2021', '2022', '2023', '2024'],
      defaultIndex: 3,
      unit: '',
    },
    {
      values: ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'],
      defaultIndex: 0,
      unit: '',
    },
    {
      values: Array.from({ length: 31 }, (_, i) => String(i + 1).padStart(2, '0')),
      defaultIndex: 0,
      unit: '',
    },
  ]);

  return (
    <LoopPicker 
      columns={dateColumns} 
      title="Select Date" 
    />
  );
}
```

### Advanced Multi-column Selection

Supports more complex multi-column configurations including units, default indexes, disabled states, etc.:

```tsx
import React, { useState } from 'react';
import LoopPicker from '@ray-js/loop-picker';

function AdvancedMultiColumnExample() {
  const [advancedColumns] = useState([
    {
      values: ['Beijing', 'Shanghai', 'Guangzhou', 'Shenzhen'],
      defaultIndex: 0,
      unit: '',
      style: 'color: #1890ff;'
    },
    {
      values: ['Red', 'Green', 'Blue', 'Yellow'],
      defaultIndex: 1,
      unit: '',
      disabled: false
    },
    {
      values: ['S', 'M', 'L', 'XL'],
      defaultIndex: 2,
      unit: 'Size'
    }
  ]);

  const onAdvancedChange = (e) => {
    console.log('Advanced multi-column selection:', e.detail.value);
  };

  return (
    <LoopPicker 
      columns={advancedColumns}
      title="Advanced Multi-column Selection"
      showToolbar
      onChange={onAdvancedChange}
    />
  );
}
```

## Data Formats

### Single Column Data

#### Simple String Array

```javascript
const columns = ['Apple', 'Banana', 'Orange'];
```

#### Object Array (Recommended)

```javascript
const columns = [
  { text: 'Apple', value: 'apple' },
  { text: 'Banana', value: 'banana' },
  { text: 'Orange', value: 'orange', disabled: true }
];
```

### Multi-column Data

#### Standard Multi-column Configuration

```javascript
const columns = [
  {
    values: ['2023', '2024', '2025'],  // First column: Year
    defaultIndex: 0,
    unit: ''
  },
  {
    values: ['01', '02', '03', '04'],  // Second column: Month
    defaultIndex: 0,
    unit: ''
  },
  {
    values: ['01', '02', '03', '04'],  // Third column: Day
    defaultIndex: 0,
    unit: ''
  }
];
```

#### Advanced Multi-column Configuration

```javascript
const columns = [
  {
    values: [
      { text: '2023', value: 2023 },
      { text: '2024', value: 2024 },
      { text: '2025', value: 2025 }
    ],
    defaultIndex: 1,
    unit: '',
    order: 0,
    disabled: false,
    style: 'color: #333;',
    fontStyle: 'font-weight: bold;'
  },
  {
    values: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
    defaultIndex: 0,
    unit: '',
    order: 1
  },
  {
    values: Array.from({ length: 31 }, (_, i) => ({
      text: `${i + 1}`,
      value: i + 1,
      disabled: i > 30  // Disable certain options
    })),
    defaultIndex: 0,
    unit: '',
    order: 2
  }
];
```

### Column Configuration Object Properties

| Property       | Type      | Default | Description                               |
| -------------- | --------- | ------- | ----------------------------------------- |
| `values`       | `Array`   | `[]`    | Column option data                        |
| `defaultIndex` | `Number`  | `0`     | Default selected index                    |
| `unit`         | `String`  | `''`    | Unit text                                 |
| `order`        | `Number`  | `0`     | Column sort order (similar to flex order) |
| `disabled`     | `Boolean` | `false` | Whether to disable the column             |
| `style`        | `String`  | `''`    | Custom style for column container         |
| `fontStyle`    | `String`  | `''`    | Custom style for column text              |

### Option Object Properties

| Property   | Type      | Default | Description              |
| ---------- | --------- | ------- | ------------------------ |
| `text`     | `String`  | `''`    | Display text             |
| `value`    | `Any`     | -       | Option value             |
| `disabled` | `Boolean` | `false` | Whether to disable option |

## Style Customization

### CSS Variables

The component supports style customization through CSS variables:

```css
.loop-picker {
  /* Basic variables */
  --loop-picker-background: #fff;
  --loop-picker-height: 220px;
  
  /* Toolbar */
  --loop-picker-toolbar-height: 44px;
  --loop-picker-toolbar-background: #f7f8fa;
  --loop-picker-toolbar-border-color: #ebedf0;
  
  /* Buttons */
  --loop-picker-button-padding: 0 16px;
  --loop-picker-button-font-size: 16px;
  --loop-picker-cancel-color: #646566;
  --loop-picker-confirm-color: #1890ff;
  
  /* Title */
  --loop-picker-title-font-size: 16px;
  --loop-picker-title-color: #323233;
  
  /* Options */
  --loop-picker-option-height: 44px;
  --loop-picker-option-font-size: 16px;
  --loop-picker-option-color: #646566;
  --loop-picker-option-disabled-color: #c8c9cc;
  
  /* Active item */
  --loop-picker-active-color: #323233;
  --loop-picker-active-font-weight: 500;
  
  /* Mask */
  --loop-picker-mask-background: linear-gradient(180deg, 
    rgba(255, 255, 255, 0.9), 
    rgba(255, 255, 255, 0.4));
}
```

### Custom Style Example

```tsx
import React, { useState } from 'react';
import LoopPicker from '@ray-js/loop-picker';
import './CustomPicker.css'; // Import custom styles

function CustomStyleExample() {
  const [columns] = useState(['Option 1', 'Option 2', 'Option 3']);

  return (
    <LoopPicker 
      className="custom-picker"
      columns={columns}
      activeStyle="color: #ff6b6b; font-weight: bold;"
      fontStyle="color: #666;"
      pickerStyle="background: #f8f9fa;"
    />
  );
}
```

```css
.custom-picker {
  --loop-picker-confirm-color: #ff6b6b;
  --loop-picker-active-color: #ff6b6b;
  --loop-picker-background: #f8f9fa;
}
```

## 📚 API Reference

### Properties

| Property              | Type              | Default     | Description                                               |
| --------------------- | ----------------- | ----------- | --------------------------------------------------------- |
| `type`                | `String`          | `'normal'`  | Picker type: `'normal'`, `'date'`, `'time'`, `'datetime'` |
| `columns`             | `Array`           | `[]`        | Picker data source                                        |
| `loop`                | `Boolean`         | `false`     | Enable loop scrolling                                     |
| `defaultIndex`        | `Number \| Array` | `0`         | Default selected index                                    |
| `title`               | `String`          | `''`        | Toolbar title                                             |
| `cancelButtonText`    | `String`          | `'Cancel'`  | Cancel button text                                        |
| `confirmButtonText`   | `String`          | `'Confirm'` | Confirm button text                                       |
| `loading`             | `Boolean`         | `false`     | Show loading state                                        |
| `disabled`            | `Boolean`         | `false`     | Disable the picker                                        |
| `itemHeight`          | `Number`          | `44`        | Height of each option (px)                                |
| `visibleItemCount`    | `Number`          | `5`         | Number of visible options                                 |
| `showToolbar`         | `Boolean`         | `false`     | Show toolbar                                              |
| `toolbarPosition`     | `String`          | `'top'`     | Toolbar position: `'top'` or `'bottom'`                   |
| `activeStyle`         | `String`          | `''`        | Style for active option                                   |
| `fontStyle`           | `String`          | `''`        | Font style for options                                    |
| `pickerStyle`         | `String`          | `''`        | Container style                                           |

### Events

| Event       | Description                              | Parameters         |
| ----------- | ---------------------------------------- | ------------------ |
| `onChange`  | Triggered when selection changes         | `{ value, index }` |
| `onConfirm` | Triggered when confirm button is clicked | `{ value, index }` |
| `onCancel`  | Triggered when cancel button is clicked  | `{ value, index }` |

### Methods

| Method                           | Description             | Parameters                        | Return Value |
| -------------------------------- | ----------------------- | --------------------------------- | ------------ |
| `getValues()`                    | Get current selected values   | -                           | `Any|Array`  |
| `getIndexes()`                   | Get current selected indexes  | -                           | `Array`      |
| `setValues(values)`              | Set selected values           | `values: Array|Any`         | -            |
| `setIndexes(indexes)`            | Set selected indexes          | `indexes: Array`            | -            |
| `getColumnValue(index)`          | Get value of specific column  | `index: Number`             | `Any`        |
| `getColumnValues(index)`         | Get all values of specific column | `index: Number`        | `Array`      |
| `setColumnValue(index, value)`   | Set value of specific column  | `index: Number, value: Any` | -            |
| `setColumnValues(index, values)` | Set all values of specific column | `index: Number, values: Array` | -        |

## Development

### Requirements

- Node.js 16+
- Tuya MiniApp Base Library 2.0.0+

### Local Development

```bash
# Install dependencies
npm install
//or
yarn install

# Run example
npm run start:tuya
//or
yarn start:tuya

# Build for production
npm run build
//or
yarn build
```

## Changelog

### Version History

- **v1.0.0** (2025-10-13)
  - ✨ Initial release