@modern-kit/react
Version:
1 lines • 10.7 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../../../src/components/Slot/Slot.utils.ts","../../../src/components/Slot/index.tsx"],"sourcesContent":["import * as React from 'react';\n\ntype AnyProps = Record<string, any>;\n\n/**\n * @description Slot 컴포넌트의 props와 자식 요소의 props를 병합하는 함수입니다.\n */\nexport function mergeProps(slotProps: AnyProps, childProps: AnyProps) {\n // 모든 자식의 props를 오버라이드 해야 합니다.\n const overrideProps = { ...childProps };\n\n for (const propName in childProps) {\n const slotPropValue = slotProps[propName];\n const childPropValue = childProps[propName];\n\n const isHandler = /^on[A-Z]/.test(propName);\n if (isHandler) {\n // handler가 둘 다 존재하는 경우\n if (slotPropValue && childPropValue) {\n overrideProps[propName] = (...args: unknown[]) => {\n childPropValue(...args);\n slotPropValue(...args);\n };\n }\n // handler가 slotProps에만 존재하는 경우\n else if (slotPropValue) {\n overrideProps[propName] = slotPropValue;\n }\n }\n // style 및 className 병합\n else if (propName === 'style') {\n overrideProps[propName] = { ...slotPropValue, ...childPropValue };\n } else if (propName === 'className') {\n overrideProps[propName] = [slotPropValue, childPropValue]\n .filter(Boolean)\n .join(' ');\n }\n }\n\n return { ...slotProps, ...overrideProps };\n}\n\n// React 19 이전 버전에서 `element.props.ref`에 접근하면 경고가 표시되고, `element.ref` 사용을 제안합니다.\n// React 19 부터는 `element.ref`에 액세스하면 그 반대가 됩니다.\n// https://github.com/facebook/react/pull/28348\n//\n// 경고가 표시되지 않는 방법을 사용하여 ref에 접근합니다.\nexport function getElementRef(element: React.ReactElement) {\n // React <=18 in DEV\n let getter = Object.getOwnPropertyDescriptor(element.props, 'ref')?.get;\n let mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning;\n if (mayWarn) {\n return (element as any).ref;\n }\n\n // React 19 in DEV\n getter = Object.getOwnPropertyDescriptor(element, 'ref')?.get;\n mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning;\n if (mayWarn) {\n return element.props.ref;\n }\n\n // Not DEV\n return element.props.ref || (element as any).ref;\n}\n","import { useMergeRefs } from '../../hooks/useMergeRefs';\nimport { mergeProps, getElementRef } from './Slot.utils';\nimport React from 'react';\n\nexport interface SlotProps extends React.HTMLAttributes<HTMLElement> {\n children?: React.ReactNode;\n}\n\n/**\n * @description 주어진 Props를 직계 자식 컴포넌트에 병합하고, 자식 컴포넌트를 렌더링하는 컴포넌트입니다.\n *\n * Slot은 부모 컴포넌트의 기능을 자식 컴포넌트에 전달하는 합성 패턴을 구현합니다. 이를 통해:\n * - 부모 컴포넌트의 `props`, `ref`, `이벤트 핸들러` 등을 자식 컴포넌트에 전달할 수 있습니다.\n * - 자식 컴포넌트의 구현을 변경하지 않고도 새로운 기능을 추가할 수 있습니다.\n * - 컴포넌트 간의 결합도를 낮추고 재사용성을 높일 수 있습니다.\n *\n * Slot은 아래와 같은 특징이 있습니다.\n * - 자식 요소로 `단일 요소`만 허용됩니다.\n * - 자식 요소로 컴포넌트가 온다면 해당 컴포넌트는 필수적으로 `forwardRef`, `props`를 허용해야 합니다. 허용하지 않으면 기능이 정상적으로 동작하지 않습니다.\n * - Slot을 사용 할 경우 아래 링크를 참고하세요.\n *\n * @see https://www.radix-ui.com/primitives/docs/guides/composition#your-component-must-spread-props\n * @see https://www.radix-ui.com/primitives/docs/guides/composition#your-component-must-forward-ref\n *\n * @example\n * ```tsx\n * import React from \"react\";\n * import { Slot } from \"@modern-kit/react\";\n *\n * function Button({ asChild, ...props }) {\n * const Comp = asChild ? Slot : \"button\";\n * return <Comp {...props} />;\n * }\n *\n * // default\n * <Button onClick={onClick}>Button</Button>\n *\n * // asChild\n * <Button asChild onClick={onClick}>\n * <div>asChild Button</div>\n * </Button>\n * ```\n */\nexport const Slot = React.forwardRef<HTMLElement, SlotProps>(\n (props, forwardedRef) => {\n const { children, ...slotProps } = props;\n const childrenArray = React.Children.toArray(children);\n const slottable = childrenArray.find(isSlottable);\n\n if (slottable) {\n // 렌더링할 새 요소로 `Slottable`의 children으로 전달된 요소를 할당한다.\n const newElement = slottable.props.children;\n\n // childrenArray를 순회하면서 새로운 자식 요소를 생성한다.\n // Slottable 컴포넌트를 발견하면 해당 자식 요소를 새로운 자식으로 교체한다.\n const newChildren = childrenArray.map((child) => {\n if (child === slottable) {\n if (React.Children.count(newElement) > 1) {\n return React.Children.only(null);\n }\n return React.isValidElement(newElement)\n ? (newElement.props.children as React.ReactNode)\n : null;\n }\n return child;\n });\n\n return (\n <SlotClone {...slotProps} ref={forwardedRef}>\n {React.isValidElement(newElement)\n ? React.cloneElement(newElement, undefined, newChildren)\n : null}\n </SlotClone>\n );\n }\n\n // Slottable 컴포넌트가 존재하지 않을 경우, children을 그대로 렌더링한다.\n return (\n <SlotClone {...slotProps} ref={forwardedRef}>\n {children}\n </SlotClone>\n );\n }\n);\n\nSlot.displayName = 'Slot';\n\ninterface SlotCloneProps {\n children: React.ReactNode;\n}\n\n/**\n * @description Slot의 자식 요소를 복제하고, slotProps와 자식 요소의 props를 병합한다.\n */\nconst SlotClone = React.forwardRef<HTMLElement, SlotCloneProps>(\n (props, forwardedRef) => {\n const { children, ...slotProps } = props;\n\n const childRef = React.isValidElement(children)\n ? getElementRef(children)\n : null;\n const mergedRefs = useMergeRefs(forwardedRef, childRef);\n\n if (!React.isValidElement(children)) {\n return React.Children.count(children) > 1\n ? React.Children.only(null)\n : null;\n }\n\n return React.cloneElement(children, {\n ...mergeProps(slotProps, children.props as Record<string, any>),\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n ref: forwardedRef ? mergedRefs : childRef,\n });\n }\n);\n\nSlotClone.displayName = 'SlotClone';\n\ninterface SlottableProps {\n children?: React.ReactNode;\n}\n\n/**\n * @description Slot 컴포넌트의 자식 요소로 사용되며, Slot이 렌더링할 대상이다.\n */\nexport const Slottable = ({ children }: SlottableProps) => {\n return <>{children}</>;\n};\n\n/**\n * @description child가 유효한 React 요소인지 확인하고, type이 'Slottable' 컴포넌트인지 확인한다.\n */\nfunction isSlottable(\n child: React.ReactNode\n): child is React.ReactElement<React.PropsWithChildren> {\n return React.isValidElement(child) && child.type === Slottable;\n}\n"],"names":[],"mappings":"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"}