feat(stepper,updater): add progress-aware stepper flows and updater countdown states
This commit is contained in:
@@ -1,7 +1,45 @@
|
||||
import { html } from '@design.estate/dees-element';
|
||||
import { DeesStepper, type IStep } from './dees-stepper.js';
|
||||
|
||||
const demoSteps: IStep[] = [
|
||||
const waitForProgressTick = async (timeoutArg: number, signal?: AbortSignal): Promise<boolean> => {
|
||||
return new Promise((resolve) => {
|
||||
let completed = false;
|
||||
|
||||
const finish = (result: boolean) => {
|
||||
if (completed) {
|
||||
return;
|
||||
}
|
||||
|
||||
completed = true;
|
||||
if (signal) {
|
||||
signal.removeEventListener('abort', handleAbort);
|
||||
}
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
const handleAbort = () => {
|
||||
window.clearTimeout(timeoutId);
|
||||
finish(false);
|
||||
};
|
||||
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
finish(true);
|
||||
}, timeoutArg);
|
||||
|
||||
if (signal) {
|
||||
signal.addEventListener('abort', handleAbort, { once: true });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const createContinueMenuOptions = (labelArg = 'Continue') => [
|
||||
{
|
||||
name: labelArg,
|
||||
action: async (stepper?: DeesStepper) => stepper?.goNext(),
|
||||
},
|
||||
];
|
||||
|
||||
const createDemoSteps = (): IStep[] => [
|
||||
{
|
||||
title: 'Account Setup',
|
||||
content: html`
|
||||
@@ -10,9 +48,7 @@ const demoSteps: IStep[] = [
|
||||
<dees-input-text key="password" label="Create Password" type="password" required></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Continue', action: async (stepper) => stepper!.goNext() },
|
||||
],
|
||||
menuOptions: createContinueMenuOptions(),
|
||||
},
|
||||
{
|
||||
title: 'Profile Details',
|
||||
@@ -22,9 +58,7 @@ const demoSteps: IStep[] = [
|
||||
<dees-input-text key="lastName" label="Last Name" required></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Continue', action: async (stepper) => stepper!.goNext() },
|
||||
],
|
||||
menuOptions: createContinueMenuOptions(),
|
||||
},
|
||||
{
|
||||
title: 'Contact Information',
|
||||
@@ -34,9 +68,74 @@ const demoSteps: IStep[] = [
|
||||
<dees-input-text key="company" label="Company"></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Continue', action: async (stepper) => stepper!.goNext() },
|
||||
],
|
||||
menuOptions: createContinueMenuOptions(),
|
||||
},
|
||||
{
|
||||
title: 'Provision Workspace',
|
||||
content: html`
|
||||
<dees-panel>
|
||||
<p>
|
||||
We are creating your starter workspace, applying your onboarding choices,
|
||||
and preparing a live preview. This step moves forward automatically when
|
||||
the environment is ready.
|
||||
</p>
|
||||
</dees-panel>
|
||||
`,
|
||||
progressStep: {
|
||||
label: 'Workspace setup',
|
||||
percentage: 8,
|
||||
indeterminate: true,
|
||||
statusRows: 4,
|
||||
statusText: 'Allocating a clean workspace...',
|
||||
terminalLines: ['Allocating a clean workspace'],
|
||||
},
|
||||
validationFunc: async (stepper, _htmlElement, signal) => {
|
||||
const progressFrames = [
|
||||
{ line: 'Allocating a clean workspace', percentage: 8, delay: 500 },
|
||||
{ line: 'Syncing account preferences', percentage: 24, delay: 650 },
|
||||
{ line: 'Installing selected integrations', percentage: 47, delay: 700 },
|
||||
{ line: 'Generating starter project files', percentage: 71, delay: 650 },
|
||||
{ line: 'Booting the live preview environment', percentage: 92, delay: 700 },
|
||||
];
|
||||
|
||||
stepper.resetProgressStep();
|
||||
|
||||
for (const [index, progressFrame] of progressFrames.entries()) {
|
||||
if (signal?.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index === 0) {
|
||||
stepper.updateProgressStep({
|
||||
percentage: progressFrame.percentage,
|
||||
indeterminate: true,
|
||||
statusText: `${progressFrame.line}...`,
|
||||
terminalLines: [progressFrame.line],
|
||||
});
|
||||
} else {
|
||||
stepper.appendProgressStepLine(progressFrame.line);
|
||||
stepper.updateProgressStep({
|
||||
percentage: progressFrame.percentage,
|
||||
indeterminate: true,
|
||||
statusText: `${progressFrame.line}...`,
|
||||
});
|
||||
}
|
||||
|
||||
const completed = await waitForProgressTick(progressFrame.delay, signal);
|
||||
if (!completed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
stepper.appendProgressStepLine('Workspace ready');
|
||||
stepper.updateProgressStep({
|
||||
percentage: 100,
|
||||
indeterminate: false,
|
||||
statusText: 'Workspace ready.',
|
||||
});
|
||||
|
||||
await waitForProgressTick(350, signal);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Team Size',
|
||||
@@ -55,9 +154,7 @@ const demoSteps: IStep[] = [
|
||||
></dees-input-dropdown>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Continue', action: async (stepper) => stepper!.goNext() },
|
||||
],
|
||||
menuOptions: createContinueMenuOptions(),
|
||||
},
|
||||
{
|
||||
title: 'Goals',
|
||||
@@ -75,52 +172,31 @@ const demoSteps: IStep[] = [
|
||||
></dees-input-multitoggle>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Continue', action: async (stepper) => stepper!.goNext() },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Brand Preferences',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text key="brandColor" label="Primary brand color"></dees-input-text>
|
||||
<dees-input-text key="tone" label="Preferred tone (e.g. friendly, formal)"></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Continue', action: async (stepper) => stepper!.goNext() },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Integrations',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-list
|
||||
key="integrations"
|
||||
label="Integrations in use"
|
||||
placeholder="Add integration"
|
||||
></dees-input-list>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Continue', action: async (stepper) => stepper!.goNext() },
|
||||
],
|
||||
menuOptions: createContinueMenuOptions(),
|
||||
},
|
||||
{
|
||||
title: 'Review & Launch',
|
||||
content: html`
|
||||
<dees-panel>
|
||||
<p>Almost there! Review your selections and launch whenever you're ready.</p>
|
||||
<p>
|
||||
Your workspace is ready. Review the collected details and launch when
|
||||
you are ready to start.
|
||||
</p>
|
||||
</dees-panel>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Launch', action: async (stepper) => stepper!.goNext() },
|
||||
{
|
||||
name: 'Launch',
|
||||
action: async (stepper?: DeesStepper) => {
|
||||
if (stepper?.overlay) {
|
||||
await stepper.destroy();
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const cloneSteps = (): IStep[] => demoSteps.map((step) => ({ ...step }));
|
||||
|
||||
export const stepperDemo = () => html`
|
||||
<div style="position: absolute; inset: 0;">
|
||||
<div
|
||||
@@ -128,10 +204,10 @@ export const stepperDemo = () => html`
|
||||
>
|
||||
<dees-button
|
||||
@click=${async () => {
|
||||
await DeesStepper.createAndShow({ steps: cloneSteps() });
|
||||
await DeesStepper.createAndShow({ steps: createDemoSteps() });
|
||||
}}
|
||||
>Open stepper as overlay</dees-button>
|
||||
</div>
|
||||
<dees-stepper .steps=${cloneSteps()}></dees-stepper>
|
||||
<dees-stepper .steps=${createDemoSteps()}></dees-stepper>
|
||||
</div>
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user