Files
dees-catalog-mobile/ts_web/elements/00group-layout/dees-mobile-viewstack/dees-mobile-viewstack.demo.ts
2025-12-22 10:53:15 +00:00

623 lines
23 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { html, css, cssManager } from '@design.estate/dees-element';
import '@design.estate/dees-wcctools/demotools';
import { injectCssVariables } from '../../00variables.js';
import type { DeesMobileViewstack } from './dees-mobile-viewstack.js';
// Shared styles for demos
const sharedStyles = html`
<style>
.demo-container {
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
border-radius: 12px;
overflow: hidden;
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
}
.view-header {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
border-bottom: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
}
.view-title {
font-size: 18px;
font-weight: 600;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
margin: 0;
}
.back-button {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
background: transparent;
border-radius: 6px;
color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
cursor: pointer;
font-size: 20px;
}
.back-button:hover {
background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
}
.list-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
cursor: pointer;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
transition: background 150ms ease;
}
.list-item:hover {
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
}
.list-item:active {
background: ${cssManager.bdTheme('#e4e4e7', '#27272a')};
}
.item-title {
font-weight: 500;
}
.item-subtitle {
font-size: 14px;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
margin-top: 4px;
}
.chevron {
color: ${cssManager.bdTheme('#a1a1aa', '#71717a')};
}
.item-detail {
padding: 24px;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
}
.item-detail h2 {
margin: 0 0 16px;
font-size: 24px;
}
.item-detail p {
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
line-height: 1.6;
}
.status-bar {
padding: 12px 16px;
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
font-size: 12px;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
border-top: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
}
.control-panel {
display: flex;
gap: 8px;
padding: 12px 16px;
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
border-bottom: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
flex-wrap: wrap;
}
.control-button {
padding: 8px 16px;
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#3f3f46')};
background: ${cssManager.bdTheme('#ffffff', '#27272a')};
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
border-radius: 6px;
cursor: pointer;
font-size: 13px;
font-weight: 500;
}
.control-button:hover {
background: ${cssManager.bdTheme('#f4f4f5', '#3f3f46')};
}
.control-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.control-button.primary {
background: #3b82f6;
border-color: #3b82f6;
color: white;
}
.control-button.primary:hover {
background: #2563eb;
}
</style>
`;
// Helper functions
const handleListClick = (viewstack: DeesMobileViewstack, listName: string) => {
const listView = viewstack.querySelector('[view-id="list"]');
if (listView) {
(listView as HTMLElement).dataset.listName = listName;
}
viewstack.pushView('list');
};
const handleItemClick = (viewstack: DeesMobileViewstack, itemName: string) => {
const itemView = viewstack.querySelector('[view-id="item"]');
if (itemView) {
(itemView as HTMLElement).dataset.itemName = itemName;
}
viewstack.pushView('item');
};
const handleBack = (viewstack: DeesMobileViewstack) => {
viewstack.popView();
};
/**
* Demo 1: Mobile Phone Layout
* Simulates a typical mobile app navigation pattern
*/
const mobileDemo = () => {
injectCssVariables();
return html`
${sharedStyles}
<style>
.mobile-frame {
width: 375px;
height: 667px;
border: 8px solid ${cssManager.bdTheme('#1f1f1f', '#404040')};
border-radius: 32px;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
</style>
<h3 style="margin: 0 0 16px; color: ${cssManager.bdTheme('#09090b', '#fafafa')};">Mobile Phone Layout (375x667)</h3>
<p style="margin: 0 0 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};">
Simulates iPhone SE dimensions. Tap items to navigate forward, use back button to return.
</p>
<div class="mobile-frame">
<div class="demo-container" style="height: 100%; border: none; border-radius: 0;">
<dees-mobile-viewstack
initial-view="lists"
style="height: calc(100% - 44px);"
@view-changed=${(e: CustomEvent) => {
const target = e.target as HTMLElement;
const status = target?.closest('.demo-container')?.querySelector('.status-bar');
if (status) {
status.textContent = `${e.detail.currentView} (depth: ${e.detail.stackDepth})`;
}
}}
>
<dees-mobile-view view-id="lists">
<div class="view-header">
<h1 class="view-title">My Lists</h1>
</div>
<div class="list-item" @click=${(e: Event) => {
const target = e.target as HTMLElement;
const viewstack = target?.closest('dees-mobile-viewstack') as DeesMobileViewstack;
if (viewstack) handleListClick(viewstack, 'Shopping');
}}>
<div>
<div class="item-title">Shopping List</div>
<div class="item-subtitle">12 items</div>
</div>
<span class="chevron"></span>
</div>
<div class="list-item" @click=${(e: Event) => {
const target = e.target as HTMLElement;
const viewstack = target?.closest('dees-mobile-viewstack') as DeesMobileViewstack;
if (viewstack) handleListClick(viewstack, 'Todo');
}}>
<div>
<div class="item-title">Todo List</div>
<div class="item-subtitle">5 items</div>
</div>
<span class="chevron"></span>
</div>
</dees-mobile-view>
<dees-mobile-view view-id="list">
<div class="view-header">
<button class="back-button" @click=${(e: Event) => {
const target = e.target as HTMLElement;
const viewstack = target?.closest('dees-mobile-viewstack') as DeesMobileViewstack;
if (viewstack) handleBack(viewstack);
}}></button>
<h1 class="view-title">Items</h1>
</div>
<div class="list-item" @click=${(e: Event) => {
const target = e.target as HTMLElement;
const viewstack = target?.closest('dees-mobile-viewstack') as DeesMobileViewstack;
if (viewstack) handleItemClick(viewstack, 'Milk');
}}>
<div><div class="item-title">Milk</div></div>
<span class="chevron"></span>
</div>
<div class="list-item" @click=${(e: Event) => {
const target = e.target as HTMLElement;
const viewstack = target?.closest('dees-mobile-viewstack') as DeesMobileViewstack;
if (viewstack) handleItemClick(viewstack, 'Bread');
}}>
<div><div class="item-title">Bread</div></div>
<span class="chevron"></span>
</div>
</dees-mobile-view>
<dees-mobile-view view-id="item">
<div class="view-header">
<button class="back-button" @click=${(e: Event) => {
const target = e.target as HTMLElement;
const viewstack = target?.closest('dees-mobile-viewstack') as DeesMobileViewstack;
if (viewstack) handleBack(viewstack);
}}></button>
<h1 class="view-title">Details</h1>
</div>
<div class="item-detail">
<h2>Item Details</h2>
<p>Full item information would appear here.</p>
</div>
</dees-mobile-view>
</dees-mobile-viewstack>
<div class="status-bar">lists (depth: 1)</div>
</div>
</div>
`;
};
/**
* Demo 2: Desktop/Tablet Layout
* Wider layout suitable for tablets and desktop embedded views
*/
const desktopDemo = () => {
injectCssVariables();
return html`
${sharedStyles}
<h3 style="margin: 0 0 16px; color: ${cssManager.bdTheme('#09090b', '#fafafa')};">Desktop/Tablet Layout</h3>
<p style="margin: 0 0 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};">
Wider container suitable for tablet or embedded desktop use. Same navigation behavior.
</p>
<div class="demo-container" style="width: 100%; max-width: 600px; height: 500px;">
<dees-mobile-viewstack
initial-view="categories"
style="height: calc(100% - 44px);"
@view-changed=${(e: CustomEvent) => {
const target = e.target as HTMLElement;
const status = target?.closest('.demo-container')?.querySelector('.status-bar');
if (status) {
status.textContent = `View: ${e.detail.currentView} | Stack: ${e.detail.stackDepth} | Direction: ${e.detail.direction}`;
}
}}
>
<dees-mobile-view view-id="categories">
<div class="view-header">
<h1 class="view-title">Categories</h1>
</div>
<div class="list-item" @click=${(e: Event) => {
const target = e.target as HTMLElement;
const viewstack = target?.closest('dees-mobile-viewstack') as DeesMobileViewstack;
viewstack?.pushView('products');
}}>
<div>
<div class="item-title">Electronics</div>
<div class="item-subtitle">248 products</div>
</div>
<span class="chevron"></span>
</div>
<div class="list-item" @click=${(e: Event) => {
const target = e.target as HTMLElement;
const viewstack = target?.closest('dees-mobile-viewstack') as DeesMobileViewstack;
viewstack?.pushView('products');
}}>
<div>
<div class="item-title">Clothing</div>
<div class="item-subtitle">512 products</div>
</div>
<span class="chevron"></span>
</div>
<div class="list-item" @click=${(e: Event) => {
const target = e.target as HTMLElement;
const viewstack = target?.closest('dees-mobile-viewstack') as DeesMobileViewstack;
viewstack?.pushView('products');
}}>
<div>
<div class="item-title">Home & Garden</div>
<div class="item-subtitle">189 products</div>
</div>
<span class="chevron"></span>
</div>
</dees-mobile-view>
<dees-mobile-view view-id="products">
<div class="view-header">
<button class="back-button" @click=${(e: Event) => {
const target = e.target as HTMLElement;
const viewstack = target?.closest('dees-mobile-viewstack') as DeesMobileViewstack;
viewstack?.popView();
}}></button>
<h1 class="view-title">Products</h1>
</div>
<div class="list-item" @click=${(e: Event) => {
const target = e.target as HTMLElement;
const viewstack = target?.closest('dees-mobile-viewstack') as DeesMobileViewstack;
viewstack?.pushView('product-detail');
}}>
<div>
<div class="item-title">Wireless Headphones</div>
<div class="item-subtitle">$149.99</div>
</div>
<span class="chevron"></span>
</div>
<div class="list-item" @click=${(e: Event) => {
const target = e.target as HTMLElement;
const viewstack = target?.closest('dees-mobile-viewstack') as DeesMobileViewstack;
viewstack?.pushView('product-detail');
}}>
<div>
<div class="item-title">Smart Watch</div>
<div class="item-subtitle">$299.99</div>
</div>
<span class="chevron"></span>
</div>
</dees-mobile-view>
<dees-mobile-view view-id="product-detail">
<div class="view-header">
<button class="back-button" @click=${(e: Event) => {
const target = e.target as HTMLElement;
const viewstack = target?.closest('dees-mobile-viewstack') as DeesMobileViewstack;
viewstack?.popView();
}}></button>
<h1 class="view-title">Product Details</h1>
</div>
<div class="item-detail">
<h2>Wireless Headphones</h2>
<p>Premium noise-cancelling headphones with 30-hour battery life.</p>
<p style="margin-top: 16px; font-weight: 600; color: ${cssManager.bdTheme('#09090b', '#fafafa')};">$149.99</p>
</div>
</dees-mobile-view>
</dees-mobile-viewstack>
<div class="status-bar">View: categories | Stack: 1 | Direction: none</div>
</div>
`;
};
/**
* Demo 3: Programmatic Control
* Demonstrates API methods for controlling navigation
* Uses dees-demowrapper for proper scoped element access in wcctools
*/
const programmaticDemo = () => {
injectCssVariables();
return html`
${sharedStyles}
<h3 style="margin: 0 0 16px; color: ${cssManager.bdTheme('#09090b', '#fafafa')};">Programmatic Control</h3>
<p style="margin: 0 0 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};">
Use the control panel to navigate programmatically via the viewstack API.
</p>
<dees-demowrapper .runAfterRender=${async (wrapper: HTMLElement) => {
const viewstack = wrapper.querySelector('dees-mobile-viewstack') as DeesMobileViewstack;
const backBtn = wrapper.querySelector('.btn-back') as HTMLButtonElement;
const rootBtn = wrapper.querySelector('.btn-root') as HTMLButtonElement;
const statusBar = wrapper.querySelector('.status-bar') as HTMLElement;
const pushABtn = wrapper.querySelector('.btn-push-a') as HTMLButtonElement;
const pushBBtn = wrapper.querySelector('.btn-push-b') as HTMLButtonElement;
const pushCBtn = wrapper.querySelector('.btn-push-c') as HTMLButtonElement;
if (!viewstack) return;
const updateButtons = () => {
if (backBtn) backBtn.disabled = !viewstack.canGoBack;
if (rootBtn) rootBtn.disabled = viewstack.stackDepth <= 1;
};
const updateStatus = () => {
if (statusBar) {
statusBar.textContent = `Current: ${viewstack.currentView} | Stack: [${viewstack.viewStack.join(' → ')}] | canGoBack: ${viewstack.canGoBack}`;
}
};
// Set up button click handlers
pushABtn?.addEventListener('click', () => viewstack.pushView('view-a'));
pushBBtn?.addEventListener('click', () => viewstack.pushView('view-b'));
pushCBtn?.addEventListener('click', () => viewstack.pushView('view-c'));
backBtn?.addEventListener('click', () => viewstack.popView());
rootBtn?.addEventListener('click', () => viewstack.goToRoot(false));
// Listen for view changes to update UI
viewstack.addEventListener('view-changed', () => {
updateButtons();
updateStatus();
});
// Initial state
updateButtons();
updateStatus();
}}>
<div class="demo-container" style="width: 100%; max-width: 500px; height: 450px;">
<div class="control-panel">
<button class="control-button primary btn-push-a">Push View A</button>
<button class="control-button primary btn-push-b">Push View B</button>
<button class="control-button primary btn-push-c">Push View C</button>
<button class="control-button btn-back" disabled>Pop View</button>
<button class="control-button btn-root" disabled>Go to Root</button>
</div>
<dees-mobile-viewstack initial-view="home" style="height: calc(100% - 100px);">
<dees-mobile-view view-id="home">
<div class="item-detail" style="text-align: center; padding-top: 60px;">
<h2>Home View</h2>
<p>This is the root view. Use the buttons above to push views onto the stack.</p>
</div>
</dees-mobile-view>
<dees-mobile-view view-id="view-a">
<div class="item-detail" style="text-align: center; padding-top: 60px; background: ${cssManager.bdTheme('#fef2f2', '#1c1917')};">
<h2 style="color: #ef4444;">View A</h2>
<p>You navigated to View A</p>
</div>
</dees-mobile-view>
<dees-mobile-view view-id="view-b">
<div class="item-detail" style="text-align: center; padding-top: 60px; background: ${cssManager.bdTheme('#f0fdf4', '#14532d')};">
<h2 style="color: #22c55e;">View B</h2>
<p>You navigated to View B</p>
</div>
</dees-mobile-view>
<dees-mobile-view view-id="view-c">
<div class="item-detail" style="text-align: center; padding-top: 60px; background: ${cssManager.bdTheme('#eff6ff', '#1e3a5f')};">
<h2 style="color: #3b82f6;">View C</h2>
<p>You navigated to View C</p>
</div>
</dees-mobile-view>
</dees-mobile-viewstack>
<div class="status-bar">Current: home | Stack: [home] | canGoBack: false</div>
</div>
</dees-demowrapper>
`;
};
/**
* Demo 4: Deep Navigation (4+ levels)
* Shows handling of deeply nested navigation
*/
const deepNavigationDemo = () => {
injectCssVariables();
return html`
${sharedStyles}
<h3 style="margin: 0 0 16px; color: ${cssManager.bdTheme('#09090b', '#fafafa')};">Deep Navigation (5 Levels)</h3>
<p style="margin: 0 0 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};">
Navigate through 5 levels deep: Region → Country → City → District → Location
</p>
<div class="demo-container" style="width: 100%; max-width: 450px; height: 500px;">
<dees-mobile-viewstack
initial-view="regions"
style="height: calc(100% - 44px);"
@view-changed=${(e: CustomEvent) => {
const target = e.target as HTMLElement;
const status = target?.closest('.demo-container')?.querySelector('.status-bar');
if (status) {
const depth = e.detail.stackDepth;
const levels = ['Regions', 'Country', 'City', 'District', 'Location'];
status.textContent = `Level ${depth}/5: ${levels[depth - 1] || 'Unknown'}`;
}
}}
>
<dees-mobile-view view-id="regions">
<div class="view-header">
<h1 class="view-title">Regions</h1>
</div>
<div class="list-item" @click=${(e: Event) => {
const target = e.target as HTMLElement;
(target?.closest('dees-mobile-viewstack') as DeesMobileViewstack)?.pushView('country');
}}>
<div><div class="item-title">Europe</div><div class="item-subtitle">44 countries</div></div>
<span class="chevron"></span>
</div>
</dees-mobile-view>
<dees-mobile-view view-id="country">
<div class="view-header">
<button class="back-button" @click=${(e: Event) => {
(e.target as HTMLElement)?.closest('dees-mobile-viewstack')?.dispatchEvent(new CustomEvent('pop-request'));
((e.target as HTMLElement)?.closest('dees-mobile-viewstack') as DeesMobileViewstack)?.popView();
}}></button>
<h1 class="view-title">Germany</h1>
</div>
<div class="list-item" @click=${(e: Event) => {
((e.target as HTMLElement)?.closest('dees-mobile-viewstack') as DeesMobileViewstack)?.pushView('city');
}}>
<div><div class="item-title">Berlin</div><div class="item-subtitle">12 districts</div></div>
<span class="chevron"></span>
</div>
</dees-mobile-view>
<dees-mobile-view view-id="city">
<div class="view-header">
<button class="back-button" @click=${(e: Event) => {
((e.target as HTMLElement)?.closest('dees-mobile-viewstack') as DeesMobileViewstack)?.popView();
}}></button>
<h1 class="view-title">Berlin</h1>
</div>
<div class="list-item" @click=${(e: Event) => {
((e.target as HTMLElement)?.closest('dees-mobile-viewstack') as DeesMobileViewstack)?.pushView('district');
}}>
<div><div class="item-title">Mitte</div><div class="item-subtitle">Central district</div></div>
<span class="chevron"></span>
</div>
</dees-mobile-view>
<dees-mobile-view view-id="district">
<div class="view-header">
<button class="back-button" @click=${(e: Event) => {
((e.target as HTMLElement)?.closest('dees-mobile-viewstack') as DeesMobileViewstack)?.popView();
}}></button>
<h1 class="view-title">Mitte</h1>
</div>
<div class="list-item" @click=${(e: Event) => {
((e.target as HTMLElement)?.closest('dees-mobile-viewstack') as DeesMobileViewstack)?.pushView('location');
}}>
<div><div class="item-title">Brandenburg Gate</div><div class="item-subtitle">Historic landmark</div></div>
<span class="chevron"></span>
</div>
</dees-mobile-view>
<dees-mobile-view view-id="location">
<div class="view-header">
<button class="back-button" @click=${(e: Event) => {
((e.target as HTMLElement)?.closest('dees-mobile-viewstack') as DeesMobileViewstack)?.popView();
}}></button>
<h1 class="view-title">Brandenburg Gate</h1>
</div>
<div class="item-detail">
<h2>Brandenburg Gate</h2>
<p>An 18th-century neoclassical monument in Berlin. One of the best-known landmarks of Germany.</p>
<p style="margin-top: 16px;">
<strong>You've reached the deepest level!</strong><br>
Use the back button to navigate up through the hierarchy.
</p>
</div>
</dees-mobile-view>
</dees-mobile-viewstack>
<div class="status-bar">Level 1/5: Regions</div>
</div>
`;
};
// Export array of demo functions
export const demoFunc = [
mobileDemo,
desktopDemo,
programmaticDemo,
deepNavigationDemo,
];