mm
This commit is contained in:
14
packages/ui-react/src/utils/Slot.tsx
Normal file
14
packages/ui-react/src/utils/Slot.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { cloneElement, isValidElement } from "react";
|
||||
import { mergeProps } from "./mergeProps";
|
||||
|
||||
export const Slot = (props: any) => {
|
||||
const { children, ...rest } = props;
|
||||
|
||||
if (!isValidElement(children)) {
|
||||
throw new Error("Slot requires a single valid React element");
|
||||
}
|
||||
|
||||
const mergedProps = mergeProps(children.props as Record<string, any>, rest);
|
||||
|
||||
return cloneElement(children, mergedProps);
|
||||
};
|
||||
107
packages/ui-react/src/utils/mergeProps.ts
Normal file
107
packages/ui-react/src/utils/mergeProps.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
type AnyProps = Record<string, any>;
|
||||
|
||||
function defaultClassMergeFn(...classes: string[]): string {
|
||||
return classes
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
// 工具类型
|
||||
type UnionToIntersection<U> =
|
||||
(U extends any ? (k: U) => void : never) extends
|
||||
(k: infer I) => void ? I : never;
|
||||
|
||||
// 泛型入口
|
||||
export function mergeProps<
|
||||
T extends AnyProps[]
|
||||
>(
|
||||
...args: [...T]
|
||||
): UnionToIntersection<T[number]>;
|
||||
|
||||
export function mergeProps<
|
||||
T extends AnyProps[]
|
||||
>(
|
||||
options: { classMergeFn: (...cls: string[]) => string },
|
||||
...args: [...T]
|
||||
): UnionToIntersection<T[number]>;
|
||||
|
||||
export function mergeProps(
|
||||
arg1: any,
|
||||
...rest: any[]
|
||||
): any {
|
||||
let options = {
|
||||
classMergeFn: defaultClassMergeFn,
|
||||
};
|
||||
|
||||
let propsList: AnyProps[];
|
||||
|
||||
if (
|
||||
arg1 &&
|
||||
typeof arg1 === "object" &&
|
||||
!Array.isArray(arg1) &&
|
||||
"classMergeFn" in arg1 &&
|
||||
typeof arg1.classMergeFn === "function"
|
||||
) {
|
||||
options = arg1;
|
||||
propsList = rest;
|
||||
} else {
|
||||
propsList = [arg1, ...rest];
|
||||
}
|
||||
|
||||
const result: AnyProps = {};
|
||||
const eventHandlers = new Map<string, Function[]>();
|
||||
const refs: any[] = [];
|
||||
|
||||
for (const props of propsList) {
|
||||
if (!props) continue;
|
||||
|
||||
if (props.className) {
|
||||
result.className = options.classMergeFn(
|
||||
result.className ?? "",
|
||||
props.className,
|
||||
);
|
||||
}
|
||||
|
||||
if (props.style) {
|
||||
result.style = { ...(result.style ?? {}), ...props.style };
|
||||
}
|
||||
|
||||
if (props.ref) {
|
||||
refs.push(props.ref);
|
||||
}
|
||||
|
||||
for (const key of Object.keys(props)) {
|
||||
if (key.startsWith("on") && typeof props[key] === "function") {
|
||||
if (!eventHandlers.has(key)) {
|
||||
eventHandlers.set(key, []);
|
||||
}
|
||||
eventHandlers.get(key)!.push(props[key]);
|
||||
} else if (key !== "ref" && key !== "className" && key !== "style") {
|
||||
result[key] = props[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// refs
|
||||
if (refs.length === 1) {
|
||||
result.ref = refs[0];
|
||||
} else if (refs.length > 1) {
|
||||
result.ref = (node: any) => {
|
||||
refs.forEach((ref) => {
|
||||
if (typeof ref === "function") ref(node);
|
||||
else if (ref) ref.current = node;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// events
|
||||
eventHandlers.forEach((handlers, key) => {
|
||||
result[key] = (...args: any[]) => {
|
||||
handlers.forEach((fn) => fn(...args));
|
||||
};
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
10
packages/ui-react/src/utils/useDefaultedProps.ts
Normal file
10
packages/ui-react/src/utils/useDefaultedProps.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
export function useDefaultedProps<
|
||||
P extends Record<string, any>,
|
||||
D extends Partial<P>,
|
||||
>(props: P, defaults: D): P & D {
|
||||
return useMemo(() => {
|
||||
return { ...defaults, ...props };
|
||||
}, [props, defaults]);
|
||||
}
|
||||
8
packages/ui-react/src/utils/useSlotRegistry.ts
Normal file
8
packages/ui-react/src/utils/useSlotRegistry.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// useButtonSlots.ts
|
||||
import { useState } from "react";
|
||||
|
||||
export function useSlotRegistry<S>() {
|
||||
const [slots, setSlots] = useState<S>()
|
||||
|
||||
return { slots, setSlots }
|
||||
}
|
||||
Reference in New Issue
Block a user