Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: multiple options && activeIconSetPath about cascader comp #6741

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 140 additions & 19 deletions src/components/cascader-view/cascader-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Skeleton from '../skeleton'
import { useUpdateEffect } from 'ahooks'
import { useFieldNames } from '../../hooks'
import type { FieldNamesType, BaseOptionType } from '../../hooks'
import { cloneDeep } from 'lodash'

const classPrefix = `adm-cascader-view`

Expand All @@ -32,14 +33,16 @@ export type CascaderValueExtend = {

export type CascaderViewProps = {
options: CascaderOption[]
value?: CascaderValue[]
defaultValue?: CascaderValue[]
onChange?: (value: CascaderValue[], extend: CascaderValueExtend) => void
value?: CascaderValue[] | any
defaultValue?: CascaderValue[] | any
onChange?: (value: CascaderValue[] | any, extend: CascaderValueExtend) => void
placeholder?: string | ((index: number) => string)
onTabsChange?: (index: number) => void
activeIcon?: ReactNode
loading?: boolean
multiple?: boolean
fieldNames?: FieldNamesType
activeIconSetPath?: boolean
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

activeIconSetPath 这个命名好奇怪。

Copy link
Author

@electroluxcode electroluxcode Sep 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

哈哈,因为是从 activeIcon 这个属性派生出来的,然后上面的 tab我个人感觉有点像是路径的意思然后就取的这个名字,看看大佬有什么更好的命名

} & NativeProps<'--height'>

const defaultProps = {
Expand All @@ -48,7 +51,6 @@ const defaultProps = {

export const CascaderView: FC<CascaderViewProps> = p => {
const props = mergeProps(defaultProps, p)

const { locale } = useConfig()
const [labelName, valueName, childrenName, disabledName] = useFieldNames(
props.fieldNames
Expand All @@ -57,23 +59,76 @@ export const CascaderView: FC<CascaderViewProps> = p => {
valueName,
childrenName,
})

const [value, setValue] = usePropsValue({
...props,
onChange: val => {
props.onChange?.(val, generateValueExtend(val))
props.onChange?.(
val,
props.multiple ? generateValueExtend(val as string[]) : {}
)
},
})
const [tabActiveIndex, setTabActiveIndex] = useState(0)

const levels = useMemo(() => {
const extractLevelsForMultiple = (
value: any[],
options: CascaderOption[],
valueName: string,
childrenName: string
) => {
const ret: {
selected: CascaderOption | undefined
options: CascaderOption[]
}[] = []

let currentOptions = options
let reachedEnd = false

for (const v of value) {
let target: CascaderOption | undefined
;(v as string[]).forEach(e => {
const temp = currentOptions.find(option =>
e.includes(option[valueName])
)
if (temp) {
target = temp
}
})
ret.push({
selected: target,
options: currentOptions,
})
if (!target || !target[childrenName]) {
reachedEnd = true
break
}
currentOptions = target[childrenName]
}

if (!reachedEnd) {
ret.push({
selected: undefined,
options: currentOptions,
})
}

return ret
}

const extractLevelsForSingle = (
value: any[],
options: CascaderOption[],
valueName: string,
childrenName: string
) => {
const ret: {
selected: CascaderOption | undefined
options: CascaderOption[]
}[] = []

let currentOptions = props.options
let currentOptions = options
let reachedEnd = false

for (const v of value) {
const target = currentOptions.find(option => option[valueName] === v)
ret.push({
Expand All @@ -86,20 +141,44 @@ export const CascaderView: FC<CascaderViewProps> = p => {
}
currentOptions = target[childrenName]
}

if (!reachedEnd) {
ret.push({
selected: undefined,
options: currentOptions,
})
}

return ret
}

const levels = useMemo(() => {
if (props.multiple) {
return extractLevelsForMultiple(
value,
props.options,
valueName,
childrenName
)
} else {
return extractLevelsForSingle(
value,
props.options,
valueName,
childrenName
)
}
}, [value, props.options])

useUpdateEffect(() => {
props.onTabsChange?.(tabActiveIndex)
if (!props.multiple) {
props.onTabsChange?.(tabActiveIndex)
}
}, [tabActiveIndex])
useEffect(() => {
setTabActiveIndex(levels.length - 1)
if (!props.multiple) {
setTabActiveIndex(levels.length - 1)
}
}, [value])
useEffect(() => {
const max = levels.length - 1
Expand All @@ -108,12 +187,37 @@ export const CascaderView: FC<CascaderViewProps> = p => {
}
}, [tabActiveIndex, levels])

const onItemSelect = (selectValue: CascaderValue, depth: number) => {
const onItemSelect = (
selectValue: CascaderValue | CascaderValue[],
depth: number
) => {
const next = value.slice(0, depth)
if (selectValue !== undefined) {
next[depth] = selectValue
}
setValue(next)
if (props.multiple) {
const cloneValue = cloneDeep(value)
cloneValue[depth] = next[depth]
setValue(cloneValue)
} else {
setValue(next)
}
}

const setPath = (selectValue: string, depth: number) => {
const currentPath = cloneDeep(value)
if (selectValue !== undefined) {
if (!currentPath[depth]) {
currentPath[depth] = []
}
currentPath[depth] = (currentPath[depth] as string[]).filter(
(item: string) => {
return item !== selectValue
}
)
currentPath[depth].push(selectValue)
}
setValue(currentPath)
}

const whetherLoading = <T extends unknown[]>(options: T) =>
Expand Down Expand Up @@ -143,8 +247,8 @@ export const CascaderView: FC<CascaderViewProps> = p => {
{selected
? selected[labelName]
: typeof placeholder === 'function'
? placeholder(index)
: placeholder}
? placeholder(index)
: placeholder}
</div>
}
forceRender
Expand All @@ -171,14 +275,31 @@ export const CascaderView: FC<CascaderViewProps> = p => {
</div>
) : (
<CheckList
value={[value[index]]}
onChange={selectValue =>
onItemSelect(selectValue[0], index)
}
value={props.multiple ? value[index] : [value[index]]}
onChange={selectValue => {
if (props.multiple) {
onItemSelect(selectValue, index)
} else {
onItemSelect(selectValue[0], index)
}
}}
multiple={props.multiple}
activeSetPathMiddleware={{
index,
activeIconSetPath: props.activeIconSetPath,
setPath,
}}
activeIcon={props.activeIcon}
>
{level.options.map(option => {
const active = value[index] === option[valueName]
let active
if (props.multiple) {
active = (value[index] as Array<any>)?.includes(
option[valueName]
)
} else {
active = value[index] === option[valueName]
}
return (
<CheckList.Item
value={option[valueName]}
Expand Down
Loading
Loading