Skip to content

Commit

Permalink
Merge pull request #3390 (sync release-22.x -> release-23.x)
Browse files Browse the repository at this point in the history
The idea here is to get `release-23.x` updated to include everything from `release-22.x` before #3367 landed, so the conflict resolution needed to land those changes in `release-23.x` can be isolated.
  • Loading branch information
brian-smith-tcril authored Jan 23, 2025
2 parents 81a0abf + b2da298 commit 2117eb6
Show file tree
Hide file tree
Showing 14 changed files with 147 additions and 85 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/sync-22-23.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Sync 23.x with 22.x
on:
push:
branches:
- release-22.x
workflow_dispatch:

jobs:
sync-branches:
runs-on: ubuntu-latest
name: Syncing branches
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Create Pull Request
id: cpr
uses: tretuna/[email protected]
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FROM_BRANCH: release-22.x
TO_BRANCH: release-23.x
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ window.ResizeObserver = window.ResizeObserver
}));

function TestComponent() {
const [containerElementRef, setContainerElementRef] = React.useState(null);
const overflowElementRef = React.useRef(null);
const [containerElementRef, setContainerElementRef] = React.useState<HTMLDivElement | null>(null);
const overflowElementRef = React.useRef<HTMLDivElement>(null);
const indexOfLastVisibleChild = useIndexOfLastVisibleChild(containerElementRef, overflowElementRef.current);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { useToggle } from '../..';
import { ToggleHandlers } from '../useToggle';

const TOGGLE_IS_ON = 'on';
const TOGGLE_IS_OFF = 'off';
Expand All @@ -19,7 +20,7 @@ const resetHandlerMocks = () => {
};

// eslint-disable-next-line react/prop-types
function FakeComponent({ defaultIsOn, handlers }) {
function FakeComponent({ defaultIsOn, handlers }: { defaultIsOn: boolean, handlers: ToggleHandlers }) {
const [isOn, setOn, setOff, toggle] = useToggle(defaultIsOn, handlers);

return (
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import { useRef, useEffect } from 'react';

/**
* A React hook to enable arrow key navigation on a component.
*/
interface HandleEnterArgs {
event: KeyboardEvent;
currentIndex: number;
activeElement: HTMLElement;
}

function handleEnter({ event, currentIndex, activeElement }) {
function handleEnter({ event, currentIndex, activeElement }: HandleEnterArgs) {
if (currentIndex === -1) { return; }
activeElement.click();
event.preventDefault();
}

function handleArrowKey({ event, currentIndex, availableElements }) {
interface HandleArrowKeyArgs {
event: KeyboardEvent;
currentIndex: number;
availableElements: NodeListOf<HTMLElement>;
}

function handleArrowKey({ event, currentIndex, availableElements }: HandleArrowKeyArgs) {
// If the focus isn't in the container, focus on the first thing
if (currentIndex === -1) { availableElements[0].focus(); }

Expand All @@ -36,6 +44,13 @@ function handleArrowKey({ event, currentIndex, availableElements }) {
event.preventDefault();
}

interface HandleEventsArgs {
event: KeyboardEvent;
ignoredKeys?: string[];
parentNode: HTMLElement | undefined;
selectors?: string;
}

/**
* Implement arrow key navigation for the given parentNode
*/
Expand All @@ -44,7 +59,7 @@ function handleEvents({
ignoredKeys = [],
parentNode,
selectors = 'a,button,input',
}) {
}: HandleEventsArgs) {
if (!parentNode) { return; }

const { key } = event;
Expand All @@ -60,7 +75,7 @@ function handleEvents({
if (!parentNode.contains(activeElement)) { return; }

// Get the list of elements we're allowed to scroll through
const availableElements = parentNode.querySelectorAll(selectors);
const availableElements = parentNode.querySelectorAll<HTMLElement>(selectors);

// No elements are available to loop through.
if (!availableElements.length) { return; }
Expand All @@ -70,18 +85,27 @@ function handleEvents({
(availableElement) => availableElement === activeElement,
);

if (key === 'Enter') {
handleEnter({ event, currentIndex, activeElement });
if (key === 'Enter' && activeElement) {
handleEnter({ event, currentIndex, activeElement: activeElement as HTMLElement });
}
handleArrowKey({ event, currentIndex, availableElements });
}

export default function useArrowKeyNavigation(props) {
const { selectors, ignoredKeys } = props || {};
const parentNode = useRef();
export interface ArrowKeyNavProps {
/** e.g. 'a,button,input' */
selectors?: string;
ignoredKeys?: string[];
}

/**
* A React hook to enable arrow key navigation on a component.
*/
export default function useArrowKeyNavigation(props: ArrowKeyNavProps = {}) {
const { selectors, ignoredKeys } = props;
const parentNode = useRef<HTMLElement>();

useEffect(() => {
const eventHandler = (event) => {
const eventHandler = (event: KeyboardEvent) => {
handleEvents({
event, ignoredKeys, parentNode: parentNode.current, selectors,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,32 @@ import { useLayoutEffect, useState } from 'react';
* that fits within its bounding rectangle. This is done by summing the widths
* of the children until they exceed the width of the container.
*
* @param {Element} containerElementRef - container element
* @param {Element} overflowElementRef - overflow element
*
* The hook returns the index of the last visible child.
*
* @param containerElementRef - container element
* @param overflowElementRef - overflow element
*/
const useIndexOfLastVisibleChild = (containerElementRef, overflowElementRef) => {
const useIndexOfLastVisibleChild = (
containerElementRef: HTMLElement | null,
overflowElementRef: HTMLElement | null,
): number => {
const [indexOfLastVisibleChild, setIndexOfLastVisibleChild] = useState(-1);

useLayoutEffect(() => {
if (!containerElementRef) {
return undefined;
}

function updateLastVisibleChildIndex() {
// Get array of child nodes from NodeList form
const childNodesArr = Array.prototype.slice.call(containerElementRef.children);
const childNodesArr = Array.prototype.slice.call(containerElementRef!.children);
const { nextIndexOfLastVisibleChild } = childNodesArr
// filter out the overflow element
.filter(childNode => childNode !== overflowElementRef)
// sum the widths to find the last visible element's index
.reduce((acc, childNode, index) => {
acc.sumWidth += childNode.getBoundingClientRect().width;
if (acc.sumWidth <= containerElementRef.getBoundingClientRect().width) {
if (acc.sumWidth <= containerElementRef!.getBoundingClientRect().width) {
acc.nextIndexOfLastVisibleChild = index;
}
return acc;
Expand All @@ -32,23 +39,18 @@ const useIndexOfLastVisibleChild = (containerElementRef, overflowElementRef) =>
// sometimes we'll show a dropdown with one item in it when it would fit,
// but allowing this case dramatically simplifies the calculations we need
// to do above.
sumWidth: overflowElementRef ? overflowElementRef.getBoundingClientRect().width : 0,
sumWidth: overflowElementRef?.getBoundingClientRect().width ?? 0,
nextIndexOfLastVisibleChild: -1,
});

setIndexOfLastVisibleChild(nextIndexOfLastVisibleChild);
}

if (containerElementRef) {
updateLastVisibleChildIndex();

const resizeObserver = new ResizeObserver(() => updateLastVisibleChildIndex());
resizeObserver.observe(containerElementRef);

return () => resizeObserver.disconnect();
}
updateLastVisibleChildIndex();

return undefined;
const resizeObserver = new ResizeObserver(() => updateLastVisibleChildIndex());
resizeObserver.observe(containerElementRef);
return () => resizeObserver.disconnect();
}, [containerElementRef, overflowElementRef]);

return indexOfLastVisibleChild;
Expand Down
9 changes: 6 additions & 3 deletions src/hooks/useIsVisible.jsx → src/hooks/useIsVisible.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { useRef, useState, useEffect } from 'react';
import React, { useRef, useState, useEffect } from 'react';

const useIsVisible = (defaultIsVisible = true) => {
const sentinelRef = useRef();
const useIsVisible = (defaultIsVisible = true): [
isVisible: boolean,
sentinelRef: React.MutableRefObject<HTMLElement | null>,
] => {
const sentinelRef = useRef<HTMLElement | null>(null);
const [isVisible, setIsVisible] = useState(defaultIsVisible);

useEffect(() => {
Expand Down
37 changes: 0 additions & 37 deletions src/hooks/useToggle.jsx

This file was deleted.

38 changes: 38 additions & 0 deletions src/hooks/useToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useState, useCallback } from 'react';

export type Toggler = [
isOn: boolean,
setOn: () => void,
setOff: () => void,
toggle: () => void,
];

export interface ToggleHandlers {
handleToggleOn?: () => void;
handleToggleOff?: () => void;
handleToggle?: (newStatus: boolean) => void;
}

export default function useToggle(defaultIsOn = false, handlers: ToggleHandlers = {}): Toggler {
const { handleToggleOn, handleToggleOff, handleToggle } = handlers;
const [isOn, setIsOn] = useState(defaultIsOn);

const setOn = useCallback(() => {
setIsOn(true);
handleToggleOn?.();
handleToggle?.(true);
}, [handleToggleOn, handleToggle]);

const setOff = useCallback(() => {
setIsOn(false);
handleToggleOff?.();
handleToggle?.(false);
}, [handleToggleOff, handleToggle]);

const toggle = useCallback(() => {
const doToggle = isOn ? setOff : setOn;
doToggle();
}, [isOn, setOn, setOff]);

return [isOn, setOn, setOff, toggle];
}
9 changes: 7 additions & 2 deletions src/hooks/useWindowSize.jsx → src/hooks/useWindowSize.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { useState, useLayoutEffect } from 'react';

function useWindowSize() {
export interface WindowSizeData {
width: number | undefined;
height: number | undefined;
}

function useWindowSize(): WindowSizeData {
// Initialize state with undefined width/height so server and client renders match
// Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/
const [windowSize, setWindowSize] = useState({
const [windowSize, setWindowSize] = useState<WindowSizeData>({
width: undefined,
height: undefined,
});
Expand Down
10 changes: 5 additions & 5 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export { default as ModalLayer } from './Modal/ModalLayer';
export { default as Overlay, OverlayTrigger } from './Overlay';
export { default as Portal } from './Modal/Portal';
export { default as Tooltip } from './Tooltip';
export { default as useWindowSize, type WindowSizeData } from './hooks/useWindowSize';
export { default as useToggle, type Toggler, type ToggleHandlers } from './hooks/useToggle';
export { default as useArrowKeyNavigation, type ArrowKeyNavProps } from './hooks/useArrowKeyNavigation';
export { default as useIndexOfLastVisibleChild } from './hooks/useIndexOfLastVisibleChild';
export { default as useIsVisible } from './hooks/useIsVisible';

// // // // // // // // // // // // // // // // // // // // // // // // // // //
// Things that don't have types
Expand Down Expand Up @@ -187,11 +192,6 @@ export const Sticky: any; // from './Sticky';
export const SelectableBox: any; // from './SelectableBox';
export const breakpoints: any; // from './utils/breakpoints';
export const Variant: any; // from './utils/constants';
export const useWindowSize: any; // from './hooks/useWindowSize';
export const useToggle: any; // from './hooks/useToggle';
export const useArrowKeyNavigation: any; // from './hooks/useArrowKeyNavigation';
export const useIndexOfLastVisibleChild: any; // from './hooks/useIndexOfLastVisibleChild';
export const useIsVisible: any; // from './hooks/useIsVisible';
export const
OverflowScrollContext: any,
OverflowScroll: any,
Expand Down
10 changes: 5 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export { default as ModalLayer } from './Modal/ModalLayer';
export { default as Overlay, OverlayTrigger } from './Overlay';
export { default as Portal } from './Modal/Portal';
export { default as Tooltip } from './Tooltip';
export { default as useWindowSize } from './hooks/useWindowSize';
export { default as useToggle } from './hooks/useToggle';
export { default as useArrowKeyNavigation } from './hooks/useArrowKeyNavigation';
export { default as useIndexOfLastVisibleChild } from './hooks/useIndexOfLastVisibleChild';
export { default as useIsVisible } from './hooks/useIsVisible';

// // // // // // // // // // // // // // // // // // // // // // // // // // //
// Things that don't have types
Expand Down Expand Up @@ -159,11 +164,6 @@ export { default as Sticky } from './Sticky';
export { default as SelectableBox } from './SelectableBox';
export { default as breakpoints } from './utils/breakpoints';
export { default as Variant } from './utils/constants';
export { default as useWindowSize } from './hooks/useWindowSize';
export { default as useToggle } from './hooks/useToggle';
export { default as useArrowKeyNavigation } from './hooks/useArrowKeyNavigation';
export { default as useIndexOfLastVisibleChild } from './hooks/useIndexOfLastVisibleChild';
export { default as useIsVisible } from './hooks/useIsVisible';
export {
OverflowScrollContext,
OverflowScroll,
Expand Down
1 change: 1 addition & 0 deletions www/gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const plugins = [
},
},
'gatsby-plugin-react-helmet',
'gatsby-plugin-typescript',
{
resolve: 'gatsby-plugin-manifest',
options: {
Expand Down
2 changes: 1 addition & 1 deletion www/src/components/header/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Search from '../Search';

export interface INavbar {
siteTitle: string,
onMenuClick: () => boolean,
onMenuClick: () => void,
setTarget: React.Dispatch<React.SetStateAction<HTMLButtonElement | null>>,
onSettingsClick?: () => void,
menuIsOpen?: boolean,
Expand Down

0 comments on commit 2117eb6

Please sign in to comment.