Skip to content

Commit

Permalink
Add origin to the state to identify the close action initiator
Browse files Browse the repository at this point in the history
  • Loading branch information
gilbarbara committed Feb 22, 2024
1 parent 026d97a commit 307dc4e
Show file tree
Hide file tree
Showing 16 changed files with 124 additions and 22 deletions.
11 changes: 9 additions & 2 deletions docs/callback.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ It will receive an object with the current state.
controlled: true,
index: 0,
lifecycle: 'init',
origin: null,
size: 4,
status: 'running',
step: { the.current.step },
Expand All @@ -24,6 +25,7 @@ It will receive an object with the current state.
controlled: true,
index: 0,
lifecycle: 'beacon',
origin: null,
size: 4,
status: 'running',
step: { the.current.step },
Expand All @@ -37,6 +39,7 @@ It will receive an object with the current state.
controlled: true,
index: 0,
lifecycle: 'complete',
origin: null,
size: 4,
status: 'running',
step: { the.current.step },
Expand All @@ -47,7 +50,7 @@ It will receive an object with the current state.
## Usage

```jsx
import Joyride, { ACTIONS, EVENTS, STATUS } from 'react-joyride';
import Joyride, { ACTIONS, EVENTS, ORIGIN, STATUS } from 'react-joyride';

export class App extends React.Component {
state = {
Expand All @@ -62,7 +65,11 @@ export class App extends React.Component {
};

handleJoyrideCallback = data => {
const { action, index, status, type } = data;
const { action, index, origin, status, type } = data;

if (action === ACTIONS.CLOSE && origin === ORIGIN.KEYBOARD) {
// do something
}

if ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND].includes(type)) {
// Update state to advance the tour
Expand Down
4 changes: 3 additions & 1 deletion docs/constants.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Joyride uses a few constants to keep its state and lifecycle.
You should use them in your component for the callback events.

```javascript
import Joyride, { ACTIONS, EVENTS, LIFECYCLE, STATUS } from 'react-joyride';
import Joyride, { ACTIONS, EVENTS, LIFECYCLE, ORIGIN, STATUS } from 'react-joyride';
```

ACTIONS - The action that updated the state.
Expand All @@ -13,6 +13,8 @@ EVENTS - The type of the event.

LIFECYCLE - The step lifecycle.

ORIGIN - The origin of the `CLOSE` action.

STATUS - The tour's status.

Consult the [source code](https://github.com/gilbarbara/react-joyride/blob/main/src/literals/index.ts) for more information.
1 change: 1 addition & 0 deletions e2e/controlled.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Controlled from '../test/__fixtures__/Controlled';
function formatCallbackResponse(input: Partial<CallBackProps>) {
return {
controlled: true,
origin: null,
size: 6,
status: STATUS.RUNNING,
...input,
Expand Down
1 change: 1 addition & 0 deletions e2e/scroll.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Scroll from '../test/__fixtures__/Scroll';
function formatCallbackResponse(input: Partial<CallBackProps>) {
return {
controlled: false,
origin: null,
size: 5,
status: STATUS.RUNNING,
...input,
Expand Down
1 change: 1 addition & 0 deletions e2e/standard.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Standard from '../test/__fixtures__/Standard';
function formatCallbackResponse(input: Partial<CallBackProps>) {
return {
controlled: false,
origin: null,
size: 6,
status: STATUS.RUNNING,
...input,
Expand Down
6 changes: 3 additions & 3 deletions src/components/Step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ export default class JoyrideStep extends React.Component<StepProps> {
continuous,
controlled,
debug,
helpers,
index,
lifecycle,
size,
status,
step,
store,
} = this.props;
const { changed, changedFrom } = treeChanges(previousProps, this.props);
const state = { action, controlled, index, lifecycle, size, status };
const state = helpers.info();

const skipBeacon =
continuous && action !== ACTIONS.CLOSE && (index > 0 || action === ACTIONS.PREV);
Expand Down Expand Up @@ -175,7 +175,7 @@ export default class JoyrideStep extends React.Component<StepProps> {
const { helpers, step } = this.props;

if (!step.disableOverlayClose) {
helpers.close();
helpers.close('overlay');
}
};

Expand Down
4 changes: 2 additions & 2 deletions src/components/Tooltip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ export default class JoyrideTooltip extends React.Component<TooltipProps> {
event.preventDefault();
const { helpers } = this.props;

helpers.close();
helpers.close('button_close');
};

handleClickPrimary = (event: React.MouseEvent<HTMLElement>) => {
event.preventDefault();
const { continuous, helpers } = this.props;

if (!continuous) {
helpers.close();
helpers.close('button_primary');

return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ class Joyride extends React.Component<Props, State> {

if (lifecycle === LIFECYCLE.TOOLTIP) {
if (event.code === 'Escape' && step && !step.disableCloseOnEsc) {
this.store.close();
this.store.close('keyboard');
}
}
};
Expand Down
7 changes: 7 additions & 0 deletions src/literals/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ export const LIFECYCLE = {
ERROR: 'error',
} as const;

export const ORIGIN = {
BUTTON_CLOSE: 'button_close',
BUTTON_PRIMARY: 'button_primary',
KEYBOARD: 'keyboard',
OVERLAY: 'overlay',
} as const;

export const STATUS = {
IDLE: 'idle',
READY: 'ready',
Expand Down
28 changes: 21 additions & 7 deletions src/modules/store.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Props as FloaterProps } from 'react-floater';
import { objectKeys, omit } from '@gilbarbara/helpers';
import is from 'is-lite';

import { ACTIONS, LIFECYCLE, STATUS } from '~/literals';

import { AnyObject, State, Status, Step, StoreHelpers, StoreOptions } from '~/types';
import { Origin, State, Status, Step, StoreHelpers, StoreOptions } from '~/types';

import { hasValidKeys } from './helpers';

Expand All @@ -16,10 +17,11 @@ const defaultState: State = {
controlled: false,
index: 0,
lifecycle: LIFECYCLE.INIT,
origin: null,
size: 0,
status: STATUS.IDLE,
};
const validKeys = ['action', 'index', 'lifecycle', 'status'];
const validKeys = objectKeys(omit(defaultState, 'controlled', 'size'));

class Store {
private beaconPopper: PopperData | null;
Expand All @@ -38,6 +40,7 @@ class Store {
continuous,
index: is.number(stepIndex) ? stepIndex : 0,
lifecycle: LIFECYCLE.INIT,
origin: null,
status: steps.length ? STATUS.READY : STATUS.IDLE,
},
true,
Expand All @@ -59,6 +62,7 @@ class Store {
controlled: this.store.get('controlled') || false,
index: parseInt(this.store.get('index'), 10),
lifecycle: this.store.get('lifecycle') || '',
origin: this.store.get('origin') || null,
size: this.store.get('size') || 0,
status: (this.store.get('status') as Status) || '',
};
Expand All @@ -74,6 +78,7 @@ class Store {
controlled,
index: nextIndex,
lifecycle: state.lifecycle ?? LIFECYCLE.INIT,
origin: state.origin ?? null,
size: state.size ?? size,
status: nextIndex === size ? STATUS.FINISHED : state.status ?? status,
};
Expand All @@ -95,14 +100,22 @@ class Store {
private setState(nextState: Partial<StateWithContinuous>, initial: boolean = false) {
const state = this.getState();

const { action, index, lifecycle, size, status } = {
const {
action,
index,
lifecycle,
origin = null,
size,
status,
} = {
...state,
...nextState,
};

this.store.set('action', action);
this.store.set('index', index);
this.store.set('lifecycle', lifecycle);
this.store.set('origin', origin);
this.store.set('size', size);
this.store.set('status', status);

Expand All @@ -121,7 +134,7 @@ class Store {
this.listener = listener;
};

public setSteps = (steps: Array<AnyObject>) => {
public setSteps = (steps: Array<Step>) => {
const { size, status } = this.getState();
const state = {
size: steps.length,
Expand Down Expand Up @@ -171,15 +184,15 @@ class Store {
this.tooltipPopper = null;
};

public close = () => {
public close = (origin: Origin | null = null) => {
const { index, status } = this.getState();

if (status !== STATUS.RUNNING) {
return;
}

this.setState({
...this.getNextState({ action: ACTIONS.CLOSE, index: index + 1 }),
...this.getNextState({ action: ACTIONS.CLOSE, index: index + 1, origin }),
});
};

Expand All @@ -198,7 +211,7 @@ class Store {
});
};

public info = (): AnyObject => this.getState();
public info = (): State => this.getState();

public next = () => {
const { index, status } = this.getState();
Expand Down Expand Up @@ -300,6 +313,7 @@ class Store {
...this.getState(),
...state,
action: state.action ?? ACTIONS.UPDATE,
origin: state.origin ?? null,
},
true,
),
Expand Down
3 changes: 2 additions & 1 deletion src/types/common.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { CSSProperties, ReactNode } from 'react';
import { ValueOf } from 'type-fest';

import { ACTIONS, EVENTS, LIFECYCLE, STATUS } from '~/literals';
import { ACTIONS, EVENTS, LIFECYCLE, ORIGIN, STATUS } from '~/literals';

export type Actions = ValueOf<typeof ACTIONS>;
export type Events = ValueOf<typeof EVENTS>;
export type Lifecycle = ValueOf<typeof LIFECYCLE>;
export type Origin = ValueOf<typeof ORIGIN>;
export type Status = ValueOf<typeof STATUS>;

export type AnyObject<T = any> = Record<string, T>;
Expand Down
8 changes: 5 additions & 3 deletions src/types/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PartialDeep, SetRequired, Simplify, ValueOf } from 'type-fest';

import type { StoreInstance } from '~/modules/store';

import { Actions, Events, Lifecycle, Locale, Placement, Status, Styles } from './common';
import { Actions, Events, Lifecycle, Locale, Origin, Placement, Status, Styles } from './common';

export type BaseProps = {
beaconComponent?: ElementType<BeaconRenderProps>;
Expand Down Expand Up @@ -51,6 +51,7 @@ export type CallBackProps = {
controlled: boolean;
index: number;
lifecycle: Lifecycle;
origin: Origin | null;
size: number;
status: Status;
step: Step;
Expand Down Expand Up @@ -85,6 +86,7 @@ export type State = {
controlled: boolean;
index: number;
lifecycle: Lifecycle;
origin: Origin | null;
size: number;
status: Status;
};
Expand Down Expand Up @@ -145,9 +147,9 @@ export type StepProps = Simplify<
>;

export type StoreHelpers = {
close: () => void;
close: (origin?: Origin | null) => void;
go: (nextIndex: number) => void;
info: (state: State) => void;
info: () => State;
next: () => void;
open: () => void;
prev: () => void;
Expand Down
1 change: 1 addition & 0 deletions test/__fixtures__/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function callbackResponseFactory(initial?: Partial<CallBackProps>) {
return (input: Partial<CallBackProps>) => {
return {
controlled,
origin: null,
size,
status,
step: expect.any(Object),
Expand Down
Loading

0 comments on commit 307dc4e

Please sign in to comment.