import * as plugins from '../00plugins.js'; import * as colors from '../00colors.js'; import { DeesElement, customElement, html, css, unsafeCSS, type CSSResult, cssManager, property, type TemplateResult, } from '@design.estate/dees-element'; import * as domtools from '@design.estate/dees-domtools'; import { stepperDemo } from './dees-stepper.demo.js'; export interface IStep { title: string; content: TemplateResult; validationFunc?: (stepper: DeesStepper, htmlElement: HTMLElement) => Promise; onReturnToStepFunc?: (stepper: DeesStepper, htmlElement: HTMLElement) => Promise; validationFuncCalled?: boolean; } declare global { interface HTMLElementTagNameMap { 'dees-stepper': DeesStepper; } } @customElement('dees-stepper') export class DeesStepper extends DeesElement { public static demo = stepperDemo; @property({ type: Array, }) public steps: IStep[] = []; @property({ type: Object, }) public selectedStep: IStep; constructor() { super(); } public static styles = [ cssManager.defaultStyles, css` :host { position: absolute; width: 100%; height: 100%; } .stepperContainer { position: absolute; width: 100%; height: 100%; background: ${cssManager.bdTheme('#eeeeeb', '#000')}; overflow: hidden; } .step { position: relative; pointer-events: none; overflow: hidden; transition: transform 0.35s ease, box-shadow 0.35s ease, filter 0.35s ease, border 0.35s ease; max-width: 500px; min-height: 300px; border-radius: 18px; background: ${cssManager.bdTheme('#ffffff', '#18181b')}; border: 1px solid ${cssManager.bdTheme('rgba(226, 232, 240, 0.9)', 'rgba(63, 63, 70, 0.85)')}; color: ${cssManager.bdTheme('#0f172a', '#f5f5f5')}; margin: auto; margin-bottom: 20px; filter: opacity(0.55) saturate(0.85); box-shadow: ${cssManager.bdTheme('0 20px 40px -25px rgba(15, 23, 42, 0.45)', '0 20px 36px -22px rgba(15, 23, 42, 0.65)')}; user-select: none; background-clip: padding-box; } .step.selected { pointer-events: all; filter: opacity(1) saturate(1); transform: translateY(-6px); border: 1px solid ${cssManager.bdTheme(colors.dark.blue, colors.dark.blue)}; box-shadow: ${cssManager.bdTheme('0 28px 60px -30px rgba(15, 23, 42, 0.42)', '0 26px 55px -28px rgba(37, 99, 235, 0.6)')}; user-select: auto; } .step.hiddenStep { filter: opacity(0); transform: translateY(16px); } .step:last-child { margin-bottom: 100vh; } .step .stepCounter { color: ${cssManager.bdTheme('#64748b', '#a1a1aa')}; position: absolute; top: 12px; right: 12px; padding: 6px 14px; font-size: 12px; border-radius: 999px; background: ${cssManager.bdTheme('rgba(226, 232, 240, 0.5)', 'rgba(63, 63, 70, 0.45)')}; border: 1px solid ${cssManager.bdTheme('rgba(226, 232, 240, 0.7)', 'rgba(63, 63, 70, 0.6)')}; } .step .goBack { position: absolute; top: 12px; left: 12px; display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; font-size: 12px; font-weight: 500; border-radius: 999px; border: 1px solid ${cssManager.bdTheme('rgba(226, 232, 240, 0.9)', 'rgba(63, 63, 70, 0.85)')}; background: ${cssManager.bdTheme('rgba(255, 255, 255, 0.9)', 'rgba(39, 39, 42, 0.85)')}; color: ${cssManager.bdTheme('#475569', '#d4d4d8')}; cursor: pointer; transition: border 0.2s ease, color 0.2s ease, background 0.2s ease, transform 0.2s ease; } .step .goBack:hover { color: ${cssManager.bdTheme('#0f172a', '#fafafa')}; border-color: ${cssManager.bdTheme(colors.dark.blue, colors.dark.blue)}; background: ${cssManager.bdTheme('rgba(226, 232, 240, 0.95)', 'rgba(63, 63, 70, 0.7)')}; transform: translateX(-2px); } .step .goBack:active { color: ${cssManager.bdTheme('#0f172a', '#fafafa')}; border-color: ${cssManager.bdTheme(colors.dark.blueActive, colors.dark.blueActive)}; background: ${cssManager.bdTheme('rgba(226, 232, 240, 0.85)', 'rgba(63, 63, 70, 0.6)')}; } .step .goBack span { transition: transform 0.2s ease; display: inline-block; } .step .goBack:hover span { transform: translateX(-2px); } .step .title { text-align: center; padding-top: 64px; font-family: 'Geist Sans', sans-serif; font-size: 24px; font-weight: 600; letter-spacing: -0.01em; color: inherit; } .step .content { padding: 24px 28px 32px; } `, ]; public render() { return html`
${this.steps.map( (stepArg) => html`
${this.getIndexOfStep(stepArg) > 0 ? html`
<- go to previous step
` : ``}
Step ${this.steps.findIndex((elementArg) => elementArg === stepArg) + 1} of ${this.steps.length}
${stepArg.title}
${stepArg.content}
` )}
`; } public getIndexOfStep = (stepArg: IStep): number => { return this.steps.findIndex((stepArg2) => stepArg === stepArg2); }; public async firstUpdated() { await this.domtoolsPromise; await this.domtools.convenience.smartdelay.delayFor(0); this.selectedStep = this.steps[0]; this.setScrollStatus(); } public async updated() { this.setScrollStatus(); } public scroller: typeof domtools.plugins.SweetScroll.prototype; public async setScrollStatus() { const stepperContainer: HTMLElement = this.shadowRoot.querySelector('.stepperContainer'); const firstStepElement: HTMLElement = this.shadowRoot.querySelector('.step'); const selectedStepElement: HTMLElement = this.shadowRoot.querySelector('.selected'); if (!selectedStepElement) { return; } if (!stepperContainer.style.paddingTop) { stepperContainer.style.paddingTop = `${ stepperContainer.offsetHeight / 2 - selectedStepElement.offsetHeight / 2 }px`; } console.log('Setting scroll status'); console.log(selectedStepElement); const scrollPosition = selectedStepElement.offsetTop - stepperContainer.offsetHeight / 2 + selectedStepElement.offsetHeight / 2; console.log(scrollPosition); const domtoolsInstance = await domtools.DomTools.setupDomTools(); if (!this.scroller) { this.scroller = new domtools.plugins.SweetScroll( { vertical: true, horizontal: false, easing: 'easeInOutExpo', duration: 700, }, stepperContainer ); } if (!this.selectedStep.validationFuncCalled && this.selectedStep.validationFunc) { this.selectedStep.validationFuncCalled = true; await this.selectedStep.validationFunc(this, selectedStepElement); } this.scroller.to(scrollPosition); } public async goBack() { const currentIndex = this.steps.findIndex((stepArg) => stepArg === this.selectedStep); if (currentIndex <= 0) { return; } const currentStep = this.steps[currentIndex]; currentStep.validationFuncCalled = false; const previousStep = this.steps[currentIndex - 1]; previousStep.validationFuncCalled = false; this.selectedStep = previousStep; await this.domtoolsPromise; await this.domtools.convenience.smartdelay.delayFor(100); this.selectedStep.onReturnToStepFunc?.(this, this.shadowRoot.querySelector('.selected')); } public goNext() { const currentIndex = this.steps.findIndex((stepArg) => stepArg === this.selectedStep); if (currentIndex < 0 || currentIndex >= this.steps.length - 1) { return; } const currentStep = this.steps[currentIndex]; currentStep.validationFuncCalled = false; const nextStep = this.steps[currentIndex + 1]; nextStep.validationFuncCalled = false; this.selectedStep = nextStep; } }