Skip to content

Commit

Permalink
feat(ui/system): asChild 추가, 관련 system 내부 명칭 및 구조 변경 (#185)
Browse files Browse the repository at this point in the history
* feat(ui/system/types.ts): PolymorphicProps 추가, 관련 변수 명칭 변경

* feat(ui/system/forward-ref.ts): PolymorphicProps 관련 변경 적용

* feat(ui/system/create-component.ts): asChild 관련 로직 createComponent 함수에 추가

* feat(ui/system/factory.ts): FavolinkFactoryFn 타입 추가

* refactor(ui/system): createComponent를 createPolymorphicComponent로 변경
  • Loading branch information
sukvvon authored May 24, 2024
1 parent 3b59f1f commit 66b6917
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 57 deletions.
32 changes: 0 additions & 32 deletions packages/ui/react/src/system/src/create-component.ts

This file was deleted.

60 changes: 60 additions & 0 deletions packages/ui/react/src/system/src/create-polymorphic-component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { composeRefs, mergeProps } from '@favolink-ui/utils';
import {
Children,
type ComponentPropsWithoutRef,
type ElementType,
type Ref,
cloneElement,
createElement,
isValidElement,
} from 'react';
import { forwardRef } from './forward-ref';
import {
type ComponentWithPolymorphic,
type JsxElements,
type PolymorphicProps,
} from './types';

export type FavolinkComponent<Component extends ElementType> =
ComponentWithPolymorphic<Component, object>;

export function createPolymorphicComponent<Component extends ElementType>(
component: Component,
) {
const favolinkComponent = forwardRef<object, Component>(
function favolinkComponent(props, ref) {
const { as: asElement, asChild, children, ...restProps } = props;

if (!asChild) {
return createElement(
asElement ?? component,
{
ref,
...restProps,
},
children,
);
}

const onlyChild = Children.only(children);

return isValidElement(onlyChild)
? cloneElement(onlyChild, {
...mergeProps(restProps, onlyChild.props),
ref: ref
? composeRefs(ref, onlyChild.ref as Ref<any>)
: (onlyChild.ref as Ref<any>),
})
: null;
},
);

return favolinkComponent as FavolinkComponent<Component>;
}

export type HTMLFavolinkComponents = {
[Element in JsxElements]: FavolinkComponent<Element>;
};

export type HTMLFavolinkProps<Element extends JsxElements> =
ComponentPropsWithoutRef<Element> & PolymorphicProps;
31 changes: 18 additions & 13 deletions packages/ui/react/src/system/src/factory.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
import { type ElementType } from 'react';
import {
type FavolinkComponent,
type HTMLFavolinkComponents,
createComponent,
} from './create-component';
import { type DOMElements } from './types';
createPolymorphicComponent,
} from './create-polymorphic-component';
import { type JsxElements } from './types';

type FavolinkFactory = {
<Element extends DOMElements>(element: Element): FavolinkComponent<Element>;
export type FavolinkFactory = {
<Component extends ElementType>(
component: Component,
): FavolinkComponent<Component>;
};

export type FavolinkFactoryFn = FavolinkFactory & HTMLFavolinkComponents;

function factory() {
const cache = new Map<DOMElements, FavolinkComponent<DOMElements>>();
const cache = new Map<JsxElements, FavolinkComponent<JsxElements>>();

return new Proxy(createComponent, {
apply: (_, __, options: [DOMElements]) => {
return createComponent(...options);
return new Proxy(createPolymorphicComponent, {
apply: (_, __, options: [ElementType]) => {
return createPolymorphicComponent(...options);
},
get: (_, element: DOMElements) => {
get: (_, element: JsxElements) => {
if (!cache.get(element)) {
cache.set(element, createComponent(element));
cache.set(element, createPolymorphicComponent(element));
}

return cache.get(element);
},
}) as FavolinkFactory & HTMLFavolinkComponents;
});
}

export const favolink = factory();
export const favolink = factory() as FavolinkFactoryFn;
13 changes: 8 additions & 5 deletions packages/ui/react/src/system/src/forward-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ import {
type ForwardRefRenderFunction,
forwardRef as ReactForwardRef,
} from 'react';
import { type ComponentWithAs, type RightJoinProps } from './types';
import {
type ComponentWithPolymorphic,
type PolymorphicProps,
type RightJoinProps,
} from './types';

export function forwardRef<Props extends object, Component extends ElementType>(
render: ForwardRefRenderFunction<
any,
RightJoinProps<ComponentPropsWithoutRef<Component>, Props> & {
as?: ElementType;
}
PolymorphicProps &
RightJoinProps<ComponentPropsWithoutRef<Component>, Props>
>,
) {
return ReactForwardRef(render) as unknown as ComponentWithAs<
return ReactForwardRef(render) as unknown as ComponentWithPolymorphic<
Component,
Props
>;
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/react/src/system/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @stylistic/padding-line-between-statements */
export * from './create-component';
export * from './create-context';
export * from './create-polymorphic-component';
export * from './create-raw-style-props';
export * from './extract-props';
export * from './factory';
Expand Down
22 changes: 16 additions & 6 deletions packages/ui/react/src/system/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,44 @@
import { type ComponentPropsWithRef, type ElementType } from 'react';
import {
type ComponentPropsWithRef,
type ElementType,
type ReactElement,
} from 'react';

export type DOMElements = keyof JSX.IntrinsicElements;
export type JsxElements = keyof JSX.IntrinsicElements;

export type PolymorphicProps = {
as?: ElementType;
asChild?: boolean;
};

export type RightJoinProps<
OriginalProps extends object,
OverrideProps extends object,
> = Omit<OriginalProps, keyof OverrideProps> & OverrideProps;

export type MergeWithAsComponentProps<
export type MergePolymorpicProps<
ComponentProps extends object,
AsComponentProps extends object,
Props extends object,
AsComponent extends ElementType,
> = {
as?: AsComponent;
asChild?: boolean;
} & (
| RightJoinProps<AsComponentProps, Props>
| RightJoinProps<ComponentProps, Props>
);

export type ComponentWithAs<
export type ComponentWithPolymorphic<
Component extends ElementType,
Props extends object,
> = {
<AsComponent extends ElementType>(
props: MergeWithAsComponentProps<
props: MergePolymorpicProps<
ComponentPropsWithRef<Component>,
ComponentPropsWithRef<AsComponent>,
Props,
AsComponent
>,
): JSX.Element;
): ReactElement;
};

0 comments on commit 66b6917

Please sign in to comment.