Skip to content

Create pdf based templates on the frontend using react

License

Notifications You must be signed in to change notification settings

guestbell/pdf-template

Repository files navigation

Pdf template

There are a lot of react steppers/wizards/onboarding libs out there. Onboarder is different:

  • Tiny
  • Non-linear - Jump between steps, lib predicts rest of the way and time taken
  • 0 dependencies - Build your UI or use our
  • Easy setup
  • Typed API

Install

npm install @guestbell/pdf-template --save

or

yarn add @guestbell/pdf-template

Demo

Complex UI or Simple example

Getting started

Following example will show you how to create wizard with:

  • Loop step (exit loop based on rules)
  • Step with form validation
  • Step with error message
  1. Put your imports in place
import Onboarder, { Steps, Structure } from "@guestbell/onboarder";
  1. Create state for your steps
type OnboarderState = {
  firstStep: never;
  loopStep: { 
    counter: number; 
    isDirty: boolean; 
    errorMessage?: string 
  };
  afterLoopStep: never;
  textStep: { message: string };
  finalStep: never;
};
  1. Create steps strongly typed with the help of generic Steps type. Each object key is a step
const steps: Steps<OnboarderState> = {
  firstStep: { Component: () => <>First step</> },
  loopStep: {
    Component: ({ setState, state }) => (
      <>
        <div>Loop step</div>
        {state.errorMessage && <div>Error: {state.errorMessage}</div>}
        <button
          onClick={() =>
            setState({ ...state, counter: state.counter + 1, isDirty: true })
          }
        >
          {state.counter.toString()}
        </button>
      </>
    ),
    initialState: {
      counter: 0,
      isDirty: false,
    },
    // Cleanup
    afterNext: ({ state, setState }) => {
      setState({ ...state, isDirty: false, errorMessage: undefined });
    },
    // On the flight validation that fires when you click next
    beforeNext: ({ state, setState }) => {
      if (state.counter < 2) {
        setState({
          ...state,
          errorMessage: "Increment to at least 2 because rules",
        });
        return false;
      }
      return true;
    },
  },
  afterLoopStep: {
    Component: ({ nextStep }) => (
      <>{nextStep === "loopStep" ? "We are looping" : "We're done looping."}</>
    ),
  },
  textStep: {
    Component: ({ setState, state }) => (
      <>
        <input
          value={state.message}
          onChange={(e) => setState({ ...state, message: e.target.value })}
        />
        <br />
        Message: {state.message}
      </>
    ),
    initialState: {
      message: "",
    },
  },
  finalStep: {
    Component: () => <>All done!!!</>,
  },
};
  1. Define structure - this tells the lib how to jump between steps
const structure: Structure<OnboarderState> = {
  firstStep: () => ({ loopStep: 1 }),
  loopStep: (state) => ({
    afterLoopStep: state.loopStep.isDirty ? 1 : -1,
  }),
  afterLoopStep: (state) => ({
    loopStep: state.loopStep?.counter < 3 ? 1 : -1,
    textStep: state.loopStep?.counter >= 3 ? 1 : -2,
  }),
  textStep: (state) => ({
    finalStep: (state.textStep?.message && 1) || -1,
  }),
};

Notice how the object has same (optional) keys like our steps. Each gets passed a global state, and returns an object with same (optional) keys like our steps. Values of this object work like this: a) Negative Number or zero means the step is allowed, but disabled based on a condition b) Positive Number means the step is allowed, actual number gives step priority, smaller number means higher priority c) Undefined or key missing means the step is never allowed 5. Put it all together

<Onboarder
  steps={steps}
  initialStep="firstStep"
  // array because multiple steps can be final
  finalSteps={['finalStep']}
  structure={structure}
  StepContainer={SimpleStepContainer}
/>
  1. Bonus: Add simple UI This example already works, but let's add some simple UI. You do this by providing a StepContainer prop to <Onboarder/>
import { StepContainerComponentProps } from "@guestbell/onboarder";

function SimpleStepContainer<TState extends {}>(
  props: React.PropsWithChildren<StepContainerComponentProps<TState>>
) {
  const {
    children,
    goToNextStep,
    hasNextStep,
    goToUndoStep,
    hasUndoStep,
    hasRedoStep,
    goToRedoStep,
    reset,
  } = props;
  return (
    <div>
      {children}
      <div style={{ marginTop: "1rem" }}>
        <button onClick={reset}>Reset</button>
        <button onClick={goToUndoStep} disabled={!hasUndoStep}>
          Previous
        </button>
        <button onClick={goToRedoStep} disabled={!hasRedoStep}>
          Next
        </button>
        <button onClick={goToNextStep} disabled={!hasNextStep}>
          Continue
        </button>
      </div>
    </div>
  );
}
  1. Done! You can see this example on github

API

Here

FAQ

How can I add my extra props to the step?

Notice that Steps has 2 generic parameters. First is the type of the state, you can use the second generic parameter to introduce extra props to your step.

How can I fire an action before going to next step?

Each step has a beforeNext async function. Use it to handle your logic. Return false if the navigation should be prevented.

How do I clean up step state after navigation?

Each step has a afterNext async function. Use it for cleanup.

Created and sponsored by

  • GuestBell - Customer centric online POS for Hotels and short terms stays.

Contributing

  1. Fork it!
  2. Create your feature branch: git checkout -b my-new-feature
  3. Commit your changes: git commit -am 'Add some feature'
  4. Push to the branch: git push origin my-new-feature
  5. Submit a pull request :D

License

MIT

About

Create pdf based templates on the frontend using react

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published