Skip to content

Commit

Permalink
Disabled indexes for keyboard nav (#493)
Browse files Browse the repository at this point in the history
* added disabled indexes support + tests

* updated storybook
  • Loading branch information
laviomri authored Jan 26, 2022
1 parent 61fab45 commit 82588de
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const ON_CLICK = action("item selected");
<Story name="Overview">
{() => {
const ref = useRef(null);
const disabledIndexes = [2, 4, 6];
const [itemsCount, setItemsCount] = useState(15);
const [numberOfItemsInLine, setNumberOfItemsInLine] = useState(4);
const width = useMemo(() => numberOfItemsInLine * ELEMENT_WIDTH_PX + 2 * PADDING_PX, [numberOfItemsInLine]);
Expand All @@ -40,7 +41,8 @@ export const ON_CLICK = action("item selected");
numberOfItemsInLine,
itemsCount,
getItemByIndex,
onItemClicked: ON_CLICK
onItemClicked: ON_CLICK,
disabledIndexes
});
const onClickByIndex = useCallback(index => () => onSelectionAction(index), [onSelectionAction]);
return (
Expand All @@ -49,6 +51,7 @@ export const ON_CLICK = action("item selected");
{items.map((item, index) => (
<Button
key={item}
disabled={disabledIndexes.includes(index)}
onClick={onClickByIndex(index)}
kind={Button.kinds.SECONDARY}
className={cx("use-grid-keyboard-nav-item", { "active-item": index === activeIndex })}
Expand Down Expand Up @@ -133,6 +136,11 @@ export const ON_CLICK = action("item selected");
</>
}
/>
<FunctionArgument
name="disabledIndexes"
type="number[]"
description="Optional array of disabled indices, which will be skipped while navigating."
/>
</FunctionArgument>
</FunctionArguments>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,4 +310,82 @@ describe("calcActiveIndexAfterArrowNavigation", () => {
expect(result).toEqual(expectedResult);
});
});

describe("disabled indexes", () => {
it("should skip a single disabled index", () => {
const direction = NAV_DIRECTIONS.RIGHT;
const itemsCount = 9;
const numberOfItemsInLine = 5;
const activeIndex = 0;
const disabledIndexes = [1, 3];
const expectedResult = { isOutbound: false, nextIndex: 2 }; // skip 1, which is a disabled index

const result = calcActiveIndexAfterArrowNavigation({
direction,
itemsCount,
numberOfItemsInLine,
activeIndex,
disabledIndexes
});

expect(result).toEqual(expectedResult);
});

it("should return outbound navigation when skipping over disabled index 0", () => {
const direction = NAV_DIRECTIONS.LEFT;
const itemsCount = 2;
const numberOfItemsInLine = 2;
const activeIndex = 1;
const disabledIndexes = [0];
const expectedResult = { isOutbound: true };

const result = calcActiveIndexAfterArrowNavigation({
direction,
itemsCount,
numberOfItemsInLine,
activeIndex,
disabledIndexes
});

expect(result).toEqual(expectedResult);
});

it("should skip multiple disabled sequential indexes in an inbound navigation", () => {
const direction = NAV_DIRECTIONS.UP;
const itemsCount = 10;
const numberOfItemsInLine = 2;
const activeIndex = 8; // last row, left item
const disabledIndexes = [2, 4, 6]; // all the items of the left column
const expectedResult = { isOutbound: false, nextIndex: 0 };

const result = calcActiveIndexAfterArrowNavigation({
direction,
itemsCount,
numberOfItemsInLine,
activeIndex,
disabledIndexes
});

expect(result).toEqual(expectedResult);
});

it("should return an outbound navigation when all indexes in the navigation direction are disabled", () => {
const direction = NAV_DIRECTIONS.UP;
const itemsCount = 5;
const numberOfItemsInLine = 5;
const activeIndex = 0; // last row, left item
const disabledIndexes = [1, 2, 3, 4]; // all the items except for the first one
const expectedResult = { isOutbound: true };

const result = calcActiveIndexAfterArrowNavigation({
direction,
itemsCount,
numberOfItemsInLine,
activeIndex,
disabledIndexes
});

expect(result).toEqual(expectedResult);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,24 @@ describe("useGridKeyboardNavigation", () => {
expect(result.current.activeIndex).toBe(0);
});

it("should skip disabled indexes when navigating with the keyboard", () => {
const items = ["0", "1", "2", "3", "4", "5", "6", "7", "8"];
const disabledIndexes = [1, 4];
const { result } = renderHookForTest({
items,
numberOfItemsInLine: 3,
focusItemIndexOnMount: 0,
focusOnMount: true,
disabledIndexes
});

act(() => {
fireEvent.keyDown(element, { key: "ArrowRight" }); // moving right from index 0 should skip disabled index 1, and set activeIndex to 2
});

expect(result.current.activeIndex).toBe(2);
});

describe("focusItemIndexOnMount", () => {
it("should set the active index according to focusItemIndexOnMount on mount, when focusOnMount is true", () => {
const items = ["a", "b", "c", "d"];
Expand Down Expand Up @@ -138,7 +156,8 @@ describe("useGridKeyboardNavigation", () => {
numberOfItemsInLine = 3,
onItemClicked = jest.fn(),
focusOnMount = false,
focusItemIndexOnMount = undefined
focusItemIndexOnMount = undefined,
disabledIndexes = []
}) {
const itemsCount = items.length;
const getItemByIndex = index => items[index];
Expand All @@ -154,7 +173,8 @@ describe("useGridKeyboardNavigation", () => {
onItemClicked,
focusOnMount,
numberOfItemsInLine,
focusItemIndexOnMount
focusItemIndexOnMount,
disabledIndexes
})
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function getActiveIndexFromInboundNavigation({ direction, numberOfItemsIn
return Math.max(0, Math.min(rawIndex, itemsCount - 1));
}

export function calcActiveIndexAfterArrowNavigation({ activeIndex, itemsCount, numberOfItemsInLine, direction }) {
function calcRawNewIndexAfterArrowNavigation({ activeIndex, itemsCount, numberOfItemsInLine, direction }) {
const getIndexLine = index => Math.ceil((index + 1) / numberOfItemsInLine);

const horizontalChange = isIndexIncrease => {
Expand Down Expand Up @@ -72,3 +72,23 @@ export function calcActiveIndexAfterArrowNavigation({ activeIndex, itemsCount, n
return verticalChange(false);
}
}

export function calcActiveIndexAfterArrowNavigation({
activeIndex,
itemsCount,
numberOfItemsInLine,
direction,
disabledIndexes = []
}) {
let result = calcRawNewIndexAfterArrowNavigation({ activeIndex, itemsCount, numberOfItemsInLine, direction });
while (!result.isOutbound && disabledIndexes.includes(result.nextIndex)) {
result = calcRawNewIndexAfterArrowNavigation({
activeIndex: result.nextIndex,
itemsCount,
numberOfItemsInLine,
direction
});
}

return result;
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const NO_ACTIVE_INDEX = -1;
* @param {function} options.getItemByIndex - a function which gets an index as a param, and returns the item on that index
* @param {boolean=} options.focusOnMount - if true, the referenced element will be focused when mounted
* @param {number=} options.focusItemIndexOnMount - optional item index to focus when mounted. Only works with "options.focusOnMount".
* @param {number[]=} options.disabledIndexes - optional array of disabled indices, which will be skipped while navigating.
* @returns {useGridKeyboardNavigationResult}
*/
export default function useGridKeyboardNavigation({
Expand All @@ -35,7 +36,8 @@ export default function useGridKeyboardNavigation({
onItemClicked, // the callback to call when an item is selected
focusOnMount = false,
getItemByIndex = () => {},
focusItemIndexOnMount = NO_ACTIVE_INDEX
focusItemIndexOnMount = NO_ACTIVE_INDEX,
disabledIndexes = []
}) {
const [isInitialActiveState, setIsInitialActiveState] = useState(
focusOnMount && focusItemIndexOnMount !== NO_ACTIVE_INDEX
Expand All @@ -55,7 +57,8 @@ export default function useGridKeyboardNavigation({
activeIndex,
itemsCount,
numberOfItemsInLine,
direction
direction,
disabledIndexes
});
if (isOutbound) {
keyboardContext?.onOutboundNavigation(ref, direction);
Expand Down

0 comments on commit 82588de

Please sign in to comment.