useDescendants
useDescendants
is a custom hook that manages descendants.
Item 0
Item 1
Item 2
Item 3
Item 4
const { useDescendants, useDescendant, DescendantsContext } =
createDescendants()
const ref = useRef<HTMLDivElement>(null)
const descendants = useDescendants()
const onFocus = useCallback(
(ev: FocusEvent<HTMLDivElement>) => {
if (ev.target !== ref.current) return
const descendant = descendants.enabledFirstValue()
if (descendant) {
descendant.node.focus()
if (ref.current) ref.current.tabIndex = -1
}
},
[descendants],
)
const onBlur = useCallback((ev: FocusEvent<HTMLDivElement>) => {
if (contains(ref.current, ev.relatedTarget)) return
if (ref.current) ref.current.tabIndex = 0
}, [])
const Item: FC<{ index: number }> = ({ index }) => {
const { descendants, register } = useDescendant()
const onKeyDown = useCallback(
(ev: KeyboardEvent<HTMLButtonElement>) => {
runKeyAction(ev, {
ArrowDown: () => {
const descendant = descendants.enabledNextValue(index)
if (descendant) descendant.node.focus()
},
ArrowUp: () => {
const descendant = descendants.enabledPrevValue(index)
if (descendant) descendant.node.focus()
},
Home: () => {
const descendant = descendants.enabledFirstValue()
if (descendant) descendant.node.focus()
},
End: () => {
const descendant = descendants.enabledLastValue()
if (descendant) descendant.node.focus()
},
})
},
[descendants],
)
return (
<Center
ref={register}
tabIndex={-1}
onKeyDown={onKeyDown}
bg="bg.contrast"
color="fg.contrast"
p="md"
>
Item {index}
</Center>
)
}
return (
<DescendantsContext value={descendants}>
<VStack
ref={ref}
tabIndex={0}
focusVisibleRing="none"
onFocus={onFocus}
onBlur={onBlur}
>
{Array.from({ length: 5 }).map((_, index) => (
<Item key={index} index={index} />
))}
</VStack>
</DescendantsContext>
)
If you use this code, you need to add
"use client"
to the top of the file.Usage
import { createDescendants } from "@yamada-ui/react"
import { createDescendants } from "@/components/ui"
import { createDescendants } from "@workspaces/ui"
const {
DescendantsContext,
useDescendant,
useDescendantRegister,
useDescendants,
useDescendantsContext,
} = createDescendants()
const descendants = useDescendants()
const { descendants, register } = useDescendant()
Disable descendant
To disable a descendant, set the disabled
prop to true
on useDescendant
.
Item 0
Item 1
Item 2
Item 3
Item 4
const { useDescendants, useDescendant, DescendantsContext } =
createDescendants()
const ref = useRef<HTMLDivElement>(null)
const descendants = useDescendants()
const onFocus = useCallback(
(ev: FocusEvent<HTMLDivElement>) => {
if (ev.target !== ref.current) return
const descendant = descendants.enabledFirstValue()
if (descendant) {
descendant.node.focus()
if (ref.current) ref.current.tabIndex = -1
}
},
[descendants],
)
const onBlur = useCallback((ev: FocusEvent<HTMLDivElement>) => {
if (contains(ref.current, ev.relatedTarget)) return
if (ref.current) ref.current.tabIndex = 0
}, [])
const Item: FC<{ index: number; disabled?: boolean }> = ({
index,
disabled,
}) => {
const { descendants, register } = useDescendant({ disabled })
const onKeyDown = useCallback(
(ev: KeyboardEvent<HTMLButtonElement>) => {
runKeyAction(ev, {
ArrowDown: () => {
const descendant = descendants.enabledNextValue(index)
if (descendant) descendant.node.focus()
},
ArrowUp: () => {
const descendant = descendants.enabledPrevValue(index)
if (descendant) descendant.node.focus()
},
Home: () => {
const descendant = descendants.enabledFirstValue()
if (descendant) descendant.node.focus()
},
End: () => {
const descendant = descendants.enabledLastValue()
if (descendant) descendant.node.focus()
},
})
},
[descendants],
)
return (
<Center
ref={register}
tabIndex={-1}
onKeyDown={onKeyDown}
bg="bg.contrast"
color="fg.contrast"
p="md"
data-disabled={dataAttr(disabled)}
_disabled={{ layerStyle: "disabled" }}
>
Item {index}
</Center>
)
}
return (
<DescendantsContext value={descendants}>
<VStack
ref={ref}
tabIndex={0}
focusVisibleRing="none"
onFocus={onFocus}
onBlur={onBlur}
>
{Array.from({ length: 5 }).map((_, index) => (
<Item key={index} index={index} disabled={index % 3 === 0} />
))}
</VStack>
</DescendantsContext>
)
If you use this code, you need to add
"use client"
to the top of the file.