Skip to content

Commit

Permalink
refactor(utils): restructure browser utils
Browse files Browse the repository at this point in the history
  • Loading branch information
gcornut committed Feb 4, 2025
1 parent 9078308 commit 17a2ad0
Show file tree
Hide file tree
Showing 33 changed files with 65 additions and 108 deletions.
2 changes: 1 addition & 1 deletion packages/lumx-react/src/components/chip/Chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useStopPropagation } from '@lumx/react/hooks/useStopPropagation';

import { GenericProps, HasTheme } from '@lumx/react/utils/type';
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
import { onEnterPressed } from '@lumx/react/utils/event';
import { onEnterPressed } from '@lumx/react/utils/browser/event';
import { forwardRef } from '@lumx/react/utils/react/forwardRef';
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { parseLocale } from '@lumx/react/utils/locale/parseLocale';
import { Locale } from '@lumx/react/utils/locale/types';
import { usePreviousValue } from '@lumx/react/hooks/usePreviousValue';
import { getYearDisplayName } from '@lumx/react/utils/date/getYearDisplayName';
import { onEnterPressed } from '@lumx/react/utils/event';
import { onEnterPressed } from '@lumx/react/utils/browser/event';
import { addMonthResetDay } from '@lumx/react/utils/date/addMonthResetDay';
import { formatDayNumber } from '@lumx/react/utils/date/formatDayNumber';
import { VISUALLY_HIDDEN } from '@lumx/react/constants';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { commonTestsSuiteRTL, SetupRenderOptions } from '@lumx/react/testing/uti
import { queryByRole, render, screen } from '@testing-library/react';
import { getByClassName, queryByClassName } from '@lumx/react/testing/utils/queries';
import userEvent from '@testing-library/user-event';
import { isFocusVisible } from '@lumx/react/utils/isFocusVisible';
import { isFocusVisible } from '@lumx/react/utils/browser/isFocusVisible';

import { useBooleanState } from '@lumx/react/hooks/useBooleanState';
import { ExpansionPanel, ExpansionPanelProps } from '.';

const CLASSNAME = ExpansionPanel.className as string;

jest.mock('@lumx/react/utils/isFocusVisible');
jest.mock('@lumx/react/utils/browser/isFocusVisible');

const mockChildrenContent = 'children content';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { SlideshowItem, Thumbnail } from '@lumx/react';
import { useMergeRefs } from '@lumx/react/utils/mergeRefs';
import { useSizeOnWindowResize } from '@lumx/react/hooks/useSizeOnWindowResize';
import { useImageSize } from '@lumx/react/hooks/useImageSize';
import { getPrefersReducedMotion } from '@lumx/react/utils/browser/getPrefersReducedMotion';
import { isReducedMotion } from '@lumx/react/utils/browser/isReducedMotion';
import { isEqual } from '@lumx/react/utils/object/isEqual';

import { CLASSNAME } from '../constants';
Expand Down Expand Up @@ -97,7 +97,7 @@ export const ImageSlide = React.memo((props: ImageSlideProps) => {
maxWidth: scrollAreaSize?.width,
}),
// Only animate when scale is set, and we are not pointer zooming and the user does not prefer reduced motion
transition: scale && !isPointerZooming && !getPrefersReducedMotion() ? 'all 250ms' : undefined,
transition: scale && !isPointerZooming && !isReducedMotion() ? 'all 250ms' : undefined,
},
}}
loadingPlaceholderImageRef={loadingPlaceholderImageRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from 'react';

import memoize from 'lodash/memoize';

import { startViewTransition } from '@lumx/react/utils/DOM/startViewTransition';
import { findImage } from '@lumx/react/utils/DOM/findImage';
import { startViewTransition } from '@lumx/react/utils/browser/DOM/startViewTransition';
import { findImage } from '@lumx/react/utils/browser/DOM/findImage';

import type { ImageLightboxProps } from './types';
import { CLASSNAME } from './constants';
Expand Down
2 changes: 1 addition & 1 deletion packages/lumx-react/src/components/list/ListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import isEmpty from 'lodash/isEmpty';

import { ListProps, Size } from '@lumx/react';
import { GenericProps } from '@lumx/react/utils/type';
import { onEnterPressed, onButtonPressed } from '@lumx/react/utils/event';
import { onEnterPressed, onButtonPressed } from '@lumx/react/utils/browser/event';
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
import { renderLink } from '@lumx/react/utils/renderLink';
import { forwardRef } from '@lumx/react/utils/react/forwardRef';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import userEvent from '@testing-library/user-event';
import { PopoverDialog } from './PopoverDialog';
import { WithButtonTrigger, WithIconButtonTrigger } from './PopoverDialog.stories';

jest.mock('@lumx/react/utils/isFocusVisible');
jest.mock('@lumx/react/utils/browser/isFocusVisible');

describe(`<${PopoverDialog.displayName}>`, () => {
it('should open and init focus', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { getFirstAndLastFocusable } from '@lumx/react/utils/focus/getFirstAndLastFocusable';
import { getFirstAndLastFocusable } from '@lumx/react/utils/browser/focus/getFirstAndLastFocusable';
import { OnBeforeUnmount } from '@lumx/react/utils/OnBeforeUnmount';
import type { PopoverProps } from './Popover';

Expand Down
4 changes: 2 additions & 2 deletions packages/lumx-react/src/components/select/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import { getByClassName, queryAllByClassName, queryByClassName } from '@lumx/rea
import { render, within } from '@testing-library/react';
import { commonTestsSuiteRTL, SetupRenderOptions } from '@lumx/react/testing/utils';
import userEvent from '@testing-library/user-event';
import { isFocusVisible } from '@lumx/react/utils/isFocusVisible';
import { isFocusVisible } from '@lumx/react/utils/browser/isFocusVisible';

import { Select, SelectProps, SelectVariant } from './Select';

const CLASSNAME = Select.className as string;

jest.mock('@lumx/react/utils/isFocusVisible');
jest.mock('@lumx/react/utils/browser/isFocusVisible');
jest.mock('@lumx/react/hooks/useId', () => ({ useId: () => ':r1:' }));

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect } from 'react';
import { getFocusableElements } from '@lumx/react/utils/focus/getFocusableElements';
import { getFocusableElements } from '@lumx/react/utils/browser/focus/getFocusableElements';

export interface UseSlideFocusManagementProps {
isSlideDisplayed?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import {
import partition from 'lodash/partition';
import userEvent from '@testing-library/user-event';

import { isFocusVisible } from '@lumx/react/utils/isFocusVisible';
import { isFocusVisible } from '@lumx/react/utils/browser/isFocusVisible';
import { TextField, TextFieldProps } from './TextField';

const CLASSNAME = TextField.className as string;

jest.mock('@lumx/react/utils/isFocusVisible');
jest.mock('@lumx/react/utils/browser/isFocusVisible');

/**
* Mounts the component and returns common DOM elements / data needed in multiple tests further down.
Expand Down
4 changes: 2 additions & 2 deletions packages/lumx-react/src/components/tooltip/Tooltip.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { screen, render } from '@testing-library/react';
import { queryAllByTagName, queryByClassName } from '@lumx/react/testing/utils/queries';
import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
import userEvent from '@testing-library/user-event';
import { isFocusVisible } from '@lumx/react/utils/isFocusVisible';
import { isFocusVisible } from '@lumx/react/utils/browser/isFocusVisible';
import { VISUALLY_HIDDEN } from '@lumx/react/constants';

import { Tooltip, TooltipProps } from './Tooltip';

const CLASSNAME = Tooltip.className as string;

jest.mock('@lumx/react/utils/isFocusVisible');
jest.mock('@lumx/react/utils/browser/isFocusVisible');
jest.mock('@lumx/react/hooks/useId', () => ({ useId: () => ':r1:' }));
// Skip delays
jest.mock('@lumx/react/constants', () => ({
Expand Down
6 changes: 3 additions & 3 deletions packages/lumx-react/src/components/tooltip/useTooltipOpen.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { MutableRefObject, useEffect, useRef, useState } from 'react';
import { browserDoesNotSupportHover } from '@lumx/react/utils/browserDoesNotSupportHover';
import { isHoverNotSupported } from '@lumx/react/utils/browser/isHoverNotSupported';
import { IS_BROWSER, TOOLTIP_HOVER_DELAY, TOOLTIP_LONG_PRESS_DELAY } from '@lumx/react/constants';
import { useCallbackOnEscape } from '@lumx/react/hooks/useCallbackOnEscape';
import { isFocusVisible } from '@lumx/react/utils/isFocusVisible';
import { isFocusVisible } from '@lumx/react/utils/browser/isFocusVisible';

/**
* Hook controlling tooltip visibility using mouse hover the anchor and delay.
Expand Down Expand Up @@ -39,7 +39,7 @@ export function useTooltipOpen(delay: number | undefined, anchorElement: HTMLEle
else timer = setTimeout(update, duration) as any;
};

const hoverNotSupported = browserDoesNotSupportHover();
const hoverNotSupported = isHoverNotSupported();
const hasTouch = 'ontouchstart' in window;

// Adapt open/close delay
Expand Down
2 changes: 1 addition & 1 deletion packages/lumx-react/src/hooks/useCallbackOnEscape.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DOCUMENT } from '@lumx/react/constants';
import { Callback } from '@lumx/react/utils/type';
import { onEscapePressed } from '@lumx/react/utils/event';
import { onEscapePressed } from '@lumx/react/utils/browser/event';
import { useEffect } from 'react';
import { Listener, makeListenerTowerContext } from '@lumx/react/utils/function/makeListenerTowerContext';

Expand Down
2 changes: 1 addition & 1 deletion packages/lumx-react/src/hooks/useFocusTrap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect } from 'react';

import { DOCUMENT } from '@lumx/react/constants';
import { getFirstAndLastFocusable } from '@lumx/react/utils/focus/getFirstAndLastFocusable';
import { getFirstAndLastFocusable } from '@lumx/react/utils/browser/focus/getFirstAndLastFocusable';
import { Falsy } from '@lumx/react/utils/type';
import { Listener, makeListenerTowerContext } from '@lumx/react/utils/function/makeListenerTowerContext';

Expand Down
4 changes: 2 additions & 2 deletions packages/lumx-react/src/hooks/useTransitionVisibility.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RefObject, useEffect, useRef, useState } from 'react';
import { userHasReducedMotion } from '@lumx/react/utils/userHasReducedMotion';
import { isReducedMotion } from '@lumx/react/utils/browser/isReducedMotion';

/**
* Returns true if the component is visible tracking the opacity transition.
Expand Down Expand Up @@ -28,7 +28,7 @@ export const useTransitionVisibility = (

// Transition event is not supported or the user prefers reduced motion.
// => Skip and set visibility to false directly.
if (!element || !window.TransitionEvent || userHasReducedMotion()) {
if (!element || !window.TransitionEvent || isReducedMotion()) {
setVisible(false);
return undefined;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import ReactDOM from 'react-dom';

import { MaybeElementOrRef } from '@lumx/react/utils/type';

import { unref } from '../react/unref';
import { getPrefersReducedMotion } from '../browser/getPrefersReducedMotion';
import { unref } from '../../react/unref';
import { isReducedMotion } from '../isReducedMotion';

function setupViewTransitionName(elementRef: MaybeElementOrRef<HTMLElement>, name: string) {
let originalName: string | null = null;
Expand Down Expand Up @@ -41,7 +41,7 @@ export async function startViewTransition({
};
}) {
const start = (document as any)?.startViewTransition?.bind(document);
const prefersReducedMotion = getPrefersReducedMotion();
const prefersReducedMotion = isReducedMotion();
const { flushSync } = ReactDOM as any;
if (prefersReducedMotion || !start || !flushSync || !viewTransitionName?.source || !viewTransitionName?.target) {
// Skip, apply changes without a transition
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getFirstAndLastFocusable } from '@lumx/react/utils/focus/getFirstAndLastFocusable';
import { getFirstAndLastFocusable } from './getFirstAndLastFocusable';

function htmlToElement(html: string): any {
const template = document.createElement('template');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getFocusableElements } from '@lumx/react/utils/focus/getFocusableElements';
import { getFocusableElements } from './getFocusableElements';

function htmlToElement(html: string): any {
const template = document.createElement('template');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { browserDoesNotSupportHover } from '@lumx/react/utils/browserDoesNotSupportHover';
import { isHoverNotSupported } from './isHoverNotSupported';

const originalMatchMedia = global.matchMedia;

describe('browserDoesNotSupportHover', () => {
describe('isHoverNotSupported', () => {
afterAll(() => {
global.matchMedia = originalMatchMedia;
});

it('should return `false` on browsers that do not support matchMedia', () => {
global.matchMedia = undefined;
expect(browserDoesNotSupportHover()).toBe(false);
expect(isHoverNotSupported()).toBe(false);
});

it('should return `false` on browsers that support matchMedia and does support hover', () => {
global.matchMedia = () => ({ matches: false });
expect(browserDoesNotSupportHover()).toBe(false);
expect(isHoverNotSupported()).toBe(false);
});

it('should return `true` on browsers that support matchMedia and does not support hover', () => {
global.matchMedia = () => ({ matches: true });
expect(browserDoesNotSupportHover()).toBe(true);
expect(isHoverNotSupported()).toBe(true);
});
});
2 changes: 2 additions & 0 deletions packages/lumx-react/src/utils/browser/isHoverNotSupported.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** Return true if the browser does not support pointer hover */
export const isHoverNotSupported = (): boolean => !!window.matchMedia?.('(hover: none)').matches;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { WINDOW } from '@lumx/react/constants';

/** Check if user prefers reduced motion */
export function getPrefersReducedMotion() {
export function isReducedMotion() {
return WINDOW?.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
}
2 changes: 0 additions & 2 deletions packages/lumx-react/src/utils/browserDoesNotSupportHover.ts

This file was deleted.

15 changes: 0 additions & 15 deletions packages/lumx-react/src/utils/isInternetExplorer.ts

This file was deleted.

27 changes: 27 additions & 0 deletions packages/lumx-react/src/utils/partitionMulti.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import partition from 'lodash/partition';
import { partitionMulti } from './partitionMulti';

describe('partitionMulti', () => {
it('should act like partition for single predicate', () => {
const data = [0, 1, 2, 3, 4, 5];
const isEven = (n: number): boolean => n % 2 === 0;

const expected = partition(data, isEven);
const actual = partitionMulti(data, [isEven]);

expect(actual).toEqual(expected);
});

it('should partition on multiple predicates', () => {
type T = string | number | boolean;
const data: T[] = ['a', 1, 'b', false, true];
const isString = (s: T): boolean => typeof s === 'string';
const isNumber = (s: T): boolean => typeof s === 'number';

const [strings, numbers, others] = partitionMulti(data, [isString, isNumber]);

expect(strings).toEqual(['a', 'b']);
expect(numbers).toEqual([1]);
expect(others).toEqual([false, true]);
});
});
7 changes: 0 additions & 7 deletions packages/lumx-react/src/utils/userHasReducedMotion.ts

This file was deleted.

48 changes: 0 additions & 48 deletions packages/lumx-react/src/utils/utils.test.ts

This file was deleted.

0 comments on commit 17a2ad0

Please sign in to comment.