Jun 26, 2020 12:44 PM
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 Airtable Blocks SDK but the Design guide does seem to reference it Quip
TIA!
Jul 01, 2020 04:11 PM
@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} →
</Button>
</div>
</div>
);
};
SetupWizard.Screen = SetupWizardScreen;
export default SetupWizard;
Jul 01, 2020 04:25 PM
this is awesome thanks!