@zidian-primitive/list
v0.1.0
Published
A flexible cascader component library built with component composition pattern, providing complete UI and logic for hierarchical selection.
Readme
@zidian-base/cascader
A flexible cascader component library built with component composition pattern, providing complete UI and logic for hierarchical selection.
Features
- 🎯 Component-Based Architecture: Modular components for maximum flexibility
- 🔄 Flexible Data Sources: Support both hierarchical and flat data structures
- 🎨 Fully Customizable: Extensive slots for custom rendering
- ♿ Accessibility: Built with accessibility in mind
- 🔍 Multiple Selection Modes: Single and multiple selection support
- 📦 TypeScript Support: Full type safety
- 🚀 Performance Optimized: Efficient state management and rendering
Installation
npm install @zidian-base/cascaderQuick Start
import React, { useState } from 'react'
import {
CascaderRoot,
CascaderPanel,
CascaderNode,
CascaderPanelHeader,
CascaderPanelIndicator,
useCascader,
type CascaderItem
} from '@zidian-base/cascader'
const data = [
{ category: 'Electronics', type: 'Phone', brand: 'iPhone' },
{ category: 'Electronics', type: 'Phone', brand: 'Samsung' },
{ category: 'Electronics', type: 'Laptop', brand: 'MacBook' },
{ category: 'Clothing', type: 'Shirt', brand: 'Nike' },
]
const handleDataConfig = {
levels: ['category', 'type', 'brand']
}
export default function App() {
const cascader = useCascader({
data,
handleDataConfig,
selectOption: {
defaultSelected: [],
selectType: 'single'
}
})
const handleSelect = (nodeIdx: number) => {
cascader.handleChangeCheckStatus(nodeIdx)
}
return (
<CascaderRoot
isSelected={true}
selectType="single"
onSelectChange={handleSelect}
>
<CascaderPanel>
<CascaderPanelHeader
column={cascader.columns[0]}
showNode={null}
/>
<CascaderPanelIndicator />
{cascader.columns.map((column, level) => (
<div key={level} className="cascader-column">
{column.map((node) => (
<CascaderNode
key={node.index}
node={node}
onSelectChange={() => handleSelect(node.indices)}
/>
))}
</div>
))}
</CascaderPanel>
</CascaderRoot>
)
}API Reference
Hook
useCascader
const cascader = useCascader({
data: T[],
handleDataConfig: FlatDataConfig,
selectOption?: SelectOption
})Hook Configuration
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| data | T[] | - | Flat data array |
| handleDataConfig | FlatDataConfig | - | Configuration for converting flat data to hierarchical |
| selectOption | SelectOption | - | Selection configuration |
Hook Return Values
| Property | Type | Description |
|----------|------|-------------|
| nodes | CascaderNodeType<T>[] | All nodes in the cascader |
| columns | CascaderItem<T>[][] | Visible columns based on current selection |
| showIndices | number[] | Indices of currently shown nodes |
| checkStatusArray | Uint8Array | Check status of all nodes |
| handleShowIndices | (depth: number, index: number) => void | Handle column expansion |
| handleChangeCheckStatus | (nodeIdx: number) => void | Handle node selection |
Components
CascaderRoot
The root component that provides cascader context.
<CascaderRoot
isSelected={boolean}
selectType={'single' | 'multiple'}
onSelectChange={(nodeIdx: number) => void}
{...divProps}
>
{children}
</CascaderRoot>Props
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| isSelected | boolean | false | Whether selection is enabled |
| selectType | 'single' | 'multiple' | - | Selection mode |
| onSelectChange | (nodeIdx: number) => void | - | Selection change callback |
CascaderPanel
Container for cascader columns and panels.
<CascaderPanel {...divProps}>
{children}
</CascaderPanel>CascaderPanelHeader
Header component for displaying column information.
<CascaderPanelHeader
column={CascaderNodeType<any>[]}
showNode={CascaderItem<any> | null}
/>Props
| Property | Type | Description |
|----------|------|-------------|
| column | CascaderNodeType<any>[] | Current column nodes |
| showNode | CascaderItem<any> | null | Currently shown node |
CascaderPanelIndicator
Visual indicator for panel navigation.
<CascaderPanelIndicator />CascaderNode
Individual node component in the cascader.
<CascaderNode
node={CascaderItem<any>}
onSelectChange={(nodeIdx: number) => void}
{...divProps}
/>Props
| Property | Type | Description |
|----------|------|-------------|
| node | CascaderItem<any> | Node data |
| onSelectChange | (nodeIdx: number) => void | Selection change callback |
Types
CascaderNodeType
interface CascaderNodeType<T = any> {
depth: number
index: number
parentIndex: number
range: [number, number]
childrenIndices: number[]
pathId: string
label: string
value: string | number
raw: T | null
}CascaderItem
interface CascaderItem<T> {
indices: number
level: number
label: string
value: string | number
originData?: T
checkStatus?: CheckStatus
}FlatDataConfig
interface FlatDataConfig {
levels: (string | [string, string])[]
}SelectOption
interface SelectOption {
defaultSelected: string[]
selectType: 'single' | 'multiple'
}CheckStatus
type CheckStatus = 0 | 1 | 2
// 0: unchecked, 1: partially checked, 2: fully checkedUsage Examples
Multiple Selection Mode
const cascader = useCascader({
data,
handleDataConfig,
selectOption: {
defaultSelected: [],
selectType: 'multiple'
}
})
<CascaderRoot
isSelected={true}
selectType="multiple"
onSelectChange={cascader.handleChangeCheckStatus}
>
{/* Components */}
</CascaderRoot>Custom Node Rendering
const CustomNode = ({ node, onSelectChange }) => {
return (
<CascaderNode
node={node}
onSelectChange={onSelectChange}
className="custom-node"
>
<div className="node-content">
<span className="node-label">{node.label}</span>
{node.checkStatus === 2 && <span>✓</span>}
</div>
</CascaderNode>
)
}Custom Header
const CustomHeader = ({ column, showNode }) => {
return (
<CascaderPanelHeader column={column} showNode={showNode}>
<div className="custom-header">
{showNode ? `Selected: ${showNode.label}` : 'Please select'}
</div>
</CascaderPanelHeader>
)
}Async Data Loading
const [data, setData] = useState([])
useEffect(() => {
fetchData().then(setData)
}, [])
const cascader = useCascader({
data,
handleDataConfig,
selectOption: {
defaultSelected: [],
selectType: 'single'
}
})Controlled Selection
const [selectedValues, setSelectedValues] = useState([])
const handleSelect = useCallback((nodeIdx: number) => {
const node = cascader.nodes[nodeIdx]
const newValue = cascader.selectOption.selectType === 'single'
? [node.value]
: [...selectedValues, node.value]
setSelectedValues(newValue)
cascader.handleChangeCheckStatus(nodeIdx)
}, [cascader, selectedValues])Advanced Patterns
Search Functionality
const [searchTerm, setSearchTerm] = useState('')
const [filteredData, setFilteredData] = useState(data)
useEffect(() => {
const filtered = data.filter(item =>
Object.values(item).some(value =>
String(value).toLowerCase().includes(searchTerm.toLowerCase())
)
)
setFilteredData(filtered)
}, [searchTerm])
const cascader = useCascader({
data: filteredData,
handleDataConfig
})Custom Animation
import { motion } from 'framer-motion'
const AnimatedNode = ({ node, onSelectChange }) => {
return (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
>
<CascaderNode node={node} onSelectChange={onSelectChange} />
</motion.div>
)
}Data Structure Examples
Hierarchical Data (Converted from Flat)
const flatData = [
{ level1: 'Electronics', level2: 'Phones', level3: 'iPhone' },
{ level1: 'Electronics', level2: 'Phones', level3: 'Samsung' },
{ level1: 'Electronics', level2: 'Laptops', level3: 'MacBook' },
]
const config = {
levels: ['level1', 'level2', 'level3']
}Complex Field Mapping
const complexData = [
{
category: { name: 'Electronics', id: 1 },
product: { name: 'iPhone', type: 'Phone' },
brand: 'Apple'
}
]
const complexConfig = {
levels: [
['category', 'name'], // Use category.name as label
['product', 'name'], // Use product.name as label
'brand' // Use brand directly
]
}Best Practices
- Performance: Use
useMemofor data transformations and filtering - Accessibility: Ensure keyboard navigation and screen reader support
- State Management: Keep selection state in parent component for controlled behavior
- Customization: Use slots for custom rendering while maintaining core functionality
- Data Structure: Keep data flat for better performance and easier manipulation
License
MIT
