refactor(react): split library file to vcn and Slot

This commit is contained in:
p-sw 2024-06-14 23:25:56 +09:00
parent 6263a99b9a
commit 47cfd907b9
2 changed files with 97 additions and 96 deletions

View File

@ -0,0 +1,97 @@
import { twMerge } from "tailwind-merge";
import React from "react";
/**
* Merges the react props.
* Basically childProps will override parentProps.
* But if it is a event handler, style, or className, it will be merged.
*
* @param parentProps - The parent props.
* @param childProps - The child props.
* @returns The merged props.
*/
function mergeReactProps(
parentProps: Record<string, unknown>,
childProps: Record<string, unknown>,
) {
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 &&
typeof childPropValue === "function" &&
typeof parentPropValue === "function"
) {
overrideProps[propName] = (...args: unknown[]) => {
childPropValue?.(...args);
parentPropValue?.(...args);
};
} else if (parentPropValue) {
overrideProps[propName] = parentPropValue;
}
} else if (
propName === "style" &&
typeof parentPropValue === "object" &&
typeof childPropValue === "object"
) {
overrideProps[propName] = { ...parentPropValue, ...childPropValue };
} else if (
propName === "className" &&
typeof parentPropValue === "string" &&
typeof childPropValue === "string"
) {
overrideProps[propName] = twMerge(parentPropValue, childPropValue);
}
}
return { ...parentProps, ...overrideProps };
}
/**
* Takes an array of refs, and returns a single ref.
*
* @param refs - The array of refs.
* @returns The single ref.
*/
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?: React.ReactNode;
}
export const Slot = React.forwardRef<
HTMLElement,
SlotProps & Record<string, unknown>
>((props, ref) => {
const { children, ...slotProps } = props;
const { asChild: _1, ...safeSlotProps } = slotProps;
if (!React.isValidElement(children)) {
console.warn(`given children "${children}" is not valid for asChild`);
return null;
}
return React.cloneElement(children, {
...mergeReactProps(safeSlotProps, children.props),
ref: combinedRef([
ref,
(children as unknown as { ref: React.Ref<HTMLElement> }).ref,
]),
} as never);
});
export interface AsChild {
asChild?: boolean;
}

View File

@ -1,4 +1,3 @@
import React from "react";
import { twMerge } from "tailwind-merge";
/**
@ -273,98 +272,3 @@ export type VariantProps<F extends (props: unknown) => string> = F extends (
) => string
? P
: never;
/**
* Merges the react props.
* Basically childProps will override parentProps.
* But if it is a event handler, style, or className, it will be merged.
*
* @param parentProps - The parent props.
* @param childProps - The child props.
* @returns The merged props.
*/
function mergeReactProps(
parentProps: Record<string, unknown>,
childProps: Record<string, unknown>,
) {
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 &&
typeof childPropValue === "function" &&
typeof parentPropValue === "function"
) {
overrideProps[propName] = (...args: unknown[]) => {
childPropValue?.(...args);
parentPropValue?.(...args);
};
} else if (parentPropValue) {
overrideProps[propName] = parentPropValue;
}
} else if (
propName === "style" &&
typeof parentPropValue === "object" &&
typeof childPropValue === "object"
) {
overrideProps[propName] = { ...parentPropValue, ...childPropValue };
} else if (
propName === "className" &&
typeof parentPropValue === "string" &&
typeof childPropValue === "string"
) {
overrideProps[propName] = twMerge(parentPropValue, childPropValue);
}
}
return { ...parentProps, ...overrideProps };
}
/**
* Takes an array of refs, and returns a single ref.
*
* @param refs - The array of refs.
* @returns The single ref.
*/
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?: React.ReactNode;
}
export const Slot = React.forwardRef<
HTMLElement,
SlotProps & Record<string, unknown>
>((props, ref) => {
const { children, ...slotProps } = props;
const { asChild: _1, ...safeSlotProps } = slotProps;
if (!React.isValidElement(children)) {
console.warn(`given children "${children}" is not valid for asChild`);
return null;
}
return React.cloneElement(children, {
...mergeReactProps(safeSlotProps, children.props),
ref: combinedRef([
ref,
(children as unknown as { ref: React.Ref<HTMLElement> }).ref,
]),
} as never);
});
export interface AsChild {
asChild?: boolean;
}