UNPKG

@plone/components

Version:

ReactJS components for Plone

156 lines (139 loc) 4.48 kB
import { useMemo, useLayoutEffect, useCallback, useRef, useState, type RefCallback, type ReactNode, type CSSProperties, } from 'react'; import { composeRenderProps } from 'react-aria-components'; import { twMerge } from 'tailwind-merge'; import { tv } from 'tailwind-variants'; import type { AriaLabelingProps, DOMProps as SharedDOMProps, } from '@react-types/shared'; export const focusRing = tv({ base: 'outline-quanta-cobalt outline forced-colors:outline-[Highlight]', variants: { isFocusVisible: { false: 'outline-0', true: 'outline-3', }, }, }); export function composeTailwindRenderProps<T>( className: string | ((v: T) => string) | undefined, tw: string, ): string | ((v: T) => string) { return composeRenderProps(className, (className) => twMerge(tw, className)); } // From https://github.com/adobe/react-spectrum/blob/main/packages/react-aria-components/src/utils.tsx export interface StyleRenderProps<T> { /** The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the element. A function may be provided to compute the class based on component state. */ className?: | string | ((values: T & { defaultClassName: string | undefined }) => string); /** The inline [style](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style) for the element. A function may be provided to compute the style based on component state. */ style?: | CSSProperties | (( values: T & { defaultStyle: CSSProperties }, ) => CSSProperties | undefined); } export interface RenderProps<T> extends StyleRenderProps<T> { /** The children of the component. A function may be provided to alter the children based on component state. */ children?: | ReactNode | ((values: T & { defaultChildren: ReactNode | undefined }) => ReactNode); } interface RenderPropsHookOptions<T> extends RenderProps<T>, SharedDOMProps, AriaLabelingProps { values: T; defaultChildren?: ReactNode; defaultClassName?: string; defaultStyle?: CSSProperties; } interface RenderPropsHookRetVal { className?: string; style?: CSSProperties; children?: ReactNode; 'data-rac': string; } export function useRenderProps<T>( props: RenderPropsHookOptions<T>, ): RenderPropsHookRetVal { const { className, style, children, defaultClassName = undefined, defaultChildren = undefined, defaultStyle, values, } = props; return useMemo(() => { let computedClassName: string | undefined; let computedStyle: React.CSSProperties | undefined; let computedChildren: React.ReactNode | undefined; if (typeof className === 'function') { computedClassName = className({ ...values, defaultClassName }); } else { computedClassName = className; } if (typeof style === 'function') { computedStyle = style({ ...values, defaultStyle: defaultStyle || {} }); } else { computedStyle = style; } if (typeof children === 'function') { computedChildren = children({ ...values, defaultChildren }); } else if (children == null) { computedChildren = defaultChildren; } else { computedChildren = children; } return { className: computedClassName ?? defaultClassName, style: computedStyle || defaultStyle ? { ...defaultStyle, ...computedStyle } : undefined, children: computedChildren ?? defaultChildren, 'data-rac': '', }; }, [ className, style, children, defaultClassName, defaultChildren, defaultStyle, values, ]); } export function useSlot( initialState: boolean | (() => boolean) = true, ): [RefCallback<Element>, boolean] { // Initial state is typically based on the parent having an aria-label or aria-labelledby. // If it does, this value should be false so that we don't update the state and cause a rerender when we go through the layoutEffect const [hasSlot, setHasSlot] = useState(initialState); const hasRun = useRef(false); // A callback ref which will run when the slotted element mounts. // This should happen before the useLayoutEffect below. const ref = useCallback((el: any) => { hasRun.current = true; setHasSlot(!!el); }, []); // If the callback hasn't been called, then reset to false. useLayoutEffect(() => { if (!hasRun.current) { setHasSlot(false); } }, []); return [ref, hasSlot]; }