Skip to content

Commit

Permalink
refactor(ui/system): createContext 함수의 인자를 간략화, 반환값 Provider, useCont…
Browse files Browse the repository at this point in the history
…ext이 포함된 배열로 변경 (#322)

* refactor(ui/system): createContext 함수의 인자를 간략화, 반환값 Provider, useContext이 포함된 배열로 변경

* refactor(ui/components): createContext 변경사항 반영
  • Loading branch information
sukvvon authored Aug 21, 2024
1 parent 8edb1b0 commit 87d75aa
Show file tree
Hide file tree
Showing 13 changed files with 83 additions and 78 deletions.
33 changes: 13 additions & 20 deletions packages/ui/src/components/checkbox/checkbox.primitive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,15 @@ import {
} from '../../system';
import { mergeFns, mergeStyles } from '../../utils';

const CHECKBOX_NAME = 'Checkbox';

type CheckboxContextValue = {
state: boolean;
disabled?: boolean;
};

const [CheckboxProvider, useCheckboxContext] =
createContext<CheckboxContextValue>({
name: 'CheckboxContext',
hookName: 'useCheckboxContext',
providerName: '<CheckboxProvider />',
});
createContext<CheckboxContextValue>(CHECKBOX_NAME);

type CheckboxProps = HTMLFavolinkPropsWithout<
'button',
Expand Down Expand Up @@ -68,9 +66,7 @@ const Checkbox = forwardRef<CheckboxProps, 'button'>(
useEffect(() => {
const form = button?.form;

if (!form) {
return;
}
if (!form) return;

function reset() {
setChecked(initialCheckedStateRef.current);
Expand All @@ -84,7 +80,7 @@ const Checkbox = forwardRef<CheckboxProps, 'button'>(
}, [button, setChecked]);

return (
<CheckboxProvider value={{ state: checked, disabled }}>
<CheckboxProvider state={checked} disabled={disabled}>
<favolink.button
type="button"
role="checkbox"
Expand All @@ -97,9 +93,7 @@ const Checkbox = forwardRef<CheckboxProps, 'button'>(
{...restProps}
ref={composedRefs}
onKeyDown={mergeFns(props.onKeyDown, (event) => {
if (event.key === 'Enter') {
event.preventDefault();
}
if (event.key === 'Enter') event.preventDefault();
})}
onClick={mergeFns(props.onClick, (event) => {
setChecked(!checked);
Expand All @@ -108,9 +102,8 @@ const Checkbox = forwardRef<CheckboxProps, 'button'>(
hasConsumerStoppedPropagation.current =
event.isPropagationStopped();

if (!hasConsumerStoppedPropagation.current) {
if (!hasConsumerStoppedPropagation.current)
event.stopPropagation();
}
}
})}
/>
Expand All @@ -131,13 +124,15 @@ const Checkbox = forwardRef<CheckboxProps, 'button'>(
},
);

Checkbox.displayName = 'Checkbox';
Checkbox.displayName = CHECKBOX_NAME;

const INDICATOR_NAME = 'CheckboxIndicator';

type CheckboxIndicatorProps = HTMLFavolinkProps<'span'>;

const CheckboxIndicator = forwardRef<CheckboxIndicatorProps, 'span'>(
function CheckboxIndicator(props, forwardedRef) {
const context = useCheckboxContext();
const context = useCheckboxContext(INDICATOR_NAME);

return (
context.state && (
Expand All @@ -153,7 +148,7 @@ const CheckboxIndicator = forwardRef<CheckboxIndicatorProps, 'span'>(
},
);

CheckboxIndicator.displayName = 'CheckboxIndicator';
CheckboxIndicator.displayName = INDICATOR_NAME;

type BubbleInputProps = HTMLProps<'input'> & {
bubbles: boolean;
Expand All @@ -176,9 +171,7 @@ function BubbleInput(props: BubbleInputProps) {
) as PropertyDescriptor;
const setChecked = checkedDescriptor.set?.bind(input);

if (prevChecked === checked || !setChecked) {
return;
}
if (prevChecked === checked || !setChecked) return;

const event = new Event('click', { bubbles });

Expand Down
6 changes: 5 additions & 1 deletion packages/ui/src/components/menu/menu-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { type Placement, usePosition } from '../../hooks';
import { type HTMLFavolinkProps, favolink, forwardRef } from '../../system';
import { composeRefs, cx } from '../../utils';

const CONTENT_NAME = 'MenuContent';

export type MenuContentProps = HTMLFavolinkProps<'div'> & {
placement?: Placement;
sideOffset?: number;
Expand All @@ -20,7 +22,7 @@ export const MenuContent = forwardRef<MenuContentProps, 'div'>(
...restProps
} = props;

const context = useMenuContext();
const context = useMenuContext(CONTENT_NAME);

const coordinate = usePosition(
context.open,
Expand Down Expand Up @@ -48,3 +50,5 @@ export const MenuContent = forwardRef<MenuContentProps, 'div'>(
);
},
);

MenuContent.displayName = CONTENT_NAME;
6 changes: 5 additions & 1 deletion packages/ui/src/components/menu/menu-trigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { useMenuContext } from './menu.context';
import { type HTMLFavolinkProps, favolink, forwardRef } from '../../system';
import { composeRefs, cx, mergeFns } from '../../utils';

const TRIGGER_NAME = 'MenuTrigger';

export type MenuTriggerProps = HTMLFavolinkProps<'button'>;

export const MenuTrigger = forwardRef<MenuTriggerProps, 'button'>(
function MenuTrigger(props, ref) {
const { children, className, onClick, ...restProps } = props;

const context = useMenuContext();
const context = useMenuContext(TRIGGER_NAME);

return (
<favolink.button
Expand All @@ -24,3 +26,5 @@ export const MenuTrigger = forwardRef<MenuTriggerProps, 'button'>(
);
},
);

MenuTrigger.displayName = TRIGGER_NAME;
7 changes: 2 additions & 5 deletions packages/ui/src/components/menu/menu.context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,5 @@ type MenuContextValue = {
containerRef: Ref<HTMLDivElement>;
};

export const [MenuProvider, useMenuContext] = createContext<MenuContextValue>({
name: 'MenuContentContext',
hookName: 'useMenuContentContext',
providerName: '<MenuContentProvider />',
});
export const [MenuProvider, useMenuContext] =
createContext<MenuContextValue>('Menu');
7 changes: 6 additions & 1 deletion packages/ui/src/components/menu/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ export function Menu(props: MenuProps) {
const onOpenChange = onExternalOnOpenChange ?? onInternalOnOpenChange;

return (
<MenuProvider value={{ open, onOpenChange, triggerRef, containerRef }}>
<MenuProvider
open={open}
onOpenChange={onOpenChange}
triggerRef={triggerRef}
containerRef={containerRef}
>
{children}
</MenuProvider>
);
Expand Down
6 changes: 5 additions & 1 deletion packages/ui/src/components/modal/modal-close.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { useModalContext } from './modal.context';
import { type HTMLFavolinkProps, favolink, forwardRef } from '../../system';
import { cx, mergeFns } from '../../utils';

const CLOSE_NAME = 'ModalClose';

export type ModalCloseProps = HTMLFavolinkProps<'button'>;

export const ModalClose = forwardRef<ModalCloseProps, 'button'>(
function ModalClose(props, ref) {
const { children, className, onClick, ...restProps } = props;

const { onOpenChange } = useModalContext();
const { onOpenChange } = useModalContext(CLOSE_NAME);

return (
<favolink.button
Expand All @@ -25,3 +27,5 @@ export const ModalClose = forwardRef<ModalCloseProps, 'button'>(
);
},
);

ModalClose.displayName = CLOSE_NAME;
6 changes: 5 additions & 1 deletion packages/ui/src/components/modal/modal-overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { useModalContext } from './modal.context';
import { type HTMLFavolinkProps, favolink, forwardRef } from '../../system';
import { cx, mergeFns } from '../../utils';

const OVERLAY_NAME = 'ModalOverlay';

export type ModalOverlayProps = HTMLFavolinkProps<'div'>;

export const ModalOverlay = forwardRef<ModalOverlayProps, 'div'>(
function ModalOverlay(props, ref) {
const { className, onClick, ...restProps } = props;

const { onOpenChange, closeOnOverlayClick } = useModalContext();
const { onOpenChange, closeOnOverlayClick } = useModalContext(OVERLAY_NAME);

return (
<favolink.div
Expand All @@ -31,3 +33,5 @@ export const ModalOverlay = forwardRef<ModalOverlayProps, 'div'>(
);
},
);

ModalOverlay.displayName = OVERLAY_NAME;
4 changes: 3 additions & 1 deletion packages/ui/src/components/modal/modal-portal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { Children } from 'react';
import { useModalContext } from './modal.context';
import { Portal, type PortalProps } from '../portal/';

const PORTAL_NAME = 'ModalPortal';

export type ModalPortalProps = PortalProps;

export function ModalPortal(props: ModalPortalProps) {
const { children, ...restProps } = props;

const { onOpen } = useModalContext();
const { onOpen } = useModalContext(PORTAL_NAME);

return (
onOpen &&
Expand Down
6 changes: 5 additions & 1 deletion packages/ui/src/components/modal/modal-topbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { ChevronLeftIcon, CloseIcon } from '../../icons';
import { type HTMLFavolinkProps, favolink, forwardRef } from '../../system';
import { cx, mergeFns } from '../../utils';

const TOPBAR_NAME = 'ModalTopbar';

export type ModalTopbarProps = HTMLFavolinkProps<'div'> &
styles.ModalTopbarVariants & {
onLeftIconClick?: (event: MouseEvent) => void;
Expand All @@ -21,7 +23,7 @@ export const ModalTopbar = forwardRef<ModalTopbarProps, 'div'>(
...restProps
} = props;

const { onOpenChange } = useModalContext();
const { onOpenChange } = useModalContext(TOPBAR_NAME);

const isCouple = layout !== 'single';

Expand Down Expand Up @@ -51,3 +53,5 @@ export const ModalTopbar = forwardRef<ModalTopbarProps, 'div'>(
);
},
);

ModalTopbar.displayName = TOPBAR_NAME;
6 changes: 5 additions & 1 deletion packages/ui/src/components/modal/modal-trigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { useModalContext } from './modal.context';
import { type HTMLFavolinkProps, favolink, forwardRef } from '../../system';
import { cx, mergeFns } from '../../utils';

const TRIGGER_NAME = 'ModalTrigger';

export type ModalTriggerProps = HTMLFavolinkProps<'button'>;

export const ModalTrigger = forwardRef<ModalTriggerProps, 'button'>(
function MenuTrigger(props, ref) {
const { className, children, onClick, ...restProps } = props;

const { onOpenChange } = useModalContext();
const { onOpenChange } = useModalContext(TRIGGER_NAME);

return (
<favolink.button
Expand All @@ -25,3 +27,5 @@ export const ModalTrigger = forwardRef<ModalTriggerProps, 'button'>(
);
},
);

ModalTrigger.displayName = TRIGGER_NAME;
6 changes: 1 addition & 5 deletions packages/ui/src/components/modal/modal.context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,4 @@ export type ModalContextValue = {
};

export const [ModalProvider, useModalContext] =
createContext<ModalContextValue>({
name: 'ModalContext',
hookName: 'useModalContext',
providerName: '<ModalProvider />',
});
createContext<ModalContextValue>('Modal');
8 changes: 3 additions & 5 deletions packages/ui/src/components/modal/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ export function Modal(props: ModalProps) {

return (
<ModalProvider
value={{
onOpen,
onOpenChange,
closeOnOverlayClick,
}}
onOpen={onOpen}
onOpenChange={onOpenChange}
closeOnOverlayClick={closeOnOverlayClick}
>
{children}
</ModalProvider>
Expand Down
60 changes: 25 additions & 35 deletions packages/ui/src/system/create-context.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,43 @@
import {
type Context,
type Provider,
type ReactNode,
createContext as createReactContext,
useMemo,
useContext as useReactContext,
} from 'react';

type CreatContextOptions<T> = {
name?: string;
hookName?: string;
providerName?: string;
errorMessage?: string;
defaultValue?: T;
};

type CreateContextReturn<T> = [Provider<T>, () => T, Context<T>];
export function createContext<T extends object | null>(
rootComponentName: string,
defaultValue?: T,
) {
const Context = createReactContext<T | undefined>(defaultValue);

function getErrorMessage(hook: string, provider: string) {
return `${hook} returned \`undefined\`. Seems you forgot to wrap component within ${provider}`;
}
function Provider(props: T & { children: ReactNode }) {
const { children, ...context } = props;

export function createContext<T>(options: CreatContextOptions<T>) {
const {
name,
hookName = 'useContext',
providerName = 'Provider',
errorMessage,
defaultValue,
} = options;
// eslint-disable-next-line react-hooks/exhaustive-deps
const value = useMemo(() => context, Object.values(context)) as T;

const Context = createReactContext<T | undefined>(defaultValue);
return <Context.Provider value={value}>{children}</Context.Provider>;
}

Context.displayName = name;
Provider.displayName = rootComponentName + 'Provider';

function useContext() {
function useContext(consumerName: string) {
const context = useReactContext(Context);

if (!context) {
const error = new Error(
errorMessage ?? getErrorMessage(hookName, providerName),
);
if (context) return context;

if (defaultValue !== undefined) return defaultValue;

error.name = 'ContextError';
Error.captureStackTrace(error, useContext);
const error = new Error(
`\`${consumerName}\` must be used within \`${rootComponentName}\``,
);

throw error;
}
error.name = 'ContextError';
Error.captureStackTrace(error, useContext);

return context;
throw error;
}

return [Context.Provider, useContext, Context] as CreateContextReturn<T>;
return [Provider, useContext] as const;
}

0 comments on commit 87d75aa

Please sign in to comment.