feat(RovingTabIndex): improvements

This commit is contained in:
Aminejv 2022-01-04 18:54:56 +01:00
parent 79d34d78c4
commit 13b206873e
3 changed files with 74 additions and 66 deletions

View File

@ -108,14 +108,18 @@ const FilterSection = React.forwardRef(({ title, children, ...props }, ref) => {
</Tooltip.Root>
)}
{isExpanded ? (
<motion.ul
layoutId={title + "section"}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
css={STYLES_FILTERS_GROUP}
>
{children}
</motion.ul>
<RovingTabIndex.Provider axis="vertical">
<RovingTabIndex.List>
<motion.ul
layoutId={title + "section"}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
css={STYLES_FILTERS_GROUP}
>
{children}
</motion.ul>
</RovingTabIndex.List>
</RovingTabIndex.Provider>
) : null}
</div>
);
@ -151,27 +155,23 @@ function Tags({ viewer, data, onAction, ...props }) {
const [, { hidePopup }] = useFilterContext();
return (
<RovingTabIndex.Provider axis="vertical">
<RovingTabIndex.List>
<FilterSection title="Tags" {...props}>
{viewer.slates.map((slate, index) => (
<li key={slate.id}>
<RovingTabIndex.Item index={index}>
<FilterButton
href={`/$/slate/${slate.id}`}
isSelected={slate.id === data?.id}
onAction={onAction}
Icon={slate.isPublic ? SVG.Hash : SVG.SecurityLock}
onClick={hidePopup}
>
{slate.slatename}
</FilterButton>
</RovingTabIndex.Item>
</li>
))}
</FilterSection>
</RovingTabIndex.List>
</RovingTabIndex.Provider>
<FilterSection title="Tags" {...props}>
{viewer.slates.map((slate, index) => (
<li key={slate.id}>
<RovingTabIndex.Item index={index}>
<FilterButton
href={`/$/slate/${slate.id}`}
isSelected={slate.id === data?.id}
onAction={onAction}
Icon={slate.isPublic ? SVG.Hash : SVG.SecurityLock}
onClick={hidePopup}
>
{slate.slatename}
</FilterButton>
</RovingTabIndex.Item>
</li>
))}
</FilterSection>
);
}

View File

@ -1,7 +1,8 @@
import * as React from "react";
import { jsx } from "@emotion/react";
import { mergeRefs } from "~/common/utilities";
import { useEventListener, useMounted } from "~/common/hooks";
import { useEventListener } from "~/common/hooks";
/* -------------------------------------------------------------------------------------------------
* RovingTabIndex Provider
@ -16,28 +17,37 @@ export function Provider({ axis, children }) {
const [focusedIndex, setFocusedIndex] = React.useState(initialIndex);
const registerItem = ({ index, ref }) => (focusedElementsRefs.current[index] = ref);
const cleanupItem = (index) => delete focusedElementsRefs.current[index];
const cleanupItem = (index) => {
if (index === focusedIndex) {
setFocusedIndex(initialIndex);
}
delete focusedElementsRefs.current[index];
};
const focusElement = (index) => {
const focusedElementRef = focusedElementsRefs.current[index];
focusedElementRef?.current?.focus();
};
const setIndexToNextElement = () => {
const nextIndex = focusedIndex + 1;
const elementsExists = focusedElementsRefs.current[nextIndex];
setFocusedIndex(elementsExists ? nextIndex : initialIndex);
const nextFocusedIndex = elementsExists ? nextIndex : initialIndex;
setFocusedIndex(nextFocusedIndex);
focusElement(nextFocusedIndex);
};
const setIndexPreviousElement = () => {
const prevIndex = focusedIndex - 1;
let prevFocusedIndex = null;
if (prevIndex >= initialIndex) {
setFocusedIndex(prevIndex);
return;
prevFocusedIndex = prevIndex;
} else {
prevFocusedIndex = Math.max(...Object.keys(focusedElementsRefs.current));
}
const lastIndex = Math.max(...Object.keys(focusedElementsRefs.current));
setFocusedIndex(lastIndex);
setFocusedIndex(prevFocusedIndex);
focusElement(prevFocusedIndex);
};
useMounted(() => {
const focusedElementRef = focusedElementsRefs.current[focusedIndex];
focusedElementRef?.current?.focus();
}, [focusedIndex]);
const contextValue = React.useMemo(
() => [
{ focusedIndex, axis },
@ -71,13 +81,11 @@ const useRovingHandler = ({ ref }) => {
});
};
export const List = React.forwardRef(({ children }, forwardedRef) => {
export const List = React.forwardRef(({ as = "div", children, ...props }, forwardedRef) => {
const ref = React.useRef();
useRovingHandler({ ref });
return React.cloneElement(React.Children.only(children), {
ref: mergeRefs([ref, children.ref, forwardedRef]),
});
return jsx(as, { ...props, ref: mergeRefs([ref, forwardedRef]) }, children);
});
/* -------------------------------------------------------------------------------------------------

View File

@ -203,25 +203,25 @@ function Channels({
<AnimateSharedLayout>
<RovingTabIndex.Provider axis="horizontal">
<RovingTabIndex.List>
<div css={STYLES_CHANNEL_BUTTONS_WRAPPER}>
{channels.map((channel, index) => (
<motion.div layoutId={`jumper-${channel.id}`} initial={false} key={channel.id}>
<RovingTabIndex.Item index={index}>
<ChannelButton
isSelected={channel.doesContainFile}
onClick={() => onAddFileToChannel(channel, channel.doesContainFile)}
title={channel.slatename}
style={{ maxWidth: "48ch" }}
>
{channel.slatename}
</ChannelButton>
</RovingTabIndex.Item>
</motion.div>
))}
<RovingTabIndex.List css={STYLES_CHANNEL_BUTTONS_WRAPPER}>
{channels.map((channel, index) => (
<motion.div layoutId={`jumper-${channel.id}`} initial={false} key={channel.id}>
<RovingTabIndex.Item index={index}>
<ChannelButton
isSelected={channel.doesContainFile}
onClick={() => onAddFileToChannel(channel, channel.doesContainFile)}
title={channel.slatename}
style={{ maxWidth: "48ch" }}
>
{channel.slatename}
</ChannelButton>
</RovingTabIndex.Item>
</motion.div>
))}
<Show when={isCreatingChannel}>
<motion.div initial={{ opacity: 0.5, y: 4 }} animate={{ opacity: 1, y: 0 }}>
<Show when={isCreatingChannel}>
<motion.div initial={{ opacity: 0.5, y: 4 }} animate={{ opacity: 1, y: 0 }}>
<RovingTabIndex.Item index={channels.length}>
<ChannelButton
css={Styles.HORIZONTAL_CONTAINER_CENTERED}
onClick={(e) => (e.stopPropagation(), onCreateChannel(searchQuery))}
@ -240,9 +240,9 @@ function Channels({
/>
<span style={{ marginLeft: 4 }}>{searchQuery}</span>
</ChannelButton>
</motion.div>
</Show>
</div>
</RovingTabIndex.Item>
</motion.div>
</Show>
</RovingTabIndex.List>
</RovingTabIndex.Provider>
</AnimateSharedLayout>