diff --git a/packages/react/shared.ts b/packages/react/shared.tsx similarity index 68% rename from packages/react/shared.ts rename to packages/react/shared.tsx index c889512..d8b05b8 100644 --- a/packages/react/shared.ts +++ b/packages/react/shared.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { twMerge } from "tailwind-merge"; /** @@ -88,7 +89,10 @@ type PresetType<V extends VariantType, N extends string> = { * @returns function (variantProps) -> class name, * @returns function (anyProps) -> [variantProps, otherProps] */ -export function vcn<V extends VariantType, N extends string /* Preset names */>({ +export function vcn< + V extends VariantType, + N extends string /* Preset names */, +>({ base, variants, defaults, @@ -106,7 +110,7 @@ export function vcn<V extends VariantType, N extends string /* Preset names */>( } ) => string, <AnyPropBeforeResolve extends Record<string, any>>( - anyProps: AnyPropBeforeResolve, + anyProps: AnyPropBeforeResolve ) => [ Partial<VariantKV<V>>, Omit< @@ -158,9 +162,7 @@ export function vcn<V extends VariantType, N extends string /* Preset names */>( return Object.entries(anyProps).reduce( ([variantProps, otherProps], [key, value]) => { - if ( - variantKeys.includes(key) - ) { + if (variantKeys.includes(key)) { return [{ ...variantProps, [key]: value }, otherProps]; } return [variantProps, { ...otherProps, [key]: value }]; @@ -168,7 +170,10 @@ export function vcn<V extends VariantType, N extends string /* Preset names */>( [{}, {}] ) as [ Partial<VariantKV<V>>, - Omit<typeof anyProps, keyof Partial<VariantKV<V>> | "preset" | "className">, + Omit< + typeof anyProps, + keyof Partial<VariantKV<V>> | "preset" | "className" + >, ]; }, ]; @@ -193,3 +198,72 @@ export type VariantProps<F extends (props: any) => string> = F extends ( ) => string ? P : never; + +function mergeReactProps( + parentProps: Record<string, any>, + childProps: Record<string, any> +) { + const overrideProps = { ...childProps }; + + for (const propName in childProps) { + const parentPropValue = parentProps[propName]; + const childPropValue = childProps[propName]; + + const isHandler = /^on[A-Z]/.test(propName); + if (isHandler) { + if (childPropValue && parentPropValue) { + overrideProps[propName] = (...args: unknown[]) => { + childPropValue?.(...args); + parentPropValue?.(...args); + }; + } else if (parentPropValue) { + overrideProps[propName] = parentPropValue; + } + } else if (propName === "style") { + overrideProps[propName] = { ...parentPropValue, ...childPropValue }; + } else if (propName === "className") { + overrideProps[propName] = twMerge(parentPropValue, childPropValue); + } + } + + return { ...parentProps, ...overrideProps }; +} + +function combinedRef<I>(refs: React.Ref<I | null>[]) { + return (instance: I | null) => + refs.forEach((ref) => { + if (ref instanceof Function) { + ref(instance); + } else if (!!ref) { + (ref as React.MutableRefObject<I | null>).current = instance; + } + }); +} + +interface SlotProps { + children?: Exclude<React.ReactNode, Iterable<React.ReactNode>> | string; +} +export const Slot = React.forwardRef<any, SlotProps>((props, ref) => { + const { children, ...slotProps } = props; + if (!React.isValidElement(children)) { + return null; + } + return React.cloneElement(children, { + ...mergeReactProps(slotProps, children.props), + ref: combinedRef([ref, (children as any).ref]), + } as any); +}); + +export interface MustAsChild { + children: React.ReactElement< + unknown, + string | React.JSXElementConstructor<any> + >; +} + +export interface OptionalAsChild<T extends boolean> { + children?: T extends true + ? React.ReactElement<unknown, string | React.JSXElementConstructor<any>> + : React.ReactNode; + asChild?: T; +}