281 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			281 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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<any>;
 | |
|   onReturnToStepFunc?: (stepper: DeesStepper, htmlElement: HTMLElement) => Promise<any>;
 | |
|   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`
 | |
|       <div class="stepperContainer">
 | |
|         ${this.steps.map(
 | |
|           (stepArg) =>
 | |
|             html`<div
 | |
|               class="step ${stepArg === this.selectedStep
 | |
|                 ? 'selected'
 | |
|                 : null} ${this.getIndexOfStep(stepArg) > this.getIndexOfStep(this.selectedStep)
 | |
|                 ? 'hiddenStep'
 | |
|                 : ''}"
 | |
|             >
 | |
|               ${this.getIndexOfStep(stepArg) > 0
 | |
|                 ? html`<div class="goBack" @click=${this.goBack}><span style="font-family: Inter"><-</span> go to previous step</div>`
 | |
|                 : ``}
 | |
|               <div class="stepCounter">
 | |
|                 Step ${this.steps.findIndex((elementArg) => elementArg === stepArg) + 1} of
 | |
|                 ${this.steps.length}
 | |
|               </div>
 | |
|               <div class="title">${stepArg.title}</div>
 | |
|               <div class="content">${stepArg.content}</div>
 | |
|             </div> `
 | |
|         )}
 | |
|       </div>
 | |
|     `;
 | |
|   }
 | |
| 
 | |
|   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;
 | |
|   }
 | |
| }
 |