/
collapse.tsx
125 lines (114 loc) · 3.31 KB
/
collapse.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import { View } from "@tarojs/components"
import { ViewProps } from "@tarojs/components/types/View"
import classNames from "classnames"
import * as _ from "lodash"
import * as React from "react"
import { Children, cloneElement, isValidElement, ReactElement, ReactNode, useCallback } from "react"
import { prefixClassname } from "../styles"
import { HAIRLINE_BORDER_TOP_BOTTOM } from "../styles/hairline"
import CollapseItem from "./collapse-item"
import CollapseContext from "./collapse.context"
function validateActiveValue(value: string | number | Array<string | number>, accordion: boolean) {
if (accordion && Array.isArray(value)) {
// eslint-disable-next-line
console.error('[Taroify] Collapse: "value" should not be Array in accordion mode')
return false
}
if (!accordion && !Array.isArray(value)) {
// eslint-disable-next-line
console.error('[Taroify] Collapse: "value" should be Array in non-accordion mode')
return false
}
return true
}
interface CollapseChildren {
items: ReactNode[]
}
function useCollapseChildren(children?: ReactNode): CollapseChildren {
const __children__: CollapseChildren = {
items: [],
}
Children.forEach(children, (child: ReactNode) => {
if (isValidElement(child)) {
const element = child as ReactElement
const elementType = element.type
if (elementType === CollapseItem) {
const { key, props } = element
const index = _.size(__children__.items)
const { value } = props
__children__.items?.push(
cloneElement(element, {
key: key ?? index,
value: value ?? index,
}),
)
} else {
__children__.items?.push(element)
}
} else {
__children__.items?.push(child)
}
})
return __children__
}
export interface CollapseProps extends ViewProps {
value?: any
accordion?: boolean
bordered?: boolean
children?: ReactNode
onChange?: (value: any) => void
}
function Collapse(props: CollapseProps) {
const {
className,
bordered,
value = "",
accordion = false,
onChange,
children: childrenProp,
...restProps
} = props
const { items } = useCollapseChildren(childrenProp)
const toggleItem = useCallback(
(itemValue: number | string, expanded: boolean) => {
if (accordion) {
onChange?.(itemValue === value ? "" : itemValue)
} else if (expanded) {
onChange?.((value as any[]).concat(itemValue))
} else {
onChange?.((value as any[]).filter((activeKey) => activeKey !== itemValue))
}
},
[accordion, value, onChange],
)
const isExpanded = useCallback(
(itemValue: number | string) => {
if (process.env.NODE_ENV !== "production" && !validateActiveValue(value, accordion)) {
return false
}
return accordion ? value === itemValue : (value as Array<number | string>).includes(itemValue)
},
[accordion, value],
)
return (
<CollapseContext.Provider
value={{
isExpanded,
toggleItem,
}}
>
<View
className={classNames(
prefixClassname("collapse"),
{
[HAIRLINE_BORDER_TOP_BOTTOM]: bordered,
},
className,
)}
children={items}
{...restProps}
/>
</CollapseContext.Provider>
)
}
export default Collapse