Skip to content

Commit

Permalink
feat(*): Add interface of additonal router option (#32)
Browse files Browse the repository at this point in the history
* feat(*): Add interface of additonal router option

* apply review
  • Loading branch information
minuukang authored Aug 20, 2024
1 parent 66b059f commit 00c31d9
Show file tree
Hide file tree
Showing 11 changed files with 327 additions and 101 deletions.
65 changes: 61 additions & 4 deletions docs/src/pages/docs/use-funnel.en.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { UseFunnelCodeBlock, Keyword } from '@/components'
import { Callout } from 'nextra/components'
import { UseFunnelCodeBlock, useFunnelPackages, Keyword } from '@/components'
import { Callout, Tabs } from 'nextra/components'

# `useFunnel()`

Expand All @@ -16,6 +16,7 @@ function useFunnel<T>(options: UseFunnelOptions<T>): UseFunnelResults<T>;
* [UseFunnelOptions](#usefunneloptions)
* [UseFunnelResults](#usefunnelresults)
* [FunnelHistory](#funnelhistory)
* [RouteOption](#routeoption)

## UseFunnelOptions

Expand Down Expand Up @@ -80,8 +81,16 @@ An object that manages the transitions of the funnel. It contains several method

```tsx
interface FunnelHistory<TContextMap, TCurrentStep extends keyof TContextMap> {
push: <TTargetStep extends keyof TContextMap>(step: TTargetStep, context: TContextMap[TTargetStep] | ((prev: TContextMap<TCurrentStep>) => T[TTargetStep])) => Promise<void> | void;
replace: <TTargetStep extends keyof TContextMap>(step: TTargetStep, context: TContextMap[TTargetStep] | ((prev: TContextMap<TCurrentStep>) => T[TTargetStep])) => Promise<void> | void;
push: <TTargetStep extends keyof TContextMap>(
step: TTargetStep,
context: TContextMap[TTargetStep] | ((prev: TContextMap<TCurrentStep>) => T[TTargetStep]),
routeOption?: RouteOption
) => Promise<void> | void;
replace: <TTargetStep extends keyof TContextMap>(
step: TTargetStep,
context: TContextMap[TTargetStep] | ((prev: TContextMap<TCurrentStep>) => T[TTargetStep]),
routeOption?: RouteOption
) => Promise<void> | void;
go: (index: number) => void | Promise<void>;
back: () => void | Promise<void>;
}
Expand All @@ -90,13 +99,61 @@ interface FunnelHistory<TContextMap, TCurrentStep extends keyof TContextMap> {
- `push` (`function`): Adds a new state and moves to the next funnel step.
- `step` (`string`): The name of the next step.
- `context` (`object` | `function`): An object representing the state of the next step. You can also pass a function that uses the current step state to create the next step state.
- `routeOption` (`object`): A routing option that can be set for the next step. Different options are provided for each package. For more information, see [RouteOption](#routeoption).
- `replace` (`function`): Replaces the current state with a new one and transitions to the next step.
- `step` (`string`): The name of the next step.
- `context` (`object` | `function`): An object representing the state of the next step. You can also pass a function that uses the current step state to create the next step state.
- `routeOption` (`object`): A routing option that can be set for the next step. Different options are provided for each package. For more information, see [RouteOption](#routeoption).
- `go` (`function`): Moves to the step at the specified index in the `historySteps` array.
- `index` (`number`): The index of the step to move to.
- `back` (`function`): Moves to the previous step.


### RouteOption

<Tabs items={useFunnelPackages.map((p) => p.packageTitle)} storageKey="favorite-package">
<Tabs.Tab>
```ts
interface RouteOption {
scroll?: boolean;
locale?: string;
shallow?: boolean;
}
```

For more information, see the [documentation](https://nextjs.org/docs/pages/api-reference/functions/use-router#routerpush) for the package.

<Callout>
At `@use-funnel/next`, the `shallow` option is intentionally set to `true` by default.
</Callout>
</Tabs.Tab>
<Tabs.Tab>
```ts
interface RouteOption {
preventScrollReset?: boolean;
unstable_flushSync?: boolean;
unstable_viewTransition?: boolean;
}
```

For more information, see the [documentation](https://reactrouter.com/en/main/hooks/use-navigate) for the package.
</Tabs.Tab>
<Tabs.Tab>
```ts
interface RouteOption {}
```

At this package, no options are provided.
</Tabs.Tab>
<Tabs.Tab>
```ts
interface RouteOption {}
```

At this package, no options are provided.
</Tabs.Tab>
</Tabs>

## Example

### Basic Usage
Expand Down
64 changes: 60 additions & 4 deletions docs/src/pages/docs/use-funnel.ko.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { UseFunnelCodeBlock, Keyword } from '@/components'
import { Callout } from 'nextra/components'
import { UseFunnelCodeBlock, useFunnelPackages, Keyword } from '@/components'
import { Callout, Tabs } from 'nextra/components'

# `useFunnel()`

Expand All @@ -16,6 +16,7 @@ function useFunnel<T>(options: UseFunnelOptions<T>): UseFunnelResults<T>;
* [UseFunnelOptions](#usefunneloptions)
* [UseFunnelResults](#usefunnelresults)
* [FunnelHistory](#funnelhistory)
* [RouteOption](#routeoption)

## UseFunnelOptions

Expand Down Expand Up @@ -78,8 +79,16 @@ type UseFunnelResults<T> = {

```tsx
interface FunnelHistory<TContextMap, TCurrentStep extends keyof TContextMap> {
push: <TTargetStep extends keyof TContextMap>(step: TTargetStep, context: TContextMap[TTargetStep] | ((prev: TContextMap<TCurrentStep>) => T[TTargetStep])) => Promise<void> | void;
replace: <TTargetStep extends keyof TContextMap>(step: TTargetStep, context: TContextMap[TTargetStep] | ((prev: TContextMap<TCurrentStep>) => T[TTargetStep])) => Promise<void> | void;
push: <TTargetStep extends keyof TContextMap>(
step: TTargetStep,
context: TContextMap[TTargetStep] | ((prev: TContextMap<TCurrentStep>) => T[TTargetStep]),
routeOption?: RouteOption
) => Promise<void> | void;
replace: <TTargetStep extends keyof TContextMap>(
step: TTargetStep,
context: TContextMap[TTargetStep] | ((prev: TContextMap<TCurrentStep>) => T[TTargetStep]),
routeOption?: RouteOption
) => Promise<void> | void;
go: (index: number) => void | Promise<void>;
back: () => void | Promise<void>;
}
Expand All @@ -88,13 +97,60 @@ interface FunnelHistory<TContextMap, TCurrentStep extends keyof TContextMap> {
- `push` (`function`): 새로운 단계로 이동해요. 이전 단계의 히스토리를 남겨요.
- `step` (`string`): 이동할 단계의 이름이에요.
- `context` (`object` | `function`): 이동할 단계의 상태를 나타내는 객체에요. 혹은 현재 단계 상태를 이용해서 다음 단계 상태를 만드는 함수를 넣을 수 있어요.
- `routeOption` (`object`): 이동할 단계에다가 설정할 수 있는 라우팅 옵션이에요. 패키지 별로 다른 옵션을 제공이 돼요. 자세한 내용은 하단의 [RouteOption](#routeoption)을 참고하세요.
- `replace` (`function`): 새로운 단계로 이동해요. 이전 단계의 히스토리를 남기지 않아요.
- `step` (`string`): 이동할 단계의 이름입니다.
- `context` (`object` | `function`): 이동할 단계의 상태를 나타내는 객체입니다. 혹은 현재 단계 상태를 이용해서 다음 단계 상태를 만드는 함수를 넣을 수 있어요.
- `routeOption` (`object`): 이동할 단계에다가 설정할 수 있는 라우팅 옵션이에요. 패키지 별로 다른 옵션을 제공이 돼요. 자세한 내용은 하단의 [RouteOption](#routeoption)을 참고하세요.
- `go` (`function`): 현재 인덱스 기준으로 지정된 단계로 이동해요.
- `index` (`number`): 이동하려는 단계의 인덱스에요. 현재 단계로부터의 **상대적인 위치**를 나타내요.
- `back` (`function`): 이전 단계로 이동해요.

### RouteOption

<Tabs items={useFunnelPackages.map((p) => p.packageTitle)} storageKey="favorite-package">
<Tabs.Tab>
```ts
interface RouteOption {
scroll?: boolean;
locale?: string;
shallow?: boolean;
}
```

자세한 내용은 해당 패키지의 [문서](https://nextjs.org/docs/pages/api-reference/functions/use-router#routerpush)를 참고하세요.

<Callout>
`@use-funnel/next` 에서는 의도적으로 `shallow` 옵션의 기본 값을 `true` 로 설정하고 있어요.
</Callout>
</Tabs.Tab>
<Tabs.Tab>
```ts
interface RouteOption {
preventScrollReset?: boolean;
unstable_flushSync?: boolean;
unstable_viewTransition?: boolean;
}
```

자세한 내용은 해당 패키지의 [문서](https://reactrouter.com/en/main/hooks/use-navigate)를 참고하세요.
</Tabs.Tab>
<Tabs.Tab>
```ts
interface RouteOption {}
```

해당 패키지에서는 옵션을 제공하고 있지 않아요.
</Tabs.Tab>
<Tabs.Tab>
```ts
interface RouteOption {}
```

해당 패키지에서는 옵션을 제공하고 있지 않아요.
</Tabs.Tab>
</Tabs>

## 예시

### 기본 예시
Expand Down
45 changes: 29 additions & 16 deletions packages/core/src/FunnelRender.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,61 @@
import { Fragment, useMemo, useRef } from 'react';
import { AnyStepContextMap, FunnelHistory, FunnelStep } from './core.js';
import { AnyStepContextMap, FunnelHistory, FunnelStep, RouteOption } from './core.js';
import { FunnelRouterTransitionOption } from './router.js';

export type FunnelStepByContextMap<TStepContextMap extends AnyStepContextMap> = {
[TStepKey in keyof TStepContextMap & string]: FunnelStep<TStepContextMap, TStepKey>;
export type FunnelStepByContextMap<TStepContextMap extends AnyStepContextMap, TRouteOption extends RouteOption> = {
[TStepKey in keyof TStepContextMap & string]: FunnelStep<TStepContextMap, TStepKey, TRouteOption>;
}[keyof TStepContextMap & string];

export type FunnelRenderReady<TStepContextMap extends AnyStepContextMap> = FunnelStepByContextMap<TStepContextMap>;
export type FunnelRenderReady<
TStepContextMap extends AnyStepContextMap,
TRouteOption extends RouteOption,
> = FunnelStepByContextMap<TStepContextMap, TRouteOption>;

export type FunnelRenderOverlayHandler = {
close: () => void;
};

export type RenderResult<TStepContextMap extends AnyStepContextMap, TStepKey extends keyof TStepContextMap & string> =
export type RenderResult<
TStepContextMap extends AnyStepContextMap,
TStepKey extends keyof TStepContextMap & string,
TRouteOption extends RouteOption,
> =
| {
type: 'render';
render: (step: FunnelStep<TStepContextMap, TStepKey>) => React.ReactNode;
render: (step: FunnelStep<TStepContextMap, TStepKey, TRouteOption>) => React.ReactNode;
}
| {
type: 'overlay';
render: (step: FunnelStep<TStepContextMap, TStepKey> & FunnelRenderOverlayHandler) => React.ReactNode;
render: (
step: FunnelStep<TStepContextMap, TStepKey, TRouteOption> & FunnelRenderOverlayHandler,
) => React.ReactNode;
};

export interface FunnelRenderComponentProps<TStepContextMap extends AnyStepContextMap> {
funnel: FunnelRenderReady<TStepContextMap>;
export interface FunnelRenderComponentProps<
TStepContextMap extends AnyStepContextMap,
TRouteOption extends RouteOption,
> {
funnel: FunnelRenderReady<TStepContextMap, TRouteOption>;
steps: {
[TStepKey in keyof TStepContextMap & string]:
| RenderResult<TStepContextMap, TStepKey>
| ((step: FunnelStep<TStepContextMap, TStepKey>) => React.ReactNode);
| RenderResult<TStepContextMap, TStepKey, TRouteOption>
| ((step: FunnelStep<TStepContextMap, TStepKey, TRouteOption>) => React.ReactNode);
};
}

function neverUseHistory(): never {
throw new Error('Cannot use history in overlay render before steps.');
}

const overlayBeforeHistory: FunnelHistory<any, any> = {
const overlayBeforeHistory: FunnelHistory<any, any, any> = {
push: neverUseHistory,
replace: neverUseHistory,
go: neverUseHistory,
back: neverUseHistory,
};

export function FunnelRender<TStepContextMap extends AnyStepContextMap>(
props: FunnelRenderComponentProps<TStepContextMap>,
export function FunnelRender<TStepContextMap extends AnyStepContextMap, TRouteOption extends RouteOption>(
props: FunnelRenderComponentProps<TStepContextMap, TRouteOption>,
) {
const { funnel, steps } = props;
const render = steps[funnel.step];
Expand Down Expand Up @@ -124,14 +136,15 @@ export function FunnelRender<TStepContextMap extends AnyStepContextMap>(
function useOverwriteFunnelHistoryTransitionArgument<
TStepContextMap extends AnyStepContextMap,
TStepKey extends keyof TStepContextMap & string,
TRouteOption extends RouteOption,
>(
history: FunnelHistory<TStepContextMap, TStepKey>,
history: FunnelHistory<TStepContextMap, TStepKey, TRouteOption>,
callback: (
step: TStepKey,
context?: TStepContextMap[TStepKey],
option?: FunnelRouterTransitionOption,
) => FunnelRouterTransitionOption,
): FunnelHistory<TStepContextMap, TStepKey> {
): FunnelHistory<TStepContextMap, TStepKey, TRouteOption> {
const callbackRef = useRef(callback);
callbackRef.current = callback;
return useMemo(() => {
Expand Down
50 changes: 31 additions & 19 deletions packages/core/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,28 @@ export type GetFunnelStateByName<TFunnelState extends AnyFunnelState, TName exte
{ step: TName }
>;

type TransitionFnArguments<TName extends PropertyKey, TContext> =
export type RouteOption = Partial<Record<string, any>>;

type TransitionFnArguments<TName extends PropertyKey, TContext, TRouteOption extends RouteOption> =
Partial<TContext> extends TContext
? [target: TName, context?: TContext, option?: FunnelRouterTransitionOption]
: [target: TName, context: TContext, option?: FunnelRouterTransitionOption];
? [target: TName, context?: TContext, option?: FunnelRouterTransitionOption & TRouteOption]
: [target: TName, context: TContext, option?: FunnelRouterTransitionOption & TRouteOption];

type TransitionFn<TState extends AnyFunnelState, TNextState extends AnyFunnelState> = <
TName extends TNextState['step'],
>(
type TransitionFn<
TState extends AnyFunnelState,
TNextState extends AnyFunnelState,
TRouteOption extends RouteOption,
> = <TName extends TNextState['step']>(
...args:
| TransitionFnArguments<
TName,
CompareMergeContext<TState['context'], GetFunnelStateByName<TNextState, TName>['context']>
CompareMergeContext<TState['context'], GetFunnelStateByName<TNextState, TName>['context']>,
TRouteOption
>
| [
target: TName,
callback: (prev: TState['context']) => GetFunnelStateByName<TNextState, TName>['context'],
option?: FunnelRouterTransitionOption,
option?: FunnelRouterTransitionOption & TRouteOption,
]
) => Promise<GetFunnelStateByName<TNextState, TName>>;

Expand All @@ -42,38 +47,45 @@ export type FunnelStateByContextMap<TStepContextMap extends AnyStepContextMap> =
export type FunnelTransition<
TStepContextMap extends AnyStepContextMap,
TStepKey extends keyof TStepContextMap & string,
TRouteOption extends RouteOption,
> = TransitionFn<
FunnelState<TStepKey, TStepContextMap[TStepKey]>,
{
[NextStepKey in keyof TStepContextMap & string]: FunnelState<NextStepKey, TStepContextMap[NextStepKey]>;
}[keyof TStepContextMap & string]
}[keyof TStepContextMap & string],
TRouteOption
>;

export interface FunnelHistory<
TStepContextMap extends AnyStepContextMap,
TStepKey extends keyof TStepContextMap & string,
TRouteOption extends RouteOption,
> {
push: FunnelTransition<TStepContextMap, TStepKey>;
replace: FunnelTransition<TStepContextMap, TStepKey>;
push: FunnelTransition<TStepContextMap, TStepKey, TRouteOption>;
replace: FunnelTransition<TStepContextMap, TStepKey, TRouteOption>;
go: (index: number) => void | Promise<void>;
back: () => void | Promise<void>;
}

interface GlobalFunnelHistory<TStepContextMap extends AnyStepContextMap> {
push: FunnelTransition<TStepContextMap, keyof TStepContextMap & string>;
replace: FunnelTransition<TStepContextMap, keyof TStepContextMap & string>;
interface GlobalFunnelHistory<TStepContextMap extends AnyStepContextMap, TRouteOption extends RouteOption> {
push: FunnelTransition<TStepContextMap, keyof TStepContextMap & string, TRouteOption>;
replace: FunnelTransition<TStepContextMap, keyof TStepContextMap & string, TRouteOption>;
}

export type FunnelStep<TStepContextMap extends AnyStepContextMap, TStepKey extends keyof TStepContextMap & string> = {
export type FunnelStep<
TStepContextMap extends AnyStepContextMap,
TStepKey extends keyof TStepContextMap & string,
TRouteOption extends RouteOption,
> = {
step: TStepKey;
context: TStepContextMap[TStepKey];
history: FunnelHistory<TStepContextMap, TStepKey>;
history: FunnelHistory<TStepContextMap, TStepKey, TRouteOption>;
index: number;
historySteps: FunnelState<keyof TStepContextMap & string, TStepContextMap[keyof TStepContextMap]>[];
};

export type FunnelStepByContextMap<TStepContextMap extends AnyStepContextMap> = {
[key in keyof TStepContextMap & string]: FunnelStep<TStepContextMap, key>;
export type FunnelStepByContextMap<TStepContextMap extends AnyStepContextMap, TRouteOption extends RouteOption> = {
[key in keyof TStepContextMap & string]: FunnelStep<TStepContextMap, key, TRouteOption>;
}[keyof TStepContextMap & string] & {
history: GlobalFunnelHistory<TStepContextMap>;
history: GlobalFunnelHistory<TStepContextMap, TRouteOption>;
};
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { AnyFunnelState } from './core.js';
export * from './stepBuilder.js';
export { CreateFunnelStepType } from './typeUtil.js';
export * from './useFunnel.js';
Loading

0 comments on commit 00c31d9

Please sign in to comment.