Skip to content

Commit

Permalink
feat: provide APIs for integrating routed steps (#17)
Browse files Browse the repository at this point in the history
* feat: add actions param to createWizard, accepts navigate function

* feat(sync): add sync method for external location changes
  • Loading branch information
HipsterBrown authored May 3, 2022
1 parent 552f293 commit 0f086c3
Show file tree
Hide file tree
Showing 22 changed files with 618 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
node_modules
dist
examples/**/package-lock.json
examples/**/pnpm-lock.yaml
examples/**/.cache
examples/**/.parcel-cache
docs/
Expand Down
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,53 @@ wizard.goToPreviousStep({ values: { firstName: '', lastName: '' } });
console.log(wizard.currentValues); // { firstName: '', lastName: '' }
```

**Navigation**

In order to act as a good web citizen, robo-wizard provides a way to integrate with client-side routing APIs for steps that map to real URL paths.

```typescript
import { createWizard } from 'robo-wizard';

const wizard = createWizard(
['first', 'second', 'third'],
{ firstName: '', lastName: '' }
{
navigate: () => history.pushState({}, '', `/${wizard.currentStep}`)
}
);

window.addEventListener('popstate', () => {
const stepFromPath = window.location.pathname.split('/').pop();
if (stepFromPath && stepFromPath !== wizard.currentStep) wizard.sync({ step: stepFromPath })
})

wizard.start(updatedWizard => { console.log('Updated!', updatedWizard.currentStep), updatedWizard.currentValues });

console.log(wizard.currentValues); // { firstName: '', lastName: '' }

wizard.goToNextStep({ values: { firstName: 'Jane' } });

console.log(wizard.currentValues); // { firstName: 'Jane', lastName: '' }

wizard.goToNextStep({ values: { lastName: 'Doe' } });

console.log(wizard.currentValues); // { firstName: 'Jane', lastName: 'Doe' }

wizard.goToPreviousStep({ values: { firstName: '', lastName: '' } });

console.log(wizard.currentValues); // { firstName: '', lastName: '' }
```

While the above example demonstrates using the [History API](http://developer.mozilla.org/en-US/docs/Web/API/History_API), see the examples directory for how the [`history`](https://www.npmjs.com/package/history) and [`react-router`](https://www.npmjs.com/package/react-router) packages can be integrated.

## Examples

Check out the [examples](./examples/) directory to see a sample of usage with HTML and a few framework integrations.

## Work In Progress Roadmap

- [ ] `createForm` state machine generator to control form state for a step in the wizard
- [ ] example integration of routed wizard steps, i.e. using `react-router` or `history` packages
- [X] example integration of routed wizard steps, i.e. using `react-router` or `history` packages
- [ ] add history stack to internal state machine to lookup the previous step when using custom progression

## Local Development
Expand Down
24 changes: 24 additions & 0 deletions examples/history/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
15 changes: 15 additions & 0 deletions examples/history/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 51 additions & 0 deletions examples/history/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com?plugins=forms"></script>
<title>Robo Wizard Example App</title>
</head>

<body>
<div class="px-5">
<h1 class="text-2xl font-bold">Robo Wizard w/ History</h1>

<p class="font-semibold mb-8 underline uppercase">
<span data-current-step></span> step
</p>

<form data-event="next" class="mb-4">
<div class="mb-6 hidden" data-step="first">
<label for="firstName" id="firstName-label" class="block mb-2">First Name:</label>
<input class="border-2 border-solid border-gray-600 px-4 py-2" type="text" name="firstName" id="firstName"
aria-label="firstName-label" />
</div>

<div class="mb-6 hidden" data-step="second">
<label for="lastName" id="lastName-label" class="block mb-2">Last Name:</label>
<input class="border-2 border-solid border-gray-600 px-4 py-2" type="text" name="lastName" id="lastName"
aria-label="lastName-label" />
</div>

<div class="mb-6 hidden" data-step="third">
<p class="text-green-600">Welcome <span data-values="firstName"></span> <span data-values="lastName"></span>!
</p>
</div>

<div class="flex w-32 justify-between">
<button type="button" data-event="previous" class="p-3 mr-4" role="link">
Previous
</button>
<button type="submit" class="py-3 px-8 border-2 border-gray-900">
Next
</button>
</div>
</form>
</div>
<script type="module" src="/src/main.ts"></script>
</body>

</html>
18 changes: 18 additions & 0 deletions examples/history/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "history",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"devDependencies": {
"typescript": "^4.5.4",
"vite": "^2.9.7"
},
"dependencies": {
"history": "^5.3.0",
"robo-wizard": "../../"
}
}
67 changes: 67 additions & 0 deletions examples/history/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { createWizard } from 'robo-wizard';
import history from 'history/browser';


type Values = {
firstName?: string;
lastName?: string;
};

const steps: HTMLElement[] = Array.prototype.slice.call(
document.querySelectorAll('[data-step]')
);
const currentStep = document.querySelector('[data-current-step]')
const currentValues = document.querySelectorAll<HTMLElement>('[data-values]')
const stepInputs = document.querySelectorAll<HTMLInputElement>('input');

const wizard = createWizard<Values>(steps.map(element => element.dataset.step), { firstName: '', lastName: '' }, {
navigate: () => {
history.push(`/${wizard.currentStep}`)
}
});

function render() {
if (currentStep) currentStep.textContent = wizard.currentStep;
currentValues.forEach((element: HTMLElement) => {
element.textContent = wizard.currentValues[element.dataset.values] || '';
});
stepInputs.forEach((element: HTMLInputElement) => {
element.value = wizard.currentValues[element.name] || '';
});

for (const step of steps) {
if (step.dataset.step === wizard.currentStep) {
step.classList.remove('hidden');
} else {
step.classList.add('hidden');
}
}
}

document.querySelectorAll<HTMLButtonElement>('button[data-event]').forEach(button =>
button.addEventListener('click', ({ target }) => {
const { dataset } = target as HTMLButtonElement;
if (dataset.event === 'previous') {
wizard.goToPreviousStep();
}
})
);

document.querySelectorAll<HTMLFormElement>('form[data-event]').forEach(form => {
form.addEventListener('submit', event => {
event.preventDefault();
const values = Object.fromEntries(new FormData(event.currentTarget as HTMLFormElement))
wizard.goToNextStep({ values });
});
});

if (history.location.pathname === '/') {
history.push(`/${steps[0].dataset.step}`)
}

history.listen(({ location }) => {
const stepFromPath = location.pathname.split('/').pop();
if (stepFromPath && stepFromPath !== wizard.currentStep) wizard.sync({ step: stepFromPath })
})

wizard.start(render);
1 change: 1 addition & 0 deletions examples/history/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
20 changes: 20 additions & 0 deletions examples/history/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ESNext", "DOM"],
"moduleResolution": "Node",
"strict": true,
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["src"]
}
24 changes: 24 additions & 0 deletions examples/react-router/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
17 changes: 17 additions & 0 deletions examples/react-router/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com?plugins=forms"></script>
<title>Robo-Wizard Example App</title>
</head>

<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

</html>
24 changes: 24 additions & 0 deletions examples/react-router/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "react-router",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"robo-wizard": "link:../.."
},
"devDependencies": {
"@types/react": "^18.0.8",
"@types/react-dom": "^18.0.3",
"@vitejs/plugin-react": "^1.3.2",
"typescript": "^4.6.4",
"vite": "^2.9.7"
}
}
Loading

0 comments on commit 0f086c3

Please sign in to comment.