mirror of
https://github.com/filecoin-project/slate.git
synced 2024-11-29 16:54:09 +03:00
feat(RovingTabIndex): improvements
This commit is contained in:
parent
79d34d78c4
commit
13b206873e
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user