Example code for the wizard UI component?

Hi team,
I’m working on an exciting block for the Devpost but that requires a user to prefill a few things (AWS key/access) and I see that in the Google Translate block, there is a nice Wizard set up page:

Is there some sample code somewhere where we can see how the wizard works? It’s not listed as a component on https://airtable.com/developers/blocks/api but the Design guide does seem to reference it https://quip.com/qtOIAHJyoiDt

TIA!

1 Like

@Tommy_Chan - I’ve made a minimal example that demonstrates how to make a wizard.

// Main.js
import {settingsButton} from '@airtable/blocks';
import {initializeBlock, useGlobalConfig} from '@airtable/blocks/ui';
import React, {useState, useEffect} from 'react';

import MySetupWizard from './MySetupWizard';

const Pages = {
    SETUP_WIZARD: 'setupWizard',
    MAIN: 'main',
};

function Main() {
    const globalConfig = useGlobalConfig();
    const apiKey = globalConfig.get('apiKey');

    const [currentBlockState, setCurrentBlockState] = useState(() => {
        const initialPage = !apiKey ? Pages.SETUP_WIZARD : Pages.MAIN;
        return {currentPage: initialPage};
    });

    switch (currentBlockState.currentPage) {
        case Pages.SETUP_WIZARD:
            return (
                  <MySetupWizard
                      onSetupComplete={() => {
                          setCurrentBlockState({
                              currentPage: Pages.MAIN,
                          });
                      }}
                  />
            );

        case Pages.MAIN:
            return (
                <div>
                  <p>
                    This is were you would put the actual functionality of your block.
                    If some action in your block should trigger returning to the wizard,
                    you should pass an `onAction` prop to your component that trigger changing the page with
                    `setCurrentBlockState`.
                  </p>

                  <p>
                    Additional pages, like a settings page that is not just part of your wizard would also be part of this case statement.
                  </p>
                </div>
            );
    }
}

initializeBlock(() => <Main />);

////////////////////////////////////////////////////////////////////////////////////////
// MySetupWizard.js
import {ViewportConstraint, Input, useViewport, useGlobalConfig} from '@airtable/blocks/ui';
import React, {useState} from 'react';

import SetupWizard from './SetupWizard';

const SetupScreenKeys = {
    intro: 'intro',
    apiSetup: 'apiSetup',
};

function getNextScreen(currentScreen) {
    switch (currentScreen) {
        case SetupScreenKeys.intro:
            return SetupScreenKeys.apiSetup;
        default:
            return null;
    }
}

function useSetupScreen() {
    const [currentScreen, setCurrentScreen] = useState(SetupScreenKeys.intro);

    function goToNextScreen() {
        const nextScreen = getNextScreen(currentScreen);
        if (nextScreen !== null) {
            setCurrentScreen(nextScreen);

            // Reset scroll position when navigating between screens.
            window.scrollTo(0, 0);
        }
    }

    return [currentScreen, goToNextScreen];
}

function MySetupWizard({ onSetupComplete }) {
    const globalConfig = useGlobalConfig();
    const viewport = useViewport();
    const [currentScreen, goToNextScreen] = useSetupScreen();
    const [apiKey, setApiKey] = useState();

    function onSaveApiKeyClick() {
        globalConfig.setAsync('apiKey', apiKey);
        onSetupComplete();
    }

    return (
        <ViewportConstraint maxFullscreenSize={{width: 540, height: 630}}>
            <SetupWizard currentScreen={currentScreen} goToNextScreen={goToNextScreen}>
                <SetupWizard.Screen
                    key={SetupScreenKeys.intro}
                    buttonText="Get started"
                    onButtonClick={() => {
                        goToNextScreen();
                        viewport.enterFullscreenIfPossible();
                    }}
                >
                    <div>
                      <h1>The Intro Page of the Wizard</h1>
                    </div>
                </SetupWizard.Screen>

                <SetupWizard.Screen
                    key={SetupScreenKeys.apiSetup}
                    buttonText="Save API key and exit wizard"
                    onButtonClick={onSaveApiKeyClick}
                >
                    <div>
                        <ol>
                          <li>
                            Follow some instructions!
                          </li>

                          <li>
                            Follow some more instructions!
                          </li>

                          <li>
                            <Input
                                type="text"
                                onChange={(e) => setApiKey(e.target.value)}
                                placeholder="Your API Key HERE"
                            />
                          </li>
                        </ol>
                    </div>
                </SetupWizard.Screen>
            </SetupWizard>
        </ViewportConstraint>
    );
}

export default MySetupWizard;

////////////////////////////////////////////////////////////////////////////////////////
// SetupWizard.js
import {Button} from '@airtable/blocks/ui';
import React from 'react';
import _ from 'lodash';

function SetupWizard({ children, currentScreen, goToNextScreen }) {
    const screenIndicesByKey = {};
    React.Children.toArray(children).forEach((child, i) => {
      screenIndicesByKey[child.key.slice(2)] = i;
    });

    const screenIndex = screenIndicesByKey[currentScreen];
    const child = React.Children.toArray(children)[screenIndex];

    return React.cloneElement(child, {
        screenIndex,
        onButtonClick: child.props.onButtonClick || goToNextScreen,
    });
}

const SetupWizardScreen = ({
    children,
    screenIndex,
    buttonText,
    isButtonDisabled,
    onButtonClick,
}) => {
    const numScreens = 2;

    return (
        <div>
            <div>{children}</div>
            <div>
                <div
                    style={{
                        left: '50%',
                        transform: 'translateX(-50%)',
                        position: 'absolute',
                        display: 'flex',
                    }}
                >
                    {_.times(numScreens, (i) => (
                        <div
                            key={`dot-${i}`}
                            style={{
                                width: '.5rem',
                                height: '.5rem',
                                borderRadius: '50%',
                                marginRight: (i ===0 && numScreens === 2 || i !== 0 && i !== numScreens -1 ? '0.5rem': 0 ),
                                marginLeft: (i !== 0 && i !== numScreens - 1 ? '0.5rem' : 0 ),
                                backgroundColor: (screenIndex >= i ? 'hsla(0,0%,0%,0.5)' : 'hsla(0,0%,0%,0.25)' )
                            }}
                        />
                    ))}
                </div>
                <Button
                    onClick={onButtonClick}
                    disabled={isButtonDisabled}
                >
                    {buttonText} &rarr;
                </Button>
            </div>
        </div>
    );
};

SetupWizard.Screen = SetupWizardScreen;

export default SetupWizard;

1 Like

this is awesome thanks!