react-rtg

    1.1.23 • Public • Published

    About

    The idea for this library was to create a trip component library that is created in react way rather than json config, while still keeping the style in a separate css file built in such way that they can be easily overwritten and customized.

    Demo page

    Check example demo tour on github repo ./src/demo/Tour

    TLTR

    npm install react-rtg
    

    Basic usage

    import "react-rtg/build/index.css"; // import stylesheet - you can do it in sass files as well
    import React, {useState} from "react";
    import {Tour, Step} from "react-rtg";
    
    const TourGuide = ({isOpen, setOpen}) => {
        return (
            <Tour isOpen={isOpen} 
                  onClose={setOpen.bind(null, false)} 
                  onOpen={setOpen.bind(null, true)}>
                <Step placement="top-center">
                    <p>First step of tutorial placed on cenetr top of window</p>
                </Step>
                <Step selector="#buttonId" placement="bottom">
                    <p>Second on is under selected target element</p>
                </Step>
            </Tour>
        )
    };
    
    const App = () => {
        const [isTourOpen, setIsTourOpen] = useState(false)
        return (
            <TourGuide isOpen={isTourOpen} setOpen={setIsTourOpen}/>
            <button id="buttonId" onClick={setIsTourOpen.bind(null, ture)}> 
                Start Tour 
            </button>
        )
    }

    with context

    import "react-rtg/build/index.css"; // import stylesheet - you can do it in sass files as well
    import React, {useState} from "react";
    import {Tour, Step, TourRoot, useTour} from "react-rtg";
    
    const TourGuide = ({isOpen, setOpen}) => {
        return (
            <Tour id="tourId">
                <Step placement="top-center">
                    <p>First step of tutorial placed on cenetr top of window</p>
                </Step>
                <Step selector="#buttonId" placement="bottom">
                    <p>Second on is under selected target element</p>
                </Step>
            </Tour>
        )
    };
    
    const Menu = () => {
        const [tour] = useTour("tourId")
        return (
            <ul>
                <li onClick={tour.next}> Go to next</li>
                <li onClick={tour.open}> Sart tour</li>
                <li onClick={tour.close}> Finish tour</li>
            </ul>
        )
    }
    
    const Logger = () =>{
        const [tour] = useTour("tourId"); 
        const [log, setLog] = useSate("");
        
        useEffect(()=>{
            const onNext = (current, next, lenght) => {
                setLog(
                    `current step is ${current} 
                     next will be ${next}, 
                     is ${lenght} steps`
                )
            }
            tour.on("next", onNext);
            return () => tour.un("next", onNext);
        }, [tour, setLog])
        
        return (<span>{log}</span>)
    }
    
    const App = () => {
        return (
            <TourRoot> // wrap app in tour context
                <TourGuide/>
                <Menu/>
                <Logger/>
            </TourRoot>
        )
    }

    Spec

    Tour component

    Tour.propTypes = {
      id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), // tour id used in useTourControl
      children: PropTypes.arrayOf(PropTypes.element),
      className: PropTypes.string, // will add given class name to container and this className__content to content
      isOpen: PropTypes.bool,
      initialContent: PropTypes.func, // function returning initial content - it is shown if wait is present on first step
    
      onBeforeClose: PropTypes.func, // before close if return falsable the cancel close
      onBeforeOpen: PropTypes.func, // before open if return falsable then cancel open
      onClose: PropTypes.func, // after close
      onOpen: PropTypes.func, // after open eg. from click in pin
      onAfterClose: PropTypes.func,// called when modal dialog is not visible
      onAfterOpen: PropTypes.func, // called when modal dialog is visible
      onNext: PropTypes.func,// called before change step with : onNext(current, next, length): next: number or next step string id
      onChange: PropTypes.func, // fired on every step change, - before scroll animation start
      onAfterScroll: PropTypes.func, // fired on every step change, - after scroll animation is finished
      onFinish: PropTypes.func, // called onClose if is on last step
      onLock: PropTypes.func, // called when user try to navigate to lock steps
      onWait: PropTypes.func, // called when step is waiting to show - with true when waits and false when is finish
      startAt: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), // step index or string id
      closeOnBackdrop: PropTypes.bool, // if true then click on the backdrop mask will close tour
      offset: PropTypes.number, // offset of modal from target default 30
      offsetX: PropTypes.number, // by default not set, so it has general default 30
      offsetY: PropTypes.number, // ... similar like in x
      backdropOffset: PropTypes.number, // offset backdrop layer from target element
      backdropOffsetY: PropTypes.number, // vertical offset backdrop layer ....
      backdropOffsetX: PropTypes.number, // horizontal...
    
      pin: PropTypes.bool, // be default false, if true then after closing it shows pin button
      pinText: PropTypes.oneOfType([
        PropTypes.bool, // if is false then on hover don't show tool tip
        PropTypes.bool, // text on tool tip over pin button
      ]),
      pinOffset: PropTypes.number, // by default 0, offset from base placement of pin
    
      badge: PropTypes.oneOfType([
        PropTypes.bool, // if false then badge is hidden,
        PropTypes.func, // badge(current, length): string | customizing badge text
      ]),
      badgeClassName: PropTypes.string,
    
      closeButton: PropTypes.oneOfType([
        PropTypes.bool,// if false then closing button is hidden,
        PropTypes.func // closeButton(close : func) : react.element // function with close handle
      ]),
      closeButtonContent: PropTypes.oneOfType([
        PropTypes.string, // text content of button
        PropTypes.element, // react.element as content of button eg. svg element like fontawesome icon
      ]),
      closeButtonClassName: PropTypes.string, //
    
      controller: PropTypes.func,// export controlling functions : ({set, pre, next})
    
      controls: PropTypes.func,// controls({set, prev, next, current, length, close, lock}) : rect.element
      // you can return your own step selector/paginator which have set, prev, next function and
      // current, length values to disposition
    
      controlButtonsContent: PropTypes.shape({
        // for each button you can set custom content
        prev: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
        next: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
        selector: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
        current: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
      }),
      lastStepControls: PropTypes.oneOfType([
        PropTypes.func,// onFinishControls({close, prev, set}) : component
        PropTypes.element // react component
      ]),
      controlNumbers: PropTypes.bool, // if false hide number over 'step selector'
    
      modalRefreshTimeout: PropTypes.number, // advanced
      // If you change transition time on modal element from .8s to some bigger value
      // probably you have to increase as well refresh timeout.
      // By default value is 801 ms after this time Modal check to 'see' that
      // it have enough space or it have to change place, this is important on mobile device.
      scrollHiddenOverflow: PropTypes.bool, // by default true, if you pass false then if some 
      // content is overflowed container and it will be out of visible box then container will not be scrolled
      scrollTarget: PropTypes.instanceOf(HTMLElement),//by default is window but 
      // you can set other element like body
    }

    Step component

    Step.propTypes = {
      id: PropTypes.string, // step identification
      selector: PropTypes.string, // css selector if is not provided then it will be located in relation to window
      placement: PropTypes.oneOf([
        "center", // only in relation to window - in relation to selector it will cover object !!!
        "top", "bottom", "right", "left",
        "top-right", "top-left",
        "bottom-right", "bottom-left"
      ]),
      content: PropTypes.func,// content render function called with isWaiting bool arg
      // it takes precedence over regular children step content
      onBeforeShow: PropTypes.func, // called before show, you can prepare some action here
      onShow: PropTypes.func, // called after showing step but before scroll
      onAfterScroll: PropTypes.func, /// called after scrolling window to step position
      onBeforeNext: PropTypes.func, // if return false block go to next step
      onWait: PropTypes.func, // called when step is waiting to show - with true when waits and false when is finish
      approve: PropTypes.oneOfType([
        PropTypes.bool, // if you pass just 'approve=true' by default it will take click event
        PropTypes.shape({
          event: PropTypes.string, // if is set to 'none' then only promise option works
          target: PropTypes.string, // by default step selector.
          callback: PropTypes.func,
          // if event will be fired on target then callback will be called if return true then is approved
          delay: PropTypes.number, // delay from callback or pure event approve to go next slide
          promise: PropTypes.func, // a function which have to return promise, if promise is resolved then is approved
          lock: PropTypes.bool, // lock go next step until task is not done
        })
      ]),
      closeOnBackdrop: PropTypes.bool, // close on backdrop click - eg. you can set it on last step
      backdropOffset: PropTypes.number, // if is not set will take default value 5
      backdropOffsetX: PropTypes.number, // similar lik in general case
      backdropOffsetY: PropTypes.number, // ..like in x
      scroll: PropTypes.bool, //if false then on this step page is not scrolled
      scrollOffset: PropTypes.number, // by default scroll to display element on center of screen
      scrollOffsetX: PropTypes.number, // from horizontal screen center
      scrollOffsetY: PropTypes.number, // from vertical screen center
      offset: PropTypes.number, // if is not set will take general default value 30
      offsetX: PropTypes.number, // by default not set so it hase general default 30
      offsetY: PropTypes.number, // ... similar like in x
      pinPlacement: PropTypes.oneOf([
        "center", // if is pin option on, and this is not set then it will take modal placement
        "top", "bottom", "right", "left",
        "top-right", "top-left",
        "bottom-right", "bottom-left"
      ]),
      pinOffset: PropTypes.number, // offset from pin placement by default 0;
      pinText: PropTypes.oneOfType([
        PropTypes.bool, // if false then tool tip text is hidden, if true or unset then default text will be shown
        PropTypes.string, // text over hover pin if you pass empty string then tool tip will disappear
      ]),
      className: PropTypes.string, // class name for step wrapper
    
      wait: PropTypes.oneOfType([
        PropTypes.func, // a function returning promise
        PropTypes.number // a number of milliseconds to wait
      ])
    }

    useTour(id | null)

    const [tour] = useTour("tourId");
    
    //or
    const [tourMenager] = useTour();
    const _tour = tourMenager.get("tourId");
    
    tour.open();
    tour.isOpen// true
    
    tour2.isOpen // false
    tour2.open()
    tour2.isOpen //ture
    
    ///------ functions
    tour.open();
    tour.close(force: bool);//if force is true, even if the pin mode is true, it will not be displayed
    tour.next();
    tour.prev();
    tour.set(index: number); // jump to step number index
    tour.setWait({
      id: 'stepId', // if id is present it will take precedence over index
      index: 5, 
      wait: async () => {} // time in milliseconds or function which returns Promise
    }) 
    ///---- props
    tour.step.index // current index read onlny
    tour.step.props // curretn step compoment props - read onlny
    tour.step.target // current step target (html node)
    tour.isOpen // boolean read onlny
    tour.length // steps amount
    
    function onClose(){
        console.log("RTG dialog closed")
        tour.un("close", onClose);
    }
    
    tour.on("beforeClose", () => console.log("before close dialog"));
    tour.on("close", onClose);
    tour.on("afterClose", () => {}) // called async in setTimeout
    
    tour.on("beforeOpen", () => {}) // if return false then is canceled
    tour.on("open", () => {});
    tour.on("afterClose", () => {}) // called async in setTimeout
    
    toru.on("next", (fromIndex, toIndex, tourLenght, fromId) => nextStep: index|id)
    toru.on("show", ({index, id}) => {});
    tour.on("lock", () => {});
    tour.on("wait", ({index, state, id}) => {});
    tour.on("change", ({index, props}) => {})
    
    tour.on("afterScroll", () => console.log("aftetr scrolling window to target position"));
    tour.on("finish", () => {})

    hooks order:

    • Open tour dialog first time
      • onBeforeOpen // before open process start - if it's return false then opening will be aborted
      • onNext // before next is executed - onNext(current, next, length): index|null
      • onWait // *if step have wait attr and is waiting- onWait({index, true, id})
      • onWait // *if step have wait attr and is ready- onWait({index, false, id})
      • onBeforeShow (on Step component) - called before given step well be displayed - this can be only added to Step component
      • onOpen // called in during opening process
      • onChange // after current displayed step is changed onChange(index, Step.props)
      • onAfterOpen // called when step is visible all process of rendering is finished
    • Next step
      • onNext // you can return index of next step
      • onLock// called while trying to change step index on locked one
      • onBeforeNext (on Step component) - called before change given step on next one
      • onWait // *if step have wait attr and is waiting- onWait({index, true, id})
      • onWait // *if step have wait attr and is ready - onWait({index, false, id})
      • onBeforeShow (on Step component) - called before given step well be displayed -(attr on Step component)
      • onChange // called after step is changed with onChange(index, Step.props)
      • onAfterScroll // called when scrolling to target element is over
    • Close tour dialog
      • onBeforeClose // on closing process start - if you return false then closing will be aborted
      • onClose // on closing but still before
      • onFinish // on close on last step
      • onAfterClose // dialog is close

    Styling

    @import "../../node_modules/react-rtg/build/index.css"
    
    .rtg__conatiner{
      --primary-color: #0b708b;
      --wating-color: #c00021;
      --pin-color: #0fa9d0;
      --arrow-size: 15px;
    } 
    // you can change style of each step
    .rtg__container--step-3 .rtg__modal{
      --arrow-size: 30px;
    }
    .my-class.rtg.__container{}
    
    .rtg__modal{
        background: black;
        color: white;
        p {
          color:white;
        }
        max-width: 350px;
    }
    .rtg__modal--top-center:after {
        content: none // remove arrov indecates target under modal dialog
    }
    .rtg__modal--waiting{} // for styling modal when is waiting to next step ready
    
    .rtg__button--close{}
    .rtg__button--open{}
    .rtg__button--next{}
    .rtg__button--prev{}
    .rtg__button--selector{}
    .rtg__button--current{}
    
    .rtg__badge{} // plate with current step info counter
    .rtg__badge--waiting{} // for styling badge when is wating for next step rady
    .rtg__control {} // control button container
    .rtg__pin {} // pin button after temporary closing modal
    .rtg__backdrop{
        border-radius: 0px; // change backdrop hool border radius
    }

    Customization button icons

    import React from "react"
    import {Tour, Step} from "react-rtg"
    
    const TourGuide = () => {
        return (
            <Tour id={"tourId"}
                  closeButtonContent={<span>{"close"}</span>} 
                  controlButtonsContent = {{
                      prev: "prev",
                      next: <span>{"next"}</span>
                      selector: "*"
                      current: "@"
                  }}
                  badge={(current, length) => current + " from " + length}
                  lastStepControls={close => (
                        <button onClick={close.bind(null, true)}>Congratulations</button>
                  )}
                  pin
                  pinText="Clik heart to open tutorail agina"> 
                <Step placement={"center"}} id={'firstStepId'}>
                    <p>
                        This tip is on center of screen <br/>
                    </p>
                </Step>
            </Tour>
        )
    }
    
    export default TourGuide

    Full custom - control buttons component

    import React, {useCallback} from "react";
    import {Tour, Step} from "react-rtg";
    
    const TourGuide = () => {
        const controls = useCallback(({prev, next, close, current, length})=>{
            return (
                <div className={"some-class__controls"}>
                    <button onClick={close.bind(null, true)} 
                            className={"btn btn--skip"}> 
                                skip 
                    </button>
                    {current !== 0 &&
                        <button onClick={prev} 
                                className={"btn btn--prev"}>
                            Prev
                        </button>
                    }
                    <button onClick={next} 
                            className={"btn btn--next"}>
                        { ((current === length - 1) ? "Last" : "Next") + ` ${current + 1} / ${length}`}
                    </button>
                </div>
            )
        }, [])
        
        return (
            <Tour id={"tourId"}
                  closeButton={false} 
                  badge={false} 
                  controls={controls} 
                  closeOnBackdrop
                  pin> 
                <Step placement={"center"}}>
                    <p>
                        This tip is on center of screen <br/>
                        If you click on backdrop then modal will be closed.
                        Check it to see pin !!!
                    </p>
                </Step>
            </Tour>
        )
    }

    Changelog

    • 1.1 - Fully backward compatible

      • Update dev built libraries
        • npm vulnerabilities
        • webpack plugins (postcss, teaser, and many others)
        • React from 16 to 17
      • Adding 'wait' attr to Step component
        • wait on Step component
        • onWait on Step component
        • onWait on Tour component
        • setWait on useTour hook
        • tour.on('wait', cb) on useTour hook
        • adding --waiting BEM state to Modal and Badge components
      • Adding id to Step component
        • modify onNext args now contains component id as last arg
        • now you can return step id from onNext hook
        • you can pass index or id to startAt
        • you can use index or id to setWait from useTour hook

    Install

    npm i react-rtg

    DownloadsWeekly Downloads

    35

    Version

    1.1.23

    License

    MIT

    Unpacked Size

    66.6 kB

    Total Files

    4

    Last publish

    Collaborators

    • avatar