Skip to content

Commit

Permalink
refactor(utils): split type utils into modules
Browse files Browse the repository at this point in the history
  • Loading branch information
gcornut committed Jan 31, 2025
1 parent 4a73b4a commit a9c0442
Show file tree
Hide file tree
Showing 21 changed files with 183 additions and 164 deletions.
164 changes: 0 additions & 164 deletions packages/lumx-react/src/utils/type.ts

This file was deleted.

4 changes: 4 additions & 0 deletions packages/lumx-react/src/utils/type/Callback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Callback function type alias (use for readability)
*/
export type Callback = () => void;
14 changes: 14 additions & 0 deletions packages/lumx-react/src/utils/type/Comp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { ReactElement, Ref } from 'react';

/** LumX Component Type. */
export type Comp<P, T = HTMLElement> = {
(props: P & { ref?: Ref<T> }): ReactElement | null;
/** React component type. */
readonly $$typeof: symbol;
/** Component default props. */
defaultProps?: Partial<P>;
/** Component name. */
displayName?: string;
/** Component base class name. */
className?: string;
};
16 changes: 16 additions & 0 deletions packages/lumx-react/src/utils/type/ComponentRef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type React from 'react';
import type { Comp } from './Comp';

/**
* Extract ref type for a component or JSX intrinsic element
*
* @example ComponentRef<'div'> => React.Ref<HTMLDivElement>
* @example ComponentRef<Button> => React.Ref<HTMLButtonElement
*/
export type ComponentRef<C> = C extends keyof JSX.IntrinsicElements
? JSX.IntrinsicElements[C]['ref']
: C extends Comp<any, infer T>
? React.Ref<T>
: C extends React.JSXElementConstructor<{ ref?: infer R }>
? R
: never;
5 changes: 5 additions & 0 deletions packages/lumx-react/src/utils/type/Falsy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* JS falsy values.
* (excluding `NaN` as it can't be distinguished from `number`)
*/
export type Falsy = false | undefined | null | 0 | '';
11 changes: 11 additions & 0 deletions packages/lumx-react/src/utils/type/GenericProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { HasClassName } from './HasClassName';

/**
* Define a generic props types.
*/
export interface GenericProps extends HasClassName {
/**
* Any prop (particularly any supported prop for a HTML element).
*/
[propName: string]: any;
}
19 changes: 19 additions & 0 deletions packages/lumx-react/src/utils/type/HasAriaLabelOrLabelledBy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Require either `aria-label` or `arial-labelledby` prop.
* If none are set, the order will prioritize `aria-labelledby` over `aria-label` as it
* needs a visible element.
*/
export type HasAriaLabelOrLabelledBy<T = string | undefined> = T extends string
? {
/**
* The id of the element to use as title of the dialog. Can be within or out of the dialog.
* Although it is not recommended, aria-label can be used instead if no visible element is available.
*/
'aria-labelledby': T;
/** The label of the dialog. */
'aria-label'?: undefined;
}
: {
'aria-label': string;
'aria-labelledby'?: undefined;
};
6 changes: 6 additions & 0 deletions packages/lumx-react/src/utils/type/HasClassName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface HasClassName {
/**
* Class name forwarded to the root element of the component.
*/
className?: string;
}
7 changes: 7 additions & 0 deletions packages/lumx-react/src/utils/type/HasCloseMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface HasCloseMode {
/**
* Choose how the children are hidden when closed
* ('hide' keeps the children in DOM but hide them, 'unmount' remove the children from the DOM).
*/
closeMode?: 'hide' | 'unmount';
}
8 changes: 8 additions & 0 deletions packages/lumx-react/src/utils/type/HasPolymorphicAs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';

export type HasPolymorphicAs<E extends React.ElementType> = React.ComponentPropsWithoutRef<E> & {
/**
* Customize the rendered component.
*/
as?: E;
};
8 changes: 8 additions & 0 deletions packages/lumx-react/src/utils/type/HasTheme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { Theme } from '@lumx/react';

export interface HasTheme {
/**
* Theme adapting the component to light or dark background.
*/
theme?: Theme;
}
2 changes: 2 additions & 0 deletions packages/lumx-react/src/utils/type/HeadingElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** Union type of all heading elements */
export type HeadingElement = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
6 changes: 6 additions & 0 deletions packages/lumx-react/src/utils/type/MaybeElementOrRef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type React from 'react';

/**
* Maybe a HTMLElement or a React ref of a HTMLElement
*/
export type MaybeElementOrRef<E extends HTMLElement> = E | React.RefObject<E | null> | null | undefined;
4 changes: 4 additions & 0 deletions packages/lumx-react/src/utils/type/Point.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* A point coordinate in 2D space
*/
export type Point = { x: number; y: number };
2 changes: 2 additions & 0 deletions packages/lumx-react/src/utils/type/Predicate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** Predicate function type */
export type Predicate<T> = (t: T) => boolean;
4 changes: 4 additions & 0 deletions packages/lumx-react/src/utils/type/RectSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Rectangle size
*/
export type RectSize = { width: number; height: number };
4 changes: 4 additions & 0 deletions packages/lumx-react/src/utils/type/TextElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { HeadingElement } from './HeadingElement';

/** Union type of all text elements */
export type TextElement = 'span' | 'p' | HeadingElement;
2 changes: 2 additions & 0 deletions packages/lumx-react/src/utils/type/ValueOf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** Get types of the values of a record. */
export type ValueOf<T extends Record<any, any>> = T[keyof T];
19 changes: 19 additions & 0 deletions packages/lumx-react/src/utils/type/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export { Callback } from './Callback';
export { Comp } from './Comp';
export { ComponentRef } from './ComponentRef';
export { Falsy } from './Falsy';
export { GenericProps } from './GenericProps';
export { HasAriaLabelOrLabelledBy } from './HasAriaLabelOrLabelledBy';
export { HasClassName } from './HasClassName';
export { HasCloseMode } from './HasCloseMode';
export { HasPolymorphicAs } from './HasPolymorphicAs';
export { HasTheme } from './HasTheme';
export { HeadingElement } from './HeadingElement';
export { isComponent } from './isComponent';
export { isComponentType } from './isComponentType';
export { MaybeElementOrRef } from './MaybeElementOrRef';
export { Point } from './Point';
export { Predicate } from './Predicate';
export { RectSize } from './RectSize';
export { TextElement } from './TextElement';
export { ValueOf } from './ValueOf';
33 changes: 33 additions & 0 deletions packages/lumx-react/src/utils/type/isComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { ReactElement, ReactNode } from 'react';
import get from 'lodash/get';
import type { Comp } from './Comp';

/**
* Properties of a component to use to determine it's name.
* In the order of preference.
*/
const NAME_PROPERTIES: string[] = [
'type',
'type.displayName',
'displayName',
'name',
'type.name',
'props.mdxType',
'_reactInternalFiber.elementType.name',
];
/**
* Create a predicate function that checks if a ReactNode is a react element from the given component.
*
* @param component React function component or the component name
* @return predicate returning true if value is instance of the component
*/
export const isComponent =
<C>(component: Comp<C, any> | string) =>
(instance: ReactNode): instance is ReactElement => {
const componentName = typeof component === 'string' ? component : component.displayName;

return (
!!get(instance, '$$typeof') &&
NAME_PROPERTIES.some((nameProperty: string): boolean => get(instance, nameProperty) === componentName)
);
};
9 changes: 9 additions & 0 deletions packages/lumx-react/src/utils/type/isComponentType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React, { ReactElement, ReactNode } from 'react';

/**
* Similar to `isComponent` but more precise as it's not based on the component `displayName` but on the component function reference.
*/
export const isComponentType =
(type: ReactElement['type']) =>
(node: ReactNode): node is ReactElement =>
React.isValidElement(node) && node.type === type;

0 comments on commit a9c0442

Please sign in to comment.