+ ${this.steps.map((stepArg, i) => {
+ const isSelected = stepArg === this.selectedStep;
+ const isHidden = this.getIndexOfStep(stepArg) > this.getIndexOfStep(this.selectedStep);
+ const isFirst = i === 0;
+ const stepNumber = i + 1;
+ return html`
+
+
+
+
${stepArg.title}
+
${stepArg.content}
+
+ ${stepArg.footerContent
+ ? html``
+ : ''}
+
+ `;
+ })}
+
+ `;
+}
+```
+
+**Key detail:** on the first step, render a `.goBack-spacer` (empty div) in the header instead of nothing — so the `stepCounter` stays right-aligned via `justify-content: space-between`. Without a spacer, flex would left-align the counter on step 1.
+
+### 4. CSS changes
+
+**Remove from `.step`:**
+- `border-radius: 12px;`
+- `background: ${cssManager.bdTheme(...)};`
+- `border: 1px solid ${cssManager.bdTheme(...)};`
+- `color: ${cssManager.bdTheme(...)};`
+- `box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);`
+- `overflow: hidden;`
+
+**Why:** `dees-tile` owns all of these now. The `.step` selector still exists (since `dees-tile` has `class="step ..."` on it), but it only controls the outer animation wrapper: `max-width`, `min-height`, `margin`, `filter`, `transform`, `transition`, `user-select`, `pointer-events`.
+
+**Keep on `.step`:**
+- `position: relative;`
+- `pointer-events: none;` + `.step.selected { pointer-events: all; }`
+- `max-width: 500px;` / `min-height: 300px;`
+- `margin: auto; margin-bottom: 20px;`
+- `filter: opacity(0.55) saturate(0.85);` + `.selected { filter: opacity(1) saturate(1); }`
+- `.hiddenStep { filter: opacity(0); }`
+- All the cubic-bezier transitions (transform/filter/box-shadow — but box-shadow is now a no-op since dees-tile provides the shadow; leave the transition spec in so we don't have to re-check browser parsing; or just drop `box-shadow` from the transition list — I'll drop it for cleanliness).
+- `.step.entrance` + `.step.entrance.hiddenStep { transform: translateY(16px); }`
+- `.step:last-child { margin-bottom: 100vh; }`
+
+**Add for dees-tile shadow enhancement:** use `::part(outer)` to apply the modal-style elevated shadow only when in overlay mode (optional polish — inline mode stays flat):
+```css
+.stepperContainer.overlay dees-tile.step::part(outer) {
+ box-shadow:
+ 0 0 0 1px ${cssManager.bdTheme('hsl(0 0% 0% / 0.03)', 'hsl(0 0% 100% / 0.03)')},
+ 0 8px 40px ${cssManager.bdTheme('hsl(0 0% 0% / 0.12)', 'hsl(0 0% 0% / 0.5)')},
+ 0 2px 8px ${cssManager.bdTheme('hsl(0 0% 0% / 0.06)', 'hsl(0 0% 0% / 0.25)')};
+}
+```
+This exactly mirrors the dees-modal::part(outer) shadow stack (dees-modal.ts:157–161) so the overlay stepper reads as "same visual language as modal."
+
+**Restyle `.step-header` (NEW — the ``):**
+```css
+.step-header {
+ height: 48px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px 12px;
+ gap: 12px;
+}
+```
+
+**Restyle `.step .stepCounter` → `.step-header .stepCounter` (move from absolute to flex child):**
+- Drop `position: absolute; top: 12px; right: 12px;`
+- Keep everything else (padding, font-size, border-radius, background, border).
+
+**Restyle `.step .goBack` → `.step-header .goBack` (move from absolute to flex child):**
+- Drop `position: absolute; top: 12px; left: 12px;`
+- Keep everything else (padding, font-size, border-radius, background, border, hover/active states).
+
+**Add `.goBack-spacer`:**
+```css
+.goBack-spacer { width: 1px; } /* placeholder so flex space-between works on step 1 */
+```
+
+**Restyle `.step .title`:**
+- Drop `padding-top: 64px;` — no longer overlaps anything since header is in its own slot.
+- Keep `text-align: center; font-family: 'Geist Sans', sans-serif; font-size: 24px; font-weight: 600; letter-spacing: -0.01em; color: inherit;`
+- Add `padding-top: 32px;` (or similar) so there's consistent breathing room above the title inside the tile content.
+
+**Add `.step-footer` (new container for `stepArg.footerContent`):**
+```css
+.step-footer {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 8px;
+ padding: 12px 16px;
+}
+```
+
+**Add overlay-mode positioning:**
+```css
+.stepperContainer {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+.stepperContainer.overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+}
+.stepperContainer.predestroy {
+ opacity: 0;
+ transition: opacity 0.2s ease-in;
+}
+```
+
+**Adjust `:host` for dual-mode:**
+```css
+:host {
+ position: absolute; /* inline default */
+ width: 100%;
+ height: 100%;
+ font-family: ${cssGeistFontFamily};
+ color: var(--dees-color-text-primary);
+}
+:host([overlay]) {
+ position: fixed; /* overlay mode */
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+}
+```
+
+The `overlay` @state needs to reflect to an attribute for the `:host([overlay])` selector to work. Since `@state` doesn't reflect attributes, use `@property({ type: Boolean, reflect: true })` instead — change the decorator accordingly.
+
+### 5. Imports to add in `dees-stepper.ts`
+
+```ts
+import { DeesWindowLayer } from '../../00group-overlay/dees-windowlayer/dees-windowlayer.js';
+import { zIndexRegistry } from '../../00zindex.js';
+import { cssGeistFontFamily } from '../../00fonts.js';
+import '../../00group-layout/dees-tile/dees-tile.js';
+```
+
+`dees-tile` side-effect import registers the custom element. `cssGeistFontFamily` is only needed if I add it to `:host` (which I want, to match modal).
+
+### 6. SweetScroll selector stability
+
+`setScrollStatus()` selectors target `.step` and `.selected` (lines 228–229). These continue to match since I'm keeping those class names on the `
` elements. **No selector changes needed.**
+
+One subtlety: `offsetTop` / `offsetHeight` on `` should still work — the tile's `:host` is `display: flex; flex-direction: column;` which participates in layout. I'll verify visually in the demo.
+
+### 7. Demo update
+
+**File:** `ts_web/elements/00group-layout/dees-stepper/dees-stepper.demo.ts`
+
+Current demo renders one inline stepper directly. I'll keep that and add an **overlay launcher button** above it:
+
+```ts
+export const stepperDemo = () => html`
+
+ {
+ const stepper = await DeesStepper.createAndShow({
+ steps: [/* same steps as inline demo */],
+ });
+ }}>Open stepper as overlay
+
+
+`;
+```
+
+Extract the step definitions into a `const demoSteps = [...]` above the template so both the inline and overlay paths reuse them (DRY). Import `DeesStepper` at the top of the demo file.
+
+## Files to modify
+
+1. **`ts_web/elements/00group-layout/dees-stepper/dees-stepper.ts`** — main refactor (IStep, imports, render, styles, createAndShow, destroy, overlay state).
+2. **`ts_web/elements/00group-layout/dees-stepper/dees-stepper.demo.ts`** — add overlay launcher button, extract shared `demoSteps` const, import `DeesStepper`.
+
+**Files explicitly NOT modified:**
+- `dees-tile.ts` — used as-is via its slot API.
+- `dees-windowlayer.ts` — used as-is via `createAndShow` / `destroy` / `click` event.
+- `dees-modal.ts` — reference only.
+- `00zindex.ts` — reference only.
+
+## Verification
+
+1. **Build**: `pnpm run build` — must pass with no TS errors. Pure refactor, no new dependencies, no lib-check regressions expected.
+
+2. **Inline demo (backward compat)**:
+ - Start the demo server (port 8080 is already running) and navigate to the dees-stepper demo page.
+ - Confirm the stepper renders inline exactly like before: first step centered, subsequent steps dimmed below, scroll-through animation on goNext / goBack.
+ - Fill out the first form, submit → stepper scrolls to step 2. Click goBack → scrolls back.
+ - Confirm the `dees-tile` frame is visible on each step (rounded, bordered, themed) and that the title + form are inside the tile's content area.
+ - Confirm goBack button + step counter sit in the tile's header row, space-between, left/right respectively.
+
+3. **Overlay demo (new path)**:
+ - Click the "Open stepper as overlay" button.
+ - Confirm a `dees-windowlayer` with blur appears behind the stepper.
+ - Confirm the stepper fills the viewport (fixed, 100vw×100vh).
+ - Confirm z-index stacking: stepper above window layer above page content.
+ - Click the window layer (outside the tile) → stepper animates out, then destroys along with the window layer.
+ - Re-open and step through forward & back — behavior identical to inline mode.
+
+4. **Playwright visual check** (per CLAUDE.md: screenshots MUST go in `.playwright-mcp/`):
+ - `.playwright-mcp/dees-stepper-inline.png` — inline mode, step 1 with form.
+ - `.playwright-mcp/dees-stepper-overlay.png` — overlay mode, same step.
+ - `.playwright-mcp/dees-stepper-overlay-step3.png` — overlay mode mid-flow, to verify scroll-stack visual.
+ - Both light and dark themes if the demo has a theme toggle.
+
+5. **Grep sanity**:
+ - Confirm `dees-stepper` has no new unexpected match locations: `grep dees-stepper ts_web/` should still only match stepper's own files.
+ - Confirm no `.step` class collisions elsewhere (unlikely — `.step` is a plain class name; all usages should be shadow-scoped to `dees-stepper`).
+
+## Open assumptions & deferred scope
+
+These are explicit defaults in this plan. If the user wants different behavior for any of them, they should flag it on review — each is a simple follow-up but not in scope right now (CLAUDE.md: stay focused, no "while we're at it"):
+
+- **No close button on overlay stepper.** Clicking the window layer backdrop is the only way to dismiss. Matches how dees-modal with `showCloseButton: false` behaves. Can add a close button in a follow-up.
+- **No `onComplete` callback in `createAndShow`.** The last step doesn't auto-destroy the overlay — the app controls it via the step's `validationFunc`. Can add a callback option in a follow-up.
+- **No width/size options in `createAndShow`.** The step tile continues to use the stepper's existing `max-width: 500px`. Can parameterize in a follow-up.
+- **Box-shadow in the `.step` transition list** is dropped from the transition for cleanliness — the box-shadow is now on `dees-tile::part(outer)` and doesn't change between selected/hiddenStep, so transitioning it was already a no-op.
+- **`pnpm start` / dev server path**: I'll reuse the existing server on port 8080 that was already listening when this session began; if that server doesn't serve the stepper demo, I'll start wcctools manually.
+
+## Risk
+
+- **Low-medium.** The change is localized to one component and its demo. No API removal, only an additive `createAndShow` + an optional `footerContent` field. External consumers of the inline API continue to work if they only set `steps` + `selectedStep`.
+- **Biggest risk:** SweetScroll's `offsetTop` / `offsetHeight` measurements on `` may compute differently than on the former `` because `dees-tile` has an internal `display: flex; flex-direction: column;` host and a `.tile-outer { flex: 1; min-height: 0; }` inner frame. If the scroll math drifts, the mitigation is to keep the `.step` wrapper as an outer `
` that **contains** a `
`, rather than putting the class directly on ``. That preserves the exact box model SweetScroll was measuring. I'll try the direct-class approach first (simpler) and fall back to the wrapper approach if the scroll target looks off in the demo.
+- **Second risk:** The `:host([overlay])` attribute selector requires `overlay` to be a reflected `@property`, not `@state`. I've already accounted for this in the plan (decorator change).
diff --git a/ts_web/elements/00group-layout/dees-stepper/dees-stepper.demo.ts b/ts_web/elements/00group-layout/dees-stepper/dees-stepper.demo.ts
index c8eae72..8e85fdf 100644
--- a/ts_web/elements/00group-layout/dees-stepper/dees-stepper.demo.ts
+++ b/ts_web/elements/00group-layout/dees-stepper/dees-stepper.demo.ts
@@ -1,134 +1,148 @@
import { html } from '@design.estate/dees-element';
+import { DeesStepper, type IStep } from './dees-stepper.js';
+
+const demoSteps: IStep[] = [
+ {
+ title: 'Account Setup',
+ content: html`
+
+
+
+ Continue
+
+ `,
+ validationFunc: async (stepperArg, elementArg) => {
+ const deesForm = elementArg.querySelector('dees-form');
+ deesForm!.addEventListener('formData', () => stepperArg.goNext(), { once: true });
+ },
+ },
+ {
+ title: 'Profile Details',
+ content: html`
+
+
+
+ Continue
+
+ `,
+ validationFunc: async (stepperArg, elementArg) => {
+ const deesForm = elementArg.querySelector('dees-form');
+ deesForm!.addEventListener('formData', () => stepperArg.goNext(), { once: true });
+ },
+ },
+ {
+ title: 'Contact Information',
+ content: html`
+
+
+
+ Continue
+
+ `,
+ validationFunc: async (stepperArg, elementArg) => {
+ const deesForm = elementArg.querySelector('dees-form');
+ deesForm!.addEventListener('formData', () => stepperArg.goNext(), { once: true });
+ },
+ },
+ {
+ title: 'Team Size',
+ content: html`
+
+
+ Continue
+
+ `,
+ validationFunc: async (stepperArg, elementArg) => {
+ const deesForm = elementArg.querySelector('dees-form');
+ deesForm!.addEventListener('formData', () => stepperArg.goNext(), { once: true });
+ },
+ },
+ {
+ title: 'Goals',
+ content: html`
+
+
+ Continue
+
+ `,
+ validationFunc: async (stepperArg, elementArg) => {
+ const deesForm = elementArg.querySelector('dees-form');
+ deesForm!.addEventListener('formData', () => stepperArg.goNext(), { once: true });
+ },
+ },
+ {
+ title: 'Brand Preferences',
+ content: html`
+
+
+
+ Continue
+
+ `,
+ validationFunc: async (stepperArg, elementArg) => {
+ const deesForm = elementArg.querySelector('dees-form');
+ deesForm!.addEventListener('formData', () => stepperArg.goNext(), { once: true });
+ },
+ },
+ {
+ title: 'Integrations',
+ content: html`
+
+
+ Continue
+
+ `,
+ validationFunc: async (stepperArg, elementArg) => {
+ const deesForm = elementArg.querySelector('dees-form');
+ deesForm!.addEventListener('formData', () => stepperArg.goNext(), { once: true });
+ },
+ },
+ {
+ title: 'Review & Launch',
+ content: html`
+
+ Almost there! Review your selections and launch whenever you're ready.
+
+ `,
+ },
+];
+
+const cloneSteps = (): IStep[] => demoSteps.map((step) => ({ ...step }));
export const stepperDemo = () => html`
-
-
-
- Continue
-
- `,
- validationFunc: async (stepperArg, elementArg) => {
- const deesForm = elementArg.querySelector('dees-form');
- deesForm.addEventListener('formData', () => stepperArg.goNext(), { once: true });
- },
- },
- {
- title: 'Profile Details',
- content: html`
-
-
-
- Continue
-
- `,
- validationFunc: async (stepperArg, elementArg) => {
- const deesForm = elementArg.querySelector('dees-form');
- deesForm.addEventListener('formData', () => stepperArg.goNext(), { once: true });
- },
- },
- {
- title: 'Contact Information',
- content: html`
-
-
-
- Continue
-
- `,
- validationFunc: async (stepperArg, elementArg) => {
- const deesForm = elementArg.querySelector('dees-form');
- deesForm.addEventListener('formData', () => stepperArg.goNext(), { once: true });
- },
- },
- {
- title: 'Team Size',
- content: html`
-
-
- Continue
-
- `,
- validationFunc: async (stepperArg, elementArg) => {
- const deesForm = elementArg.querySelector('dees-form');
- deesForm.addEventListener('formData', () => stepperArg.goNext(), { once: true });
- },
- },
- {
- title: 'Goals',
- content: html`
-
-
- Continue
-
- `,
- validationFunc: async (stepperArg, elementArg) => {
- const deesForm = elementArg.querySelector('dees-form');
- deesForm.addEventListener('formData', () => stepperArg.goNext(), { once: true });
- },
- },
- {
- title: 'Brand Preferences',
- content: html`
-
-
-
- Continue
-
- `,
- validationFunc: async (stepperArg, elementArg) => {
- const deesForm = elementArg.querySelector('dees-form');
- deesForm.addEventListener('formData', () => stepperArg.goNext(), { once: true });
- },
- },
- {
- title: 'Integrations',
- content: html`
-
-
- Continue
-
- `,
- validationFunc: async (stepperArg, elementArg) => {
- const deesForm = elementArg.querySelector('dees-form');
- deesForm.addEventListener('formData', () => stepperArg.goNext(), { once: true });
- },
- },
- {
- title: 'Review & Launch',
- content: html`
-
- Almost there! Review your selections and launch whenever you're ready.
-
- `,
- },
- ] as const}
- >
+
+
+ {
+ await DeesStepper.createAndShow({ steps: cloneSteps() });
+ }}
+ >Open stepper as overlay
+
+
+
`;
diff --git a/ts_web/elements/00group-layout/dees-stepper/dees-stepper.ts b/ts_web/elements/00group-layout/dees-stepper/dees-stepper.ts
index 23275c4..50e2627 100644
--- a/ts_web/elements/00group-layout/dees-stepper/dees-stepper.ts
+++ b/ts_web/elements/00group-layout/dees-stepper/dees-stepper.ts
@@ -1,5 +1,4 @@
import * as plugins from '../../00plugins.js';
-import * as colors from '../../00colors.js';
import {
DeesElement,
@@ -16,10 +15,15 @@ import {
import * as domtools from '@design.estate/dees-domtools';
import { stepperDemo } from './dees-stepper.demo.js';
import { themeDefaultStyles } from '../../00theme.js';
+import { cssGeistFontFamily } from '../../00fonts.js';
+import { zIndexRegistry } from '../../00zindex.js';
+import { DeesWindowLayer } from '../../00group-overlay/dees-windowlayer/dees-windowlayer.js';
+import '../dees-tile/dees-tile.js';
export interface IStep {
title: string;
content: TemplateResult;
+ footerContent?: TemplateResult;
validationFunc?: (stepper: DeesStepper, htmlElement: HTMLElement, signal?: AbortSignal) => Promise;
onReturnToStepFunc?: (stepper: DeesStepper, htmlElement: HTMLElement) => Promise;
validationFuncCalled?: boolean;
@@ -34,9 +38,32 @@ declare global {
@customElement('dees-stepper')
export class DeesStepper extends DeesElement {
+ // STATIC
public static demo = stepperDemo;
public static demoGroups = ['Layout', 'Form'];
+ public static async createAndShow(optionsArg: {
+ steps: IStep[];
+ }): Promise {
+ const body = document.body;
+ const stepper = new DeesStepper();
+ stepper.steps = optionsArg.steps;
+ stepper.overlay = true;
+ stepper.windowLayer = await DeesWindowLayer.createAndShow({ blur: true });
+ stepper.windowLayer.addEventListener('click', async () => {
+ await stepper.destroy();
+ });
+ body.append(stepper.windowLayer);
+ body.append(stepper);
+
+ // Get z-index for stepper (should be above window layer)
+ stepper.stepperZIndex = zIndexRegistry.getNextZIndex();
+ zIndexRegistry.register(stepper, stepper.stepperZIndex);
+
+ return stepper;
+ }
+
+ // INSTANCE
@property({
type: Array,
})
@@ -47,6 +74,17 @@ export class DeesStepper extends DeesElement {
})
accessor selectedStep!: IStep;
+ @property({
+ type: Boolean,
+ reflect: true,
+ })
+ accessor overlay: boolean = false;
+
+ @property({ type: Number, attribute: false })
+ accessor stepperZIndex: number = 1000;
+
+ private windowLayer?: DeesWindowLayer;
+
constructor() {
super();
}
@@ -60,7 +98,18 @@ export class DeesStepper extends DeesElement {
position: absolute;
width: 100%;
height: 100%;
+ font-family: ${cssGeistFontFamily};
+ color: var(--dees-color-text-primary);
}
+
+ :host([overlay]) {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ }
+
.stepperContainer {
position: absolute;
width: 100%;
@@ -68,101 +117,120 @@ export class DeesStepper extends DeesElement {
overflow: hidden;
}
- .step {
+ .stepperContainer.overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ }
+
+ .stepperContainer.predestroy {
+ opacity: 0;
+ transition: opacity 0.2s ease-in;
+ }
+
+ dees-tile.step {
position: relative;
pointer-events: none;
- overflow: hidden;
- transition: transform 0.7s cubic-bezier(0.87, 0, 0.13, 1), box-shadow 0.7s cubic-bezier(0.87, 0, 0.13, 1), filter 0.7s cubic-bezier(0.87, 0, 0.13, 1), border 0.7s cubic-bezier(0.87, 0, 0.13, 1);
max-width: 500px;
min-height: 300px;
- border-radius: 12px;
- background: ${cssManager.bdTheme('#ffffff', '#0f0f11')};
- border: 1px solid ${cssManager.bdTheme('#e2e8f0', '#272729')};
- color: ${cssManager.bdTheme('#0f172a', '#f5f5f5')};
margin: auto;
margin-bottom: 20px;
filter: opacity(0.55) saturate(0.85);
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
+ transition: transform 0.7s cubic-bezier(0.87, 0, 0.13, 1), filter 0.7s cubic-bezier(0.87, 0, 0.13, 1);
user-select: none;
}
- .step.selected {
+ dees-tile.step.selected {
pointer-events: all;
filter: opacity(1) saturate(1);
user-select: auto;
}
- .step.hiddenStep {
+ dees-tile.step.hiddenStep {
filter: opacity(0);
}
- .step.entrance {
- transition: transform 0.35s ease, box-shadow 0.35s ease, filter 0.35s ease, border 0.35s ease;
+ dees-tile.step.entrance {
+ transition: transform 0.35s ease, filter 0.35s ease;
}
- .step.entrance.hiddenStep {
+ dees-tile.step.entrance.hiddenStep {
transform: translateY(16px);
}
- .step:last-child {
+ dees-tile.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)')};
+ .stepperContainer.overlay dees-tile.step::part(outer) {
+ box-shadow:
+ 0 0 0 1px ${cssManager.bdTheme('hsl(0 0% 0% / 0.03)', 'hsl(0 0% 100% / 0.03)')},
+ 0 8px 40px ${cssManager.bdTheme('hsl(0 0% 0% / 0.12)', 'hsl(0 0% 0% / 0.5)')},
+ 0 2px 8px ${cssManager.bdTheme('hsl(0 0% 0% / 0.06)', 'hsl(0 0% 0% / 0.25)')};
}
- .step .goBack {
- position: absolute;
- top: 12px;
- left: 12px;
+ .step-header {
+ height: 36px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 8px 0 12px;
+ gap: 12px;
+ }
+
+ .goBack-spacer {
+ width: 1px;
+ }
+
+ .step-header .goBack {
display: inline-flex;
align-items: center;
gap: 6px;
- padding: 6px 12px;
+ height: 24px;
+ padding: 0 8px;
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')};
+ line-height: 1;
+ border: none;
+ background: transparent;
+ color: var(--dees-color-text-muted);
+ border-radius: 4px;
cursor: pointer;
- transition: border 0.2s ease, color 0.2s ease, background 0.2s ease, transform 0.2s ease;
+ transition: background 0.15s ease, color 0.15s 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)')};
+ .step-header .goBack:hover {
+ background: var(--dees-color-hover);
+ color: var(--dees-color-text-secondary);
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-header .goBack:active {
+ background: ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(0 0% 15%)')};
}
- .step .goBack span {
+ .step-header .goBack span {
transition: transform 0.2s ease;
display: inline-block;
}
- .step .goBack:hover span {
+ .step-header .goBack:hover span {
transform: translateX(-2px);
}
- .step .title {
+ .step-header .stepCounter {
+ color: var(--dees-color-text-muted);
+ font-size: 12px;
+ font-weight: 500;
+ letter-spacing: -0.01em;
+ padding: 0 8px;
+ }
+
+ .step-body .title {
text-align: center;
- padding-top: 64px;
+ padding-top: 32px;
font-family: 'Geist Sans', sans-serif;
font-size: 24px;
font-weight: 600;
@@ -170,35 +238,53 @@ export class DeesStepper extends DeesElement {
color: inherit;
}
- .step .content {
+ .step-body .content {
padding: 32px;
}
+
+ .step-footer {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 8px;
+ padding: 12px 16px;
+ }
`,
];
public render() {
return html`
-
- ${this.steps.map(
- (stepArg) =>
- html`
- ${this.getIndexOfStep(stepArg) > 0
- ? html`
<- go to previous step
`
- : ``}
+
+ ${this.steps.map((stepArg, stepIndex) => {
+ const isSelected = stepArg === this.selectedStep;
+ const isHidden =
+ this.getIndexOfStep(stepArg) > this.getIndexOfStep(this.selectedStep);
+ const isFirst = stepIndex === 0;
+ return html`
+
+
${stepArg.title}
${stepArg.content}
-
`
- )}
+
+ ${stepArg.footerContent
+ ? html``
+ : ''}
+ `;
+ })}
`;
}
@@ -296,4 +382,20 @@ export class DeesStepper extends DeesElement {
nextStep.validationFuncCalled = false;
this.selectedStep = nextStep;
}
+
+ public async destroy() {
+ const domtools = await this.domtoolsPromise;
+ const container = this.shadowRoot!.querySelector('.stepperContainer');
+ container?.classList.add('predestroy');
+ await domtools.convenience.smartdelay.delayFor(200);
+ if (this.parentElement) {
+ this.parentElement.removeChild(this);
+ }
+ if (this.windowLayer) {
+ await this.windowLayer.destroy();
+ }
+
+ // Unregister from z-index registry
+ zIndexRegistry.unregister(this);
+ }
}