@navikt/ds-react
Version:
React components from the Norwegian Labour and Welfare Administration.
111 lines (98 loc) • 3.13 kB
text/typescript
import { useCallback } from "react";
import { composeEventHandlers } from "../../util/composeEventHandlers";
import { mergeRefs } from "../../util/hooks/useMergeRefs";
import {
useToggleGroupContext,
useToggleGroupDescendant,
} from "../ToggleGroup.context";
export interface UseToggleItemProps {
/**
* If `true`, the `ToggleItem` won't be toggleable
* @default false
*/
disabled?: boolean;
onClick?: React.MouseEventHandler;
onFocus?: React.FocusEventHandler;
onKeyDown?: React.KeyboardEventHandler;
value: string;
}
export function useToggleItem<P extends UseToggleItemProps>(
{
value,
disabled = false,
onFocus: _onFocus,
onClick,
onKeyDown: _onKeyDown,
}: P,
ref: React.ForwardedRef<HTMLButtonElement>,
) {
const { setSelectedValue, setFocusedValue, selectedValue, focusedValue } =
useToggleGroupContext();
const { register, descendants } = useToggleGroupDescendant({
disabled,
value,
});
const isSelected = value === selectedValue;
const onFocus = () => setFocusedValue(value);
/**
* Implements roving-tabindex for horizontal tabs
*/
const onKeyDown = useCallback(
(event: React.KeyboardEvent) => {
/**
* ToggleGroup.Item is registered with its prop 'value'.
* We can then use it to find the current focuses descendant
*/
const idx = descendants
.values()
.findIndex((x) => x.value === focusedValue);
const nextTab = () => {
const next = descendants.nextEnabled(idx, false);
next?.node?.focus();
};
const prevTab = () => {
const prev = descendants.prevEnabled(idx, false);
prev?.node?.focus();
};
const firstTab = () => {
const first = descendants.firstEnabled();
first?.node?.focus();
};
const lastTab = () => {
const last = descendants.lastEnabled();
last?.node?.focus();
};
const keyMap: Record<string, React.KeyboardEventHandler> = {
ArrowLeft: prevTab,
ArrowRight: nextTab,
Home: firstTab,
End: lastTab,
};
const hasModifiers =
event.shiftKey || event.ctrlKey || event.altKey || event.metaKey;
const action = keyMap[event.key];
if (action && !hasModifiers) {
event.preventDefault();
action(event);
} else if (event.key === "Tab") {
/**
* Imperative focus during keydown is risky so we prevent React's batching updates
* to avoid potential bugs. See: https://github.com/facebook/react/issues/20332
*/
selectedValue && setTimeout(() => setFocusedValue(selectedValue));
}
},
[descendants, focusedValue, selectedValue, setFocusedValue],
);
return {
ref: mergeRefs([register, ref]),
isSelected,
isFocused: focusedValue === value,
onClick: composeEventHandlers(
onClick,
() => selectedValue !== value && setSelectedValue(value),
),
onFocus: disabled ? undefined : composeEventHandlers(_onFocus, onFocus),
onKeyDown: composeEventHandlers(_onKeyDown, onKeyDown),
};
}