initial
This commit is contained in:
8
ts_web/00_commitinfo_data.ts
Normal file
8
ts_web/00_commitinfo_data.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@design.estate/dees-catalog',
|
||||
version: '3.33.0',
|
||||
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
||||
}
|
||||
13
ts_web/elements/00colors.ts
Normal file
13
ts_web/elements/00colors.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export const dark = {
|
||||
blue: '#0050b9',
|
||||
blueActive: '#0069f2',
|
||||
blueMuted: '#012452',
|
||||
text: '#ffffff',
|
||||
}
|
||||
|
||||
export const bright = {
|
||||
blue: '#0050b9',
|
||||
blueActive: '#0069f2',
|
||||
blueMuted: '#0069f2',
|
||||
text: '#333333',
|
||||
}
|
||||
53
ts_web/elements/00fonts.ts
Normal file
53
ts_web/elements/00fonts.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { unsafeCSS } from '@design.estate/dees-element';
|
||||
|
||||
/**
|
||||
* Geist Sans font family - Main font for the design system
|
||||
* Already available in the environment, no need to load
|
||||
*/
|
||||
export const geistSansFont = 'Geist Sans';
|
||||
|
||||
/**
|
||||
* Intel One Mono font family - Monospace font for code and technical content
|
||||
* Already available in the environment, no need to load
|
||||
*/
|
||||
export const intelOneMonoFont = 'Intel One Mono';
|
||||
|
||||
/**
|
||||
* Complete font family stacks with fallbacks
|
||||
*/
|
||||
export const geistFontFamily = `'${geistSansFont}', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif`;
|
||||
|
||||
export const monoFontFamily = `'${intelOneMonoFont}', 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace`;
|
||||
|
||||
/**
|
||||
* CSS-ready font family values using unsafeCSS
|
||||
* Use these in component styles
|
||||
*/
|
||||
export const cssGeistFontFamily = unsafeCSS(geistFontFamily);
|
||||
export const cssMonoFontFamily = unsafeCSS(monoFontFamily);
|
||||
|
||||
/**
|
||||
* Cal Sans font for headings - Display font
|
||||
* May need to be loaded separately
|
||||
*/
|
||||
export const calSansFont = 'Cal Sans';
|
||||
export const calSansFontFamily = `'${calSansFont}', ${geistFontFamily}`;
|
||||
export const cssCalSansFontFamily = unsafeCSS(calSansFontFamily);
|
||||
|
||||
/**
|
||||
* Roboto Slab font for special content - Serif font
|
||||
* May need to be loaded separately
|
||||
*/
|
||||
export const robotoSlabFont = 'Roboto Slab';
|
||||
export const robotoSlabFontFamily = `'${robotoSlabFont}', Georgia, serif`;
|
||||
export const cssRobotoSlabFontFamily = unsafeCSS(robotoSlabFontFamily);
|
||||
|
||||
/**
|
||||
* Base font styles that can be applied to components
|
||||
*/
|
||||
export const baseFontStyles = unsafeCSS(`
|
||||
font-family: ${geistFontFamily};
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-feature-settings: 'cv11', 'tnum', 'cv05' 1;
|
||||
`);
|
||||
13
ts_web/elements/00plugins.ts
Normal file
13
ts_web/elements/00plugins.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// @push.rocks scope
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
|
||||
export {
|
||||
smartpromise,
|
||||
}
|
||||
|
||||
// @tsclass scope
|
||||
import * as tsclass from '@tsclass/tsclass';
|
||||
|
||||
export {
|
||||
tsclass
|
||||
}
|
||||
218
ts_web/elements/00theme.ts
Normal file
218
ts_web/elements/00theme.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
import { css, type CSSResult } from '@design.estate/dees-element';
|
||||
|
||||
// ============================================
|
||||
// Theme Token Type Definitions
|
||||
// ============================================
|
||||
|
||||
export interface IThemeColors {
|
||||
bgPrimary: string;
|
||||
bgSecondary: string;
|
||||
bgTertiary: string;
|
||||
textPrimary: string;
|
||||
textSecondary: string;
|
||||
textMuted: string;
|
||||
borderDefault: string;
|
||||
borderSubtle: string;
|
||||
borderStrong: string;
|
||||
accentPrimary: string;
|
||||
accentSuccess: string;
|
||||
accentWarning: string;
|
||||
accentError: string;
|
||||
}
|
||||
|
||||
export interface IThemeSpacing {
|
||||
xs: string;
|
||||
sm: string;
|
||||
md: string;
|
||||
lg: string;
|
||||
xl: string;
|
||||
'2xl': string;
|
||||
'3xl': string;
|
||||
}
|
||||
|
||||
export interface IThemeRadius {
|
||||
xs: string;
|
||||
sm: string;
|
||||
md: string;
|
||||
lg: string;
|
||||
xl: string;
|
||||
full: string;
|
||||
}
|
||||
|
||||
export interface IThemeShadows {
|
||||
xs: string;
|
||||
sm: string;
|
||||
md: string;
|
||||
lg: string;
|
||||
}
|
||||
|
||||
export interface IThemeTransitions {
|
||||
fast: string;
|
||||
default: string;
|
||||
slow: string;
|
||||
slower: string;
|
||||
}
|
||||
|
||||
export interface IThemeControlHeights {
|
||||
sm: string;
|
||||
md: string;
|
||||
lg: string;
|
||||
xl: string;
|
||||
}
|
||||
|
||||
export interface ITheme {
|
||||
colors: {
|
||||
light: IThemeColors;
|
||||
dark: IThemeColors;
|
||||
};
|
||||
spacing: IThemeSpacing;
|
||||
radius: IThemeRadius;
|
||||
shadows: IThemeShadows;
|
||||
transitions: IThemeTransitions;
|
||||
controlHeights: IThemeControlHeights;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Default Theme Values (TypeScript Object)
|
||||
// ============================================
|
||||
|
||||
export const themeDefaults: ITheme = {
|
||||
colors: {
|
||||
light: {
|
||||
bgPrimary: '#ffffff',
|
||||
bgSecondary: '#fafafa',
|
||||
bgTertiary: '#f4f4f5',
|
||||
textPrimary: '#09090b',
|
||||
textSecondary: '#374151',
|
||||
textMuted: '#71717a',
|
||||
borderDefault: '#e5e7eb',
|
||||
borderSubtle: '#f4f4f5',
|
||||
borderStrong: '#d1d5db',
|
||||
accentPrimary: '#3b82f6',
|
||||
accentSuccess: '#22c55e',
|
||||
accentWarning: '#f59e0b',
|
||||
accentError: '#ef4444',
|
||||
},
|
||||
dark: {
|
||||
bgPrimary: '#09090b',
|
||||
bgSecondary: '#0a0a0a',
|
||||
bgTertiary: '#18181b',
|
||||
textPrimary: '#fafafa',
|
||||
textSecondary: '#d4d4d8',
|
||||
textMuted: '#a1a1aa',
|
||||
borderDefault: '#27272a',
|
||||
borderSubtle: '#1a1a1a',
|
||||
borderStrong: '#3f3f46',
|
||||
accentPrimary: '#3b82f6',
|
||||
accentSuccess: '#22c55e',
|
||||
accentWarning: '#f59e0b',
|
||||
accentError: '#ef4444',
|
||||
},
|
||||
},
|
||||
spacing: {
|
||||
xs: '4px',
|
||||
sm: '8px',
|
||||
md: '12px',
|
||||
lg: '16px',
|
||||
xl: '24px',
|
||||
'2xl': '32px',
|
||||
'3xl': '48px',
|
||||
},
|
||||
radius: {
|
||||
xs: '2px',
|
||||
sm: '4px',
|
||||
md: '6px',
|
||||
lg: '8px',
|
||||
xl: '12px',
|
||||
full: '999px',
|
||||
},
|
||||
shadows: {
|
||||
xs: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
|
||||
sm: '0 1px 3px rgba(0, 0, 0, 0.1)',
|
||||
md: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
||||
lg: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
||||
},
|
||||
transitions: {
|
||||
fast: '0.1s',
|
||||
default: '0.15s',
|
||||
slow: '0.2s',
|
||||
slower: '0.3s',
|
||||
},
|
||||
controlHeights: {
|
||||
sm: '32px',
|
||||
md: '36px',
|
||||
lg: '40px',
|
||||
xl: '48px',
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// CSS Block for Component Import
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Default theme styles to be imported into every component's static styles array.
|
||||
* Provides CSS custom properties for spacing, radius, shadows, transitions, and control heights.
|
||||
*
|
||||
* Usage:
|
||||
* ```typescript
|
||||
* import { themeDefaultStyles } from '../00theme.js';
|
||||
*
|
||||
* @customElement('my-component')
|
||||
* export class MyComponent extends DeesElement {
|
||||
* public static styles = [
|
||||
* themeDefaultStyles,
|
||||
* cssManager.defaultStyles,
|
||||
* css`...`
|
||||
* ];
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const themeDefaultStyles: CSSResult = css`
|
||||
:host {
|
||||
/* ========================================
|
||||
* Spacing Scale
|
||||
* ======================================== */
|
||||
--dees-spacing-xs: 4px;
|
||||
--dees-spacing-sm: 8px;
|
||||
--dees-spacing-md: 12px;
|
||||
--dees-spacing-lg: 16px;
|
||||
--dees-spacing-xl: 24px;
|
||||
--dees-spacing-2xl: 32px;
|
||||
--dees-spacing-3xl: 48px;
|
||||
|
||||
/* ========================================
|
||||
* Border Radius Scale
|
||||
* ======================================== */
|
||||
--dees-radius-xs: 2px;
|
||||
--dees-radius-sm: 4px;
|
||||
--dees-radius-md: 6px;
|
||||
--dees-radius-lg: 8px;
|
||||
--dees-radius-xl: 12px;
|
||||
--dees-radius-full: 999px;
|
||||
|
||||
/* ========================================
|
||||
* Shadow Elevation Scale
|
||||
* ======================================== */
|
||||
--dees-shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--dees-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
--dees-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
--dees-shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
|
||||
/* ========================================
|
||||
* Transition Duration Scale
|
||||
* ======================================== */
|
||||
--dees-transition-fast: 0.1s;
|
||||
--dees-transition-default: 0.15s;
|
||||
--dees-transition-slow: 0.2s;
|
||||
--dees-transition-slower: 0.3s;
|
||||
|
||||
/* ========================================
|
||||
* Control Height Scale
|
||||
* ======================================== */
|
||||
--dees-control-height-sm: 32px;
|
||||
--dees-control-height-md: 36px;
|
||||
--dees-control-height-lg: 40px;
|
||||
--dees-control-height-xl: 48px;
|
||||
}
|
||||
`;
|
||||
50
ts_web/elements/00zindex.ts
Normal file
50
ts_web/elements/00zindex.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Central z-index management for consistent stacking order
|
||||
* Higher numbers appear on top of lower numbers
|
||||
*/
|
||||
|
||||
export const zIndexLayers = {
|
||||
// Base layer: Regular content
|
||||
base: {
|
||||
content: 'auto',
|
||||
inputElements: 1,
|
||||
},
|
||||
|
||||
// Fixed UI elements
|
||||
fixed: {
|
||||
appBar: 10,
|
||||
sideMenu: 10,
|
||||
mobileNav: 250,
|
||||
},
|
||||
|
||||
// Overlay backdrops (semi-transparent backgrounds)
|
||||
backdrop: {
|
||||
dropdown: 1999,
|
||||
modal: 2999,
|
||||
contextMenu: 3999,
|
||||
screensaver: 9998,
|
||||
},
|
||||
|
||||
// Interactive overlays
|
||||
overlay: {
|
||||
dropdown: 2000,
|
||||
modal: 3000,
|
||||
contextMenu: 4000,
|
||||
toast: 5000,
|
||||
screensaver: 9999, // Screensaver on top of everything
|
||||
},
|
||||
} as const;
|
||||
|
||||
// Helper function to get z-index value
|
||||
export function getZIndex(category: keyof typeof zIndexLayers, subcategory?: string): number | string {
|
||||
const categoryObj = zIndexLayers[category];
|
||||
if (typeof categoryObj === 'object' && subcategory) {
|
||||
return categoryObj[subcategory as keyof typeof categoryObj] || 'auto';
|
||||
}
|
||||
return typeof categoryObj === 'number' ? categoryObj : 'auto';
|
||||
}
|
||||
|
||||
// Z-index assignments for components
|
||||
export const componentZIndex = {
|
||||
'dees-screensaver': zIndexLayers.overlay.screensaver,
|
||||
} as const;
|
||||
319
ts_web/elements/dees-screensaver/dees-screensaver.ts
Normal file
319
ts_web/elements/dees-screensaver/dees-screensaver.ts
Normal file
@@ -0,0 +1,319 @@
|
||||
import {
|
||||
customElement,
|
||||
DeesElement,
|
||||
type TemplateResult,
|
||||
html,
|
||||
property,
|
||||
css,
|
||||
cssManager,
|
||||
state,
|
||||
} from '@design.estate/dees-element';
|
||||
import { zIndexLayers } from '../00zindex.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-screensaver': DeesScreensaver;
|
||||
}
|
||||
}
|
||||
|
||||
// Subtle shadcn-inspired color palette
|
||||
const colors = [
|
||||
'hsl(0 0% 98%)', // zinc-50
|
||||
'hsl(240 5% 65%)', // zinc-400
|
||||
'hsl(240 4% 46%)', // zinc-500
|
||||
'hsl(240 5% 34%)', // zinc-600
|
||||
'hsl(217 91% 60%)', // blue-500
|
||||
'hsl(142 71% 45%)', // green-500
|
||||
];
|
||||
|
||||
@customElement('dees-screensaver')
|
||||
export class DeesScreensaver extends DeesElement {
|
||||
public static demo = () => html`<dees-screensaver .active=${true}></dees-screensaver>`;
|
||||
|
||||
// Instance management
|
||||
private static instance: DeesScreensaver | null = null;
|
||||
|
||||
public static async show(): Promise<DeesScreensaver> {
|
||||
if (DeesScreensaver.instance) {
|
||||
DeesScreensaver.instance.active = true;
|
||||
return DeesScreensaver.instance;
|
||||
}
|
||||
|
||||
const screensaver = new DeesScreensaver();
|
||||
screensaver.active = true;
|
||||
document.body.appendChild(screensaver);
|
||||
DeesScreensaver.instance = screensaver;
|
||||
return screensaver;
|
||||
}
|
||||
|
||||
public static hide(): void {
|
||||
if (DeesScreensaver.instance) {
|
||||
DeesScreensaver.instance.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static destroy(): void {
|
||||
if (DeesScreensaver.instance) {
|
||||
DeesScreensaver.instance.remove();
|
||||
DeesScreensaver.instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Styles
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: ${zIndexLayers.overlay.screensaver};
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.8s ease;
|
||||
}
|
||||
|
||||
:host([active]) {
|
||||
pointer-events: all;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: hsl(240 10% 4%);
|
||||
}
|
||||
|
||||
.time-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 96px;
|
||||
font-weight: 200;
|
||||
letter-spacing: -2px;
|
||||
line-height: 1;
|
||||
transition: color 1.5s ease;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.5px;
|
||||
opacity: 0.5;
|
||||
text-transform: uppercase;
|
||||
transition: color 1.5s ease;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.time {
|
||||
font-size: 48px;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
accessor active = false;
|
||||
|
||||
@state()
|
||||
accessor currentTime = '';
|
||||
|
||||
@state()
|
||||
accessor currentDate = '';
|
||||
|
||||
@state()
|
||||
accessor currentColor = colors[0];
|
||||
|
||||
// Animation state - non-reactive for smooth animation
|
||||
private posX = 100;
|
||||
private posY = 100;
|
||||
private velocityX = 0.3;
|
||||
private velocityY = 0.2;
|
||||
private animationId: number | null = null;
|
||||
private timeUpdateInterval: ReturnType<typeof setInterval> | null = null;
|
||||
private colorIndex = 0;
|
||||
private elementWidth = 280;
|
||||
private elementHeight = 140;
|
||||
private hasBounced = false;
|
||||
private timeContainerEl: HTMLElement | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.updateTime();
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="backdrop" @click=${this.handleClick}></div>
|
||||
<div class="time-container">
|
||||
<span class="time" style="color: ${this.currentColor};">${this.currentTime}</span>
|
||||
<span class="date" style="color: ${this.currentColor};">${this.currentDate}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
public firstUpdated(): void {
|
||||
this.timeContainerEl = this.shadowRoot?.querySelector('.time-container') as HTMLElement;
|
||||
}
|
||||
|
||||
async connectedCallback(): Promise<void> {
|
||||
await super.connectedCallback();
|
||||
this.startAnimation();
|
||||
this.startTimeUpdate();
|
||||
}
|
||||
|
||||
async disconnectedCallback(): Promise<void> {
|
||||
await super.disconnectedCallback();
|
||||
this.stopAnimation();
|
||||
this.stopTimeUpdate();
|
||||
}
|
||||
|
||||
updated(changedProperties: Map<string, unknown>): void {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has('active')) {
|
||||
if (this.active) {
|
||||
this.startAnimation();
|
||||
this.startTimeUpdate();
|
||||
} else {
|
||||
this.stopAnimation();
|
||||
this.stopTimeUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateTime(): void {
|
||||
const now = new Date();
|
||||
const hours = String(now.getHours()).padStart(2, '0');
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
this.currentTime = `${hours}:${minutes}`;
|
||||
|
||||
// Format date like "Monday, January 6"
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
};
|
||||
this.currentDate = now.toLocaleDateString('en-US', options);
|
||||
}
|
||||
|
||||
private startTimeUpdate(): void {
|
||||
if (this.timeUpdateInterval) return;
|
||||
this.updateTime();
|
||||
this.timeUpdateInterval = setInterval(() => this.updateTime(), 1000);
|
||||
}
|
||||
|
||||
private stopTimeUpdate(): void {
|
||||
if (this.timeUpdateInterval) {
|
||||
clearInterval(this.timeUpdateInterval);
|
||||
this.timeUpdateInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
private startAnimation(): void {
|
||||
if (this.animationId) return;
|
||||
|
||||
// Initialize position randomly
|
||||
const maxX = window.innerWidth - this.elementWidth;
|
||||
const maxY = window.innerHeight - this.elementHeight;
|
||||
this.posX = Math.random() * Math.max(0, maxX);
|
||||
this.posY = Math.random() * Math.max(0, maxY);
|
||||
|
||||
// Randomize initial direction - very slow, elegant movement
|
||||
this.velocityX = (Math.random() > 0.5 ? 1 : -1) * (0.2 + Math.random() * 0.15);
|
||||
this.velocityY = (Math.random() > 0.5 ? 1 : -1) * (0.15 + Math.random() * 0.1);
|
||||
|
||||
// Reset bounce state
|
||||
this.hasBounced = false;
|
||||
|
||||
const animate = () => {
|
||||
if (!this.active) {
|
||||
this.animationId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const maxX = window.innerWidth - this.elementWidth;
|
||||
const maxY = window.innerHeight - this.elementHeight;
|
||||
|
||||
// Update position
|
||||
this.posX += this.velocityX;
|
||||
this.posY += this.velocityY;
|
||||
|
||||
// Track if we're currently at a boundary
|
||||
let atBoundary = false;
|
||||
|
||||
// Bounce off walls
|
||||
if (this.posX <= 0) {
|
||||
this.posX = 0;
|
||||
this.velocityX = Math.abs(this.velocityX);
|
||||
atBoundary = true;
|
||||
} else if (this.posX >= maxX) {
|
||||
this.posX = maxX;
|
||||
this.velocityX = -Math.abs(this.velocityX);
|
||||
atBoundary = true;
|
||||
}
|
||||
|
||||
if (this.posY <= 0) {
|
||||
this.posY = 0;
|
||||
this.velocityY = Math.abs(this.velocityY);
|
||||
atBoundary = true;
|
||||
} else if (this.posY >= maxY) {
|
||||
this.posY = maxY;
|
||||
this.velocityY = -Math.abs(this.velocityY);
|
||||
atBoundary = true;
|
||||
}
|
||||
|
||||
// Change color only once per bounce (when entering boundary, not while at it)
|
||||
if (atBoundary && !this.hasBounced) {
|
||||
this.hasBounced = true;
|
||||
this.colorIndex = (this.colorIndex + 1) % colors.length;
|
||||
this.currentColor = colors[this.colorIndex];
|
||||
} else if (!atBoundary) {
|
||||
this.hasBounced = false;
|
||||
}
|
||||
|
||||
// Direct DOM manipulation for smooth position updates (no re-render)
|
||||
if (this.timeContainerEl) {
|
||||
this.timeContainerEl.style.transform = `translate(${this.posX}px, ${this.posY}px)`;
|
||||
}
|
||||
|
||||
this.animationId = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
this.animationId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
private stopAnimation(): void {
|
||||
if (this.animationId) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
this.animationId = null;
|
||||
}
|
||||
}
|
||||
|
||||
private handleClick(): void {
|
||||
this.dispatchEvent(new CustomEvent('screensaver-click'));
|
||||
// Optionally hide on click
|
||||
this.active = false;
|
||||
}
|
||||
}
|
||||
1
ts_web/elements/dees-screensaver/index.ts
Normal file
1
ts_web/elements/dees-screensaver/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './dees-screensaver.js';
|
||||
3
ts_web/elements/helperclasses/formcontroller.ts
Normal file
3
ts_web/elements/helperclasses/formcontroller.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export class FormController {
|
||||
|
||||
}
|
||||
1
ts_web/elements/helperclasses/index.ts
Normal file
1
ts_web/elements/helperclasses/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './formcontroller.js';
|
||||
5
ts_web/elements/index.ts
Normal file
5
ts_web/elements/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './00zindex.js';
|
||||
export * from './00theme.js';
|
||||
|
||||
// Standalone Components
|
||||
export * from './dees-screensaver/index.js';
|
||||
34
ts_web/elements/interfaces/appbarmenuitem.ts
Normal file
34
ts_web/elements/interfaces/appbarmenuitem.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as plugins from '../00plugins.js';
|
||||
|
||||
/**
|
||||
* Divider menu item
|
||||
*/
|
||||
export interface IAppBarMenuDivider {
|
||||
divider: true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular menu item
|
||||
*/
|
||||
export interface IAppBarMenuItemRegular extends plugins.tsclass.website.IMenuItem {
|
||||
id?: string;
|
||||
shortcut?: string; // e.g., "Cmd+S" or "Ctrl+S"
|
||||
submenu?: IAppBarMenuItem[];
|
||||
disabled?: boolean;
|
||||
checked?: boolean; // For checkbox menu items
|
||||
radioGroup?: string; // For radio button menu items
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended menu item interface for app bar menus
|
||||
* Can be either a regular menu item or a divider
|
||||
*/
|
||||
export type IAppBarMenuItem = IAppBarMenuItemRegular | IAppBarMenuDivider;
|
||||
|
||||
/**
|
||||
* Interface for the menu bar configuration
|
||||
*/
|
||||
export interface IMenuBar {
|
||||
menuItems: IAppBarMenuItem[];
|
||||
onMenuSelect?: (item: IAppBarMenuItem) => void;
|
||||
}
|
||||
365
ts_web/elements/interfaces/appconfig.ts
Normal file
365
ts_web/elements/interfaces/appconfig.ts
Normal file
@@ -0,0 +1,365 @@
|
||||
import type { TemplateResult } from '@design.estate/dees-element';
|
||||
import type { IAppBarMenuItem } from './appbarmenuitem.js';
|
||||
import type { IMenuItem } from './tab.js';
|
||||
import type { IMenuGroup } from './menugroup.js';
|
||||
import type { ISecondaryMenuGroup, ISecondaryMenuItem } from './secondarymenu.js';
|
||||
|
||||
// ==========================================
|
||||
// BOTTOM BAR INTERFACES
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Bottom bar widget status for styling
|
||||
*/
|
||||
export type TBottomBarWidgetStatus = 'idle' | 'active' | 'success' | 'warning' | 'error';
|
||||
|
||||
/**
|
||||
* Generic status widget for the bottom bar
|
||||
*/
|
||||
export interface IBottomBarWidget {
|
||||
/** Unique identifier for the widget */
|
||||
id: string;
|
||||
/** Icon to display (lucide icon name) */
|
||||
iconName?: string;
|
||||
/** Text label to display */
|
||||
label?: string;
|
||||
/** Status affects styling (colors) */
|
||||
status?: TBottomBarWidgetStatus;
|
||||
/** Tooltip text */
|
||||
tooltip?: string;
|
||||
/** Whether the widget shows a loading spinner */
|
||||
loading?: boolean;
|
||||
/** Click handler for the widget */
|
||||
onClick?: () => void;
|
||||
/** Optional context menu items on right-click */
|
||||
contextMenuItems?: IBottomBarContextMenuItem[];
|
||||
/** Position: 'left' (default) or 'right' */
|
||||
position?: 'left' | 'right';
|
||||
/** Order within position group (lower = earlier) */
|
||||
order?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Context menu item for bottom bar widgets
|
||||
*/
|
||||
export interface IBottomBarContextMenuItem {
|
||||
name: string;
|
||||
iconName?: string;
|
||||
action: () => void | Promise<void>;
|
||||
disabled?: boolean;
|
||||
divider?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bottom bar action (quick action button)
|
||||
*/
|
||||
export interface IBottomBarAction {
|
||||
/** Unique identifier */
|
||||
id: string;
|
||||
/** Icon to display */
|
||||
iconName: string;
|
||||
/** Tooltip */
|
||||
tooltip?: string;
|
||||
/** Click handler */
|
||||
onClick: () => void | Promise<void>;
|
||||
/** Whether action is disabled */
|
||||
disabled?: boolean;
|
||||
/** Position: 'left' or 'right' (default) */
|
||||
position?: 'left' | 'right';
|
||||
}
|
||||
|
||||
/**
|
||||
* Bottom bar configuration
|
||||
*/
|
||||
export interface IBottomBarConfig {
|
||||
/** Whether bottom bar is visible */
|
||||
visible?: boolean;
|
||||
/** Initial widgets */
|
||||
widgets?: IBottomBarWidget[];
|
||||
/** Initial actions */
|
||||
actions?: IBottomBarAction[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Bottom bar programmatic API
|
||||
*/
|
||||
export interface IBottomBarAPI {
|
||||
/** Add a widget */
|
||||
addWidget: (widget: IBottomBarWidget) => void;
|
||||
/** Update an existing widget by ID */
|
||||
updateWidget: (id: string, update: Partial<IBottomBarWidget>) => void;
|
||||
/** Remove a widget by ID */
|
||||
removeWidget: (id: string) => void;
|
||||
/** Get a widget by ID */
|
||||
getWidget: (id: string) => IBottomBarWidget | undefined;
|
||||
/** Clear all widgets */
|
||||
clearWidgets: () => void;
|
||||
/** Add an action button */
|
||||
addAction: (action: IBottomBarAction) => void;
|
||||
/** Remove an action by ID */
|
||||
removeAction: (id: string) => void;
|
||||
/** Clear all actions */
|
||||
clearActions: () => void;
|
||||
}
|
||||
|
||||
// Forward declaration for circular reference
|
||||
export type TDeesAppui = HTMLElement & {
|
||||
setAppBarMenus: (menus: IAppBarMenuItem[]) => void;
|
||||
updateAppBarMenu: (name: string, update: Partial<IAppBarMenuItem>) => void;
|
||||
setBreadcrumbs: (breadcrumbs: string | string[]) => void;
|
||||
setUser: (user: IAppUser | undefined) => void;
|
||||
setProfileMenuItems: (items: IAppBarMenuItem[]) => void;
|
||||
setSearchVisible: (visible: boolean) => void;
|
||||
setWindowControlsVisible: (visible: boolean) => void;
|
||||
setMainMenu: (config: IMainMenuConfig) => void;
|
||||
updateMainMenuGroup: (groupName: string, update: Partial<IMenuGroup>) => void;
|
||||
addMainMenuItem: (groupName: string, tab: IMenuItem) => void;
|
||||
removeMainMenuItem: (groupName: string, tabKey: string) => void;
|
||||
setMainMenuSelection: (tabKey: string) => void;
|
||||
setMainMenuCollapsed: (collapsed: boolean) => void;
|
||||
setMainMenuVisible: (visible: boolean) => void;
|
||||
setSecondaryMenuCollapsed: (collapsed: boolean) => void;
|
||||
setSecondaryMenuVisible: (visible: boolean) => void;
|
||||
setContentTabsVisible: (visible: boolean) => void;
|
||||
setContentTabsAutoHide: (enabled: boolean, threshold?: number) => void;
|
||||
setMainMenuBadge: (tabKey: string, badge: string | number) => void;
|
||||
clearMainMenuBadge: (tabKey: string) => void;
|
||||
setSecondaryMenu: (config: { heading?: string; groups: ISecondaryMenuGroup[] }) => void;
|
||||
updateSecondaryMenuGroup: (groupName: string, update: Partial<ISecondaryMenuGroup>) => void;
|
||||
addSecondaryMenuItem: (groupName: string, item: ISecondaryMenuItem) => void;
|
||||
setSecondaryMenuSelection: (itemKey: string) => void;
|
||||
clearSecondaryMenu: () => void;
|
||||
setContentTabs: (tabs: IMenuItem[]) => void;
|
||||
addContentTab: (tab: IMenuItem) => void;
|
||||
removeContentTab: (tabKey: string) => void;
|
||||
selectContentTab: (tabKey: string) => void;
|
||||
getSelectedContentTab: () => IMenuItem | undefined;
|
||||
activityLog: IActivityLogAPI;
|
||||
setActivityLogVisible: (visible: boolean) => void;
|
||||
toggleActivityLog: () => void;
|
||||
getActivityLogVisible: () => boolean;
|
||||
navigateToView: (viewId: string, params?: Record<string, string>) => Promise<boolean>;
|
||||
getCurrentView: () => IViewDefinition | undefined;
|
||||
// Bottom bar
|
||||
bottomBar: IBottomBarAPI;
|
||||
setBottomBarVisible: (visible: boolean) => void;
|
||||
getBottomBarVisible: () => boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* User configuration for the app bar
|
||||
*/
|
||||
export interface IAppUser {
|
||||
name: string;
|
||||
email?: string;
|
||||
avatar?: string;
|
||||
status?: 'online' | 'offline' | 'busy' | 'away';
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity entry for the activity log
|
||||
*/
|
||||
export interface IActivityEntry {
|
||||
/** Unique identifier (auto-generated if not provided) */
|
||||
id?: string;
|
||||
/** Timestamp (auto-set to now if not provided) */
|
||||
timestamp?: Date;
|
||||
/** Activity type for icon styling */
|
||||
type: 'login' | 'logout' | 'view' | 'create' | 'update' | 'delete' | 'custom';
|
||||
/** User who performed the action */
|
||||
user: string;
|
||||
/** Activity message */
|
||||
message: string;
|
||||
/** Optional custom icon (overrides type-based icon) */
|
||||
iconName?: string;
|
||||
/** Optional additional data */
|
||||
data?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity log programmatic API
|
||||
*/
|
||||
export interface IActivityLogAPI {
|
||||
/** Add a single activity entry */
|
||||
add: (entry: IActivityEntry) => void;
|
||||
/** Add multiple activity entries */
|
||||
addMany: (entries: IActivityEntry[]) => void;
|
||||
/** Clear all entries */
|
||||
clear: () => void;
|
||||
/** Get all entries */
|
||||
getEntries: () => IActivityEntry[];
|
||||
/** Filter entries */
|
||||
filter: (criteria: { user?: string; type?: IActivityEntry['type'] }) => IActivityEntry[];
|
||||
/** Search entries by message */
|
||||
search: (query: string) => IActivityEntry[];
|
||||
}
|
||||
|
||||
/**
|
||||
* View activation context passed to onActivate lifecycle hook
|
||||
*/
|
||||
export interface IViewActivationContext {
|
||||
/** Reference to the DeesAppui instance */
|
||||
appui: TDeesAppui;
|
||||
/** The view ID being activated */
|
||||
viewId: string;
|
||||
/** Route parameters if any */
|
||||
params?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* View lifecycle hooks interface
|
||||
* Views can implement these methods to receive lifecycle notifications
|
||||
*/
|
||||
export interface IViewLifecycle {
|
||||
/** Called when view is activated (displayed) */
|
||||
onActivate?: (context: IViewActivationContext) => void | Promise<void>;
|
||||
/** Called when view is deactivated (hidden) */
|
||||
onDeactivate?: () => void | Promise<void>;
|
||||
/** Called before navigation away - return false or message to block */
|
||||
canDeactivate?: () => boolean | string | Promise<boolean | string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* View definition for the view registry
|
||||
*/
|
||||
export interface IViewDefinition {
|
||||
/** Unique identifier for routing */
|
||||
id: string;
|
||||
/** Display name */
|
||||
name: string;
|
||||
/** Optional icon */
|
||||
iconName?: string;
|
||||
/**
|
||||
* The view content - can be:
|
||||
* - Tag name string (e.g., 'my-dashboard')
|
||||
* - Element class constructor
|
||||
* - Template function returning TemplateResult
|
||||
* - Async function returning any of the above (for lazy loading)
|
||||
*/
|
||||
content:
|
||||
| string
|
||||
| (new () => HTMLElement)
|
||||
| (() => TemplateResult)
|
||||
| (() => Promise<string | (new () => HTMLElement) | (() => TemplateResult)>);
|
||||
/** Secondary menu items specific to this view */
|
||||
secondaryMenu?: ISecondaryMenuGroup[];
|
||||
/** Content tabs specific to this view */
|
||||
contentTabs?: IMenuItem[];
|
||||
/** Optional route path (defaults to id). Supports params like 'settings/:section' */
|
||||
route?: string;
|
||||
/** Badge to show on menu item */
|
||||
badge?: string | number;
|
||||
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
|
||||
/** Whether to cache this view instance (default: true) */
|
||||
cache?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main menu section with view references
|
||||
*/
|
||||
export interface IMainMenuSection {
|
||||
/** Section name (optional for ungrouped) */
|
||||
name?: string;
|
||||
/** Views in this section (by ID reference) */
|
||||
views: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Main menu configuration
|
||||
*/
|
||||
export interface IMainMenuConfig {
|
||||
/** Logo icon */
|
||||
logoIcon?: string;
|
||||
/** Logo text */
|
||||
logoText?: string;
|
||||
/** Menu groups with tabs */
|
||||
groups?: IMenuGroup[];
|
||||
/** Menu sections with view references (alternative to groups) */
|
||||
sections?: IMainMenuSection[];
|
||||
/** Bottom pinned items (view IDs or tabs) */
|
||||
bottomItems?: string[];
|
||||
/** Bottom tabs */
|
||||
bottomTabs?: IMenuItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* App bar configuration
|
||||
*/
|
||||
export interface IAppBarConfig {
|
||||
menuItems?: IAppBarMenuItem[];
|
||||
breadcrumbs?: string;
|
||||
breadcrumbSeparator?: string;
|
||||
showWindowControls?: boolean;
|
||||
showSearch?: boolean;
|
||||
user?: IAppUser;
|
||||
profileMenuItems?: IAppBarMenuItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Branding configuration
|
||||
*/
|
||||
export interface IBrandingConfig {
|
||||
logoIcon?: string;
|
||||
logoText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity log configuration
|
||||
*/
|
||||
export interface IActivityLogConfig {
|
||||
/** Whether activity log is visible */
|
||||
visible?: boolean;
|
||||
/** Width of activity log panel */
|
||||
width?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main unified configuration interface for dees-appui
|
||||
*/
|
||||
export interface IAppConfig {
|
||||
/** Application branding */
|
||||
branding?: IBrandingConfig;
|
||||
|
||||
/** App bar configuration */
|
||||
appBar?: IAppBarConfig;
|
||||
|
||||
/** View definitions (the registry) */
|
||||
views: IViewDefinition[];
|
||||
|
||||
/** Main menu structure */
|
||||
mainMenu?: IMainMenuConfig;
|
||||
|
||||
/** Default view ID to show on startup */
|
||||
defaultView?: string;
|
||||
|
||||
/** Activity log configuration */
|
||||
activityLog?: IActivityLogConfig;
|
||||
|
||||
/** Bottom bar configuration */
|
||||
bottomBar?: IBottomBarConfig;
|
||||
|
||||
/** Event callbacks */
|
||||
onViewChange?: (viewId: string, view: IViewDefinition) => void;
|
||||
onSearch?: (query: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* View change event detail
|
||||
*/
|
||||
export interface IViewChangeEvent {
|
||||
viewId: string;
|
||||
view: IViewDefinition;
|
||||
previousView?: IViewDefinition;
|
||||
params?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* View lifecycle event (for rxjs Subject)
|
||||
*/
|
||||
export interface IViewLifecycleEvent {
|
||||
type: 'activated' | 'deactivated' | 'loading' | 'loaded' | 'loadError';
|
||||
viewId: string;
|
||||
element?: HTMLElement;
|
||||
params?: Record<string, string>;
|
||||
error?: unknown;
|
||||
}
|
||||
5
ts_web/elements/interfaces/index.ts
Normal file
5
ts_web/elements/interfaces/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './tab.js';
|
||||
export * from './appbarmenuitem.js';
|
||||
export * from './menugroup.js';
|
||||
export * from './appconfig.js';
|
||||
export * from './secondarymenu.js';
|
||||
8
ts_web/elements/interfaces/menugroup.ts
Normal file
8
ts_web/elements/interfaces/menugroup.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { IMenuItem } from './tab.js';
|
||||
|
||||
export interface IMenuGroup {
|
||||
name: string;
|
||||
items: IMenuItem[];
|
||||
collapsed?: boolean;
|
||||
iconName?: string;
|
||||
}
|
||||
93
ts_web/elements/interfaces/secondarymenu.ts
Normal file
93
ts_web/elements/interfaces/secondarymenu.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Secondary Menu Item Types
|
||||
*
|
||||
* Supports 8 item types:
|
||||
* 1. Tab - selectable, stays highlighted (existing behavior)
|
||||
* 2. Action - executes without selection (primary = blue)
|
||||
* 3. Danger Action - red styling with optional confirmation
|
||||
* 4. Filter - checkbox toggle, emits immediately
|
||||
* 5. Multi-Filter - collapsible box with multiple checkboxes
|
||||
* 6. Divider - visual separator
|
||||
* 7. Header - non-interactive label
|
||||
* 8. Link - opens URL
|
||||
*/
|
||||
|
||||
// Base properties shared by interactive items
|
||||
export interface ISecondaryMenuItemBase {
|
||||
key: string;
|
||||
iconName?: string;
|
||||
disabled?: boolean;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
// 1. Tab - existing behavior (selectable, stays highlighted)
|
||||
export interface ISecondaryMenuItemTab extends ISecondaryMenuItemBase {
|
||||
type?: 'tab'; // default if omitted for backward compatibility
|
||||
action: () => void;
|
||||
badge?: string | number;
|
||||
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
|
||||
}
|
||||
|
||||
// 2 & 3. Action - executes without selection
|
||||
export interface ISecondaryMenuItemAction extends ISecondaryMenuItemBase {
|
||||
type: 'action';
|
||||
action: () => void | Promise<void>;
|
||||
variant?: 'primary' | 'danger'; // primary = blue (default), danger = red
|
||||
confirmMessage?: string; // Shows confirmation dialog before executing
|
||||
}
|
||||
|
||||
// 4. Single filter toggle
|
||||
export interface ISecondaryMenuItemFilter extends ISecondaryMenuItemBase {
|
||||
type: 'filter';
|
||||
active: boolean;
|
||||
onToggle: (active: boolean) => void;
|
||||
}
|
||||
|
||||
// 5. Multi-select filter group (collapsible)
|
||||
export interface ISecondaryMenuItemMultiFilter extends ISecondaryMenuItemBase {
|
||||
type: 'multiFilter';
|
||||
collapsed?: boolean; // Accordion state
|
||||
options: Array<{
|
||||
key: string;
|
||||
label: string;
|
||||
checked: boolean;
|
||||
iconName?: string;
|
||||
}>;
|
||||
onChange: (selectedKeys: string[]) => void;
|
||||
}
|
||||
|
||||
// 6. Divider
|
||||
export interface ISecondaryMenuItemDivider {
|
||||
type: 'divider';
|
||||
}
|
||||
|
||||
// 7. Header/Label
|
||||
export interface ISecondaryMenuItemHeader {
|
||||
type: 'header';
|
||||
label: string;
|
||||
}
|
||||
|
||||
// 8. External link
|
||||
export interface ISecondaryMenuItemLink extends ISecondaryMenuItemBase {
|
||||
type: 'link';
|
||||
href: string;
|
||||
external?: boolean; // Opens in new tab (default: true if href starts with http)
|
||||
}
|
||||
|
||||
// Union type for all secondary menu items
|
||||
export type ISecondaryMenuItem =
|
||||
| ISecondaryMenuItemTab
|
||||
| ISecondaryMenuItemAction
|
||||
| ISecondaryMenuItemFilter
|
||||
| ISecondaryMenuItemMultiFilter
|
||||
| ISecondaryMenuItemDivider
|
||||
| ISecondaryMenuItemHeader
|
||||
| ISecondaryMenuItemLink;
|
||||
|
||||
// Group interface for secondary menu
|
||||
export interface ISecondaryMenuGroup {
|
||||
name: string;
|
||||
iconName?: string;
|
||||
collapsed?: boolean;
|
||||
items: ISecondaryMenuItem[];
|
||||
}
|
||||
9
ts_web/elements/interfaces/tab.ts
Normal file
9
ts_web/elements/interfaces/tab.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export interface IMenuItem {
|
||||
key: string;
|
||||
iconName?: string;
|
||||
action: () => void;
|
||||
badge?: string | number;
|
||||
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
|
||||
closeable?: boolean;
|
||||
onClose?: () => void;
|
||||
}
|
||||
4
ts_web/index.ts
Normal file
4
ts_web/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './elements/index.js';
|
||||
import * as colors from './elements/00colors.js';
|
||||
export { colors };
|
||||
export { commitinfo } from './00_commitinfo_data.js';
|
||||
2
ts_web/pages/index.ts
Normal file
2
ts_web/pages/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './mainpage.js';
|
||||
export * from './input-showcase.js';
|
||||
668
ts_web/pages/input-showcase.ts
Normal file
668
ts_web/pages/input-showcase.ts
Normal file
@@ -0,0 +1,668 @@
|
||||
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||
import '../elements/index.js';
|
||||
|
||||
export const inputShowcase = () => html`
|
||||
<div class="page-wrapper">
|
||||
<style>
|
||||
${css`
|
||||
.page-wrapper {
|
||||
display: block;
|
||||
background: ${cssManager.bdTheme('#f5f5f5', '#0a0a0a')};
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.showcase-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 48px 24px;
|
||||
}
|
||||
|
||||
|
||||
.showcase-header {
|
||||
text-align: center;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.showcase-title {
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 16px 0;
|
||||
color: ${cssManager.bdTheme('#1a1a1a', '#ffffff')};
|
||||
}
|
||||
|
||||
.showcase-subtitle {
|
||||
font-size: 20px;
|
||||
color: ${cssManager.bdTheme('#666', '#999')};
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.showcase-section {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.showcase-section:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 48px;
|
||||
}
|
||||
|
||||
/* Ensure all headings are theme-aware */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: ${cssManager.bdTheme('#1a1a1a', '#ffffff')};
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: ${cssManager.bdTheme('#666', '#999')};
|
||||
}
|
||||
|
||||
strong {
|
||||
color: ${cssManager.bdTheme('#333', '#e0e0e0')};
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
background: ${cssManager.bdTheme('#e3f2fd', '#1e3a5f')};
|
||||
}
|
||||
|
||||
.section-icon.text { background: ${cssManager.bdTheme('#e3f2fd', '#1e3a5f')}; }
|
||||
.section-icon.selection { background: ${cssManager.bdTheme('#f3e5f5', '#4a148c')}; }
|
||||
.section-icon.numeric { background: ${cssManager.bdTheme('#e8f5e9', '#1b5e20')}; }
|
||||
.section-icon.special { background: ${cssManager.bdTheme('#fff3e0', '#e65100')}; }
|
||||
.section-icon.rich { background: ${cssManager.bdTheme('#fce4ec', '#880e4f')}; }
|
||||
.section-icon.form { background: ${cssManager.bdTheme('#e0f2f1', '#004d40')}; }
|
||||
|
||||
.section-title {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: ${cssManager.bdTheme('#1a1a1a', '#ffffff')};
|
||||
}
|
||||
|
||||
.section-description {
|
||||
color: ${cssManager.bdTheme('#666', '#999')};
|
||||
margin: 0 0 32px 64px;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.demo-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
position: sticky;
|
||||
top: 24px;
|
||||
float: right;
|
||||
margin-left: 24px;
|
||||
margin-bottom: 24px;
|
||||
background: ${cssManager.bdTheme('white', '#1a1a1a')};
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 4px 12px ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(0, 0, 0, 0.5)')};
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.nav-menu {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: block;
|
||||
padding: 8px 12px;
|
||||
color: ${cssManager.bdTheme('#666', '#999')};
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: ${cssManager.bdTheme('#f0f0f0', '#2a2a2a')};
|
||||
color: ${cssManager.bdTheme('#1a1a1a', '#ffffff')};
|
||||
}
|
||||
|
||||
dees-form {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
dees-panel {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
dees-panel:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.code-example {
|
||||
background: ${cssManager.bdTheme('#f5f5f5', '#1a1a1a')};
|
||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-top: 16px;
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-size: 14px;
|
||||
overflow-x: auto;
|
||||
color: ${cssManager.bdTheme('#333', '#e0e0e0')};
|
||||
}
|
||||
|
||||
.feature-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
background: ${cssManager.bdTheme('#e3f2fd', '#1e3a5f')};
|
||||
color: ${cssManager.bdTheme('#1976d2', '#64b5f6')};
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* Form section specific styles */
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.feature-card p {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
`}
|
||||
</style>
|
||||
|
||||
<div class="showcase-container">
|
||||
<!-- Navigation Menu -->
|
||||
<nav class="nav-menu">
|
||||
<a href="#text-inputs" class="nav-item">📝 Text Inputs</a>
|
||||
<a href="#selection-inputs" class="nav-item">☑️ Selection Inputs</a>
|
||||
<a href="#numeric-inputs" class="nav-item">🔢 Numeric Inputs</a>
|
||||
<a href="#special-inputs" class="nav-item">✨ Special Inputs</a>
|
||||
<a href="#rich-editors" class="nav-item">📄 Rich Editors</a>
|
||||
<a href="#form-integration" class="nav-item">📋 Form Integration</a>
|
||||
</nav>
|
||||
|
||||
<div class="showcase-header">
|
||||
<h1 class="showcase-title">Input Components Showcase</h1>
|
||||
<p class="showcase-subtitle">
|
||||
A comprehensive collection of input components for building modern web forms and interfaces.
|
||||
<br>All components support dark mode, validation, and integrate seamlessly with dees-form.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Text Inputs Section -->
|
||||
<section id="text-inputs" class="showcase-section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon text">📝</div>
|
||||
<h2 class="section-title">Text Inputs</h2>
|
||||
</div>
|
||||
<p class="section-description">
|
||||
Standard text input components for collecting various types of textual data.
|
||||
Includes password fields, validation, and specialized formatting.
|
||||
</p>
|
||||
|
||||
<dees-panel .title=${'Basic Text Inputs'}>
|
||||
<div class="demo-grid">
|
||||
<dees-input-text
|
||||
.label=${'Username'}
|
||||
.placeholder=${'Enter your username'}
|
||||
.description=${'Choose a unique username'}
|
||||
.required=${true}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-text
|
||||
.label=${'Email Address'}
|
||||
.inputType=${'email'}
|
||||
.placeholder=${'user@example.com'}
|
||||
.validationText=${'Please enter a valid email'}
|
||||
.required=${true}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-text
|
||||
.label=${'Password'}
|
||||
.isPasswordBool=${true}
|
||||
.placeholder=${'Enter secure password'}
|
||||
.description=${'Must be at least 8 characters'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-text
|
||||
.label=${'Website URL'}
|
||||
.inputType=${'url'}
|
||||
.placeholder=${'https://example.com'}
|
||||
.value=${'https://design.estate'}
|
||||
></dees-input-text>
|
||||
</div>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Search Bar'} .subtitle=${'Advanced search with suggestions'}>
|
||||
<dees-searchbar
|
||||
.placeholder=${'Search for anything...'}
|
||||
></dees-searchbar>
|
||||
|
||||
<div class="code-example">
|
||||
// Search with custom suggestions
|
||||
<dees-searchbar
|
||||
.placeholder="Search products..."
|
||||
.suggestions=${['Laptop', 'Phone', 'Tablet']}
|
||||
></dees-searchbar>
|
||||
</div>
|
||||
</dees-panel>
|
||||
</section>
|
||||
|
||||
<!-- Selection Inputs Section -->
|
||||
<section id="selection-inputs" class="showcase-section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon selection">☑️</div>
|
||||
<h2 class="section-title">Selection Inputs</h2>
|
||||
</div>
|
||||
<p class="section-description">
|
||||
Components for selecting from predefined options. Includes checkboxes, radio buttons,
|
||||
dropdowns, and multi-select controls.
|
||||
</p>
|
||||
|
||||
<dees-panel .title=${'Checkboxes and Radio Buttons'}>
|
||||
<div class="demo-grid">
|
||||
<div>
|
||||
<dees-input-checkbox
|
||||
.label=${'Accept Terms & Conditions'}
|
||||
.description=${'You must accept to continue'}
|
||||
.required=${true}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Subscribe to Newsletter'}
|
||||
.value=${true}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Enable Notifications'}
|
||||
.disabled=${true}
|
||||
></dees-input-checkbox>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dees-input-radiogroup
|
||||
.label=${'Select Plan'}
|
||||
.options=${['Free', 'Pro', 'Enterprise']}
|
||||
.selectedOption=${'Pro'}
|
||||
.required=${true}
|
||||
></dees-input-radiogroup>
|
||||
</div>
|
||||
</div>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Dropdown Selection'}>
|
||||
<div class="demo-grid">
|
||||
<dees-input-dropdown
|
||||
.label=${'Country'}
|
||||
.options=${[
|
||||
{option: 'United States', key: 'us', payload: 'US'},
|
||||
{option: 'Canada', key: 'ca', payload: 'CA'},
|
||||
{option: 'United Kingdom', key: 'uk', payload: 'UK'},
|
||||
{option: 'Germany', key: 'de', payload: 'DE'},
|
||||
{option: 'France', key: 'fr', payload: 'FR'},
|
||||
{option: 'Japan', key: 'jp', payload: 'JP'}
|
||||
]}
|
||||
.placeholder=${'Select your country'}
|
||||
.required=${true}
|
||||
></dees-input-dropdown>
|
||||
|
||||
<dees-input-dropdown
|
||||
.label=${'Preferred Language'}
|
||||
.options=${[
|
||||
{option: 'English', key: 'en', payload: 'EN'},
|
||||
{option: 'Spanish', key: 'es', payload: 'ES'},
|
||||
{option: 'French', key: 'fr', payload: 'FR'},
|
||||
{option: 'German', key: 'de', payload: 'DE'},
|
||||
{option: 'Japanese', key: 'ja', payload: 'JA'}
|
||||
]}
|
||||
.value=${{option: 'English', key: 'en', payload: 'EN'}}
|
||||
></dees-input-dropdown>
|
||||
</div>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Multi Toggle'} .subtitle=${'Toggle between multiple options'}>
|
||||
<dees-input-multitoggle
|
||||
.label=${'Theme Preference'}
|
||||
.options=${['Light', 'Dark', 'Auto']}
|
||||
.value=${'Auto'}
|
||||
></dees-input-multitoggle>
|
||||
|
||||
<dees-input-multitoggle
|
||||
.label=${'View Mode'}
|
||||
.options=${['Grid', 'List', 'Cards']}
|
||||
.value=${'Grid'}
|
||||
.description=${'Choose how to display items'}
|
||||
></dees-input-multitoggle>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Type List'} .subtitle=${'Dynamic list of typed items'}>
|
||||
<dees-input-typelist
|
||||
.label=${'Skills'}
|
||||
.description=${'Add your technical skills'}
|
||||
.placeholder=${'Type and press Enter'}
|
||||
></dees-input-typelist>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Tags Input'} .subtitle=${'Add and manage tags with suggestions'}>
|
||||
<div class="demo-grid">
|
||||
<dees-input-tags
|
||||
.label=${'Project Tags'}
|
||||
.placeholder=${'Add tags...'}
|
||||
.value=${['frontend', 'typescript', 'webcomponents']}
|
||||
.description=${'Press Enter or comma to add'}
|
||||
></dees-input-tags>
|
||||
|
||||
<dees-input-tags
|
||||
.label=${'Technologies'}
|
||||
.placeholder=${'Type to see suggestions...'}
|
||||
.suggestions=${[
|
||||
'React', 'Vue', 'Angular', 'Svelte',
|
||||
'Node.js', 'Deno', 'Express', 'MongoDB'
|
||||
]}
|
||||
.value=${['React', 'Node.js']}
|
||||
.maxTags=${5}
|
||||
.description=${'Maximum 5 tags, with suggestions'}
|
||||
></dees-input-tags>
|
||||
</div>
|
||||
</dees-panel>
|
||||
</section>
|
||||
|
||||
<!-- Numeric Inputs Section -->
|
||||
<section id="numeric-inputs" class="showcase-section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon numeric">🔢</div>
|
||||
<h2 class="section-title">Numeric Inputs</h2>
|
||||
</div>
|
||||
<p class="section-description">
|
||||
Specialized inputs for numeric values, including quantity selectors and formatted inputs.
|
||||
</p>
|
||||
|
||||
<dees-panel .title=${'Quantity Selector'}>
|
||||
<div class="demo-grid">
|
||||
<dees-input-quantityselector
|
||||
.label=${'Product Quantity'}
|
||||
.value=${1}
|
||||
.min=${1}
|
||||
.max=${100}
|
||||
.description=${'Select quantity (1-100)'}
|
||||
></dees-input-quantityselector>
|
||||
|
||||
<dees-input-quantityselector
|
||||
.label=${'Team Size'}
|
||||
.value=${5}
|
||||
.min=${1}
|
||||
.max=${50}
|
||||
.step=${5}
|
||||
></dees-input-quantityselector>
|
||||
</div>
|
||||
</dees-panel>
|
||||
</section>
|
||||
|
||||
<!-- Special Inputs Section -->
|
||||
<section id="special-inputs" class="showcase-section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon special">✨</div>
|
||||
<h2 class="section-title">Special Inputs</h2>
|
||||
</div>
|
||||
<p class="section-description">
|
||||
Specialized input components for specific data types like phone numbers, IBAN, and file uploads.
|
||||
</p>
|
||||
|
||||
<dees-panel .title=${'Date & Time Picker'} .subtitle=${'Calendar-based date selection'}>
|
||||
<div class="demo-grid">
|
||||
<dees-input-datepicker
|
||||
.label=${'Event Date'}
|
||||
.placeholder=${'Select date'}
|
||||
.description=${'Choose a date from the calendar'}
|
||||
></dees-input-datepicker>
|
||||
|
||||
<dees-input-datepicker
|
||||
.label=${'Appointment Time'}
|
||||
.enableTime=${true}
|
||||
.timeFormat=${'12h'}
|
||||
.description=${'Date and time with AM/PM'}
|
||||
></dees-input-datepicker>
|
||||
|
||||
<dees-input-datepicker
|
||||
.label=${'Deadline'}
|
||||
.enableTime=${true}
|
||||
.timeFormat=${'24h'}
|
||||
.minuteIncrement=${15}
|
||||
.minDate=${new Date().toISOString()}
|
||||
.description=${'Future dates only, 15 min increments'}
|
||||
></dees-input-datepicker>
|
||||
</div>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Phone & IBAN'}>
|
||||
<div class="demo-grid">
|
||||
<dees-input-phone
|
||||
.label=${'Phone Number'}
|
||||
.placeholder=${'+1 (555) 123-4567'}
|
||||
.required=${true}
|
||||
.description=${'International format supported'}
|
||||
></dees-input-phone>
|
||||
|
||||
<dees-input-iban
|
||||
.label=${'Bank Account (IBAN)'}
|
||||
.placeholder=${'DE89 3704 0044 0532 0130 00'}
|
||||
.description=${'European IBAN format'}
|
||||
></dees-input-iban>
|
||||
</div>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'File Upload'} .subtitle=${'Drag & drop or click to upload'}>
|
||||
<dees-input-fileupload
|
||||
.label=${'Upload Documents'}
|
||||
.description=${'PDF, DOC, DOCX up to 10MB'}
|
||||
.accept=${'.pdf,.doc,.docx'}
|
||||
.multiple=${true}
|
||||
></dees-input-fileupload>
|
||||
|
||||
<dees-input-fileupload
|
||||
.label=${'Profile Picture'}
|
||||
.description=${'JPG, PNG up to 5MB'}
|
||||
.accept=${'image/*'}
|
||||
></dees-input-fileupload>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Profile Picture Input'} .subtitle=${'Image upload with cropping'}>
|
||||
<div class="demo-grid">
|
||||
<dees-input-profilepicture
|
||||
.label=${'User Avatar'}
|
||||
.description=${'Round profile picture'}
|
||||
.shape=${'round'}
|
||||
.size=${120}
|
||||
></dees-input-profilepicture>
|
||||
|
||||
<dees-input-profilepicture
|
||||
.label=${'Company Logo'}
|
||||
.description=${'Square format'}
|
||||
.shape=${'square'}
|
||||
.size=${120}
|
||||
></dees-input-profilepicture>
|
||||
|
||||
<dees-input-profilepicture
|
||||
.label=${'Team Member'}
|
||||
.description=${'Larger profile image'}
|
||||
.shape=${'round'}
|
||||
.size=${150}
|
||||
></dees-input-profilepicture>
|
||||
</div>
|
||||
</dees-panel>
|
||||
</section>
|
||||
|
||||
<!-- Rich Editors Section -->
|
||||
<section id="rich-editors" class="showcase-section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon rich">📄</div>
|
||||
<h2 class="section-title">Rich Text Editors</h2>
|
||||
<span class="feature-badge">New!</span>
|
||||
</div>
|
||||
<p class="section-description">
|
||||
Advanced text editors for creating rich content with formatting, images, and structured blocks.
|
||||
</p>
|
||||
|
||||
<dees-panel .title=${'Rich Text Editor'} .subtitle=${'TipTap-based rich text editing'}>
|
||||
<dees-input-richtext
|
||||
.label=${'Article Content'}
|
||||
.placeholder=${'Start writing...'}
|
||||
.description=${'Full formatting toolbar with markdown shortcuts'}
|
||||
.minHeight=${300}
|
||||
.showWordCount=${true}
|
||||
></dees-input-richtext>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'WYSIWYG Block Editor'} .subtitle=${'Block-based editor with slash commands'}>
|
||||
<dees-input-wysiwyg
|
||||
.label=${'Page Content'}
|
||||
.description=${'Type "/" for commands or use markdown shortcuts'}
|
||||
.outputFormat=${'html'}
|
||||
></dees-input-wysiwyg>
|
||||
</dees-panel>
|
||||
</section>
|
||||
|
||||
<!-- Form Integration Section -->
|
||||
<section id="form-integration" class="showcase-section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon form">📋</div>
|
||||
<h2 class="section-title">Form Integration</h2>
|
||||
</div>
|
||||
<p class="section-description">
|
||||
All input components integrate seamlessly with dees-form for validation,
|
||||
submission handling, and data management.
|
||||
</p>
|
||||
|
||||
<dees-panel .title=${'Complete Form Example'} .subtitle=${'All inputs working together'}>
|
||||
<dees-form>
|
||||
<h3>User Registration</h3>
|
||||
|
||||
<div class="demo-grid">
|
||||
<dees-input-text
|
||||
.label=${'First Name'}
|
||||
.required=${true}
|
||||
.key=${'firstName'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-text
|
||||
.label=${'Last Name'}
|
||||
.required=${true}
|
||||
.key=${'lastName'}
|
||||
></dees-input-text>
|
||||
</div>
|
||||
|
||||
<dees-input-text
|
||||
.label=${'Email'}
|
||||
.inputType=${'email'}
|
||||
.required=${true}
|
||||
.key=${'email'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-phone
|
||||
.label=${'Phone Number'}
|
||||
.required=${true}
|
||||
.key=${'phone'}
|
||||
></dees-input-phone>
|
||||
|
||||
<dees-input-dropdown
|
||||
.label=${'Country'}
|
||||
.options=${[
|
||||
{option: 'United States', key: 'us', payload: 'US'},
|
||||
{option: 'Canada', key: 'ca', payload: 'CA'},
|
||||
{option: 'United Kingdom', key: 'uk', payload: 'UK'},
|
||||
{option: 'Germany', key: 'de', payload: 'DE'},
|
||||
{option: 'France', key: 'fr', payload: 'FR'}
|
||||
]}
|
||||
.required=${true}
|
||||
.key=${'country'}
|
||||
></dees-input-dropdown>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Account Type'}
|
||||
.options=${['Personal', 'Business']}
|
||||
.required=${true}
|
||||
.key=${'accountType'}
|
||||
.selectedOption=${'Personal'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-richtext
|
||||
.label=${'Bio'}
|
||||
.placeholder=${'Tell us about yourself...'}
|
||||
.minHeight=${150}
|
||||
.key=${'bio'}
|
||||
></dees-input-richtext>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'I agree to the Terms of Service'}
|
||||
.required=${true}
|
||||
.key=${'terms'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Subscribe to newsletter'}
|
||||
.key=${'newsletter'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-form-submit .text=${'Create Account'}></dees-form-submit>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Form Features'}>
|
||||
<div class="feature-grid">
|
||||
<div class="feature-card" style="background: rgba(0, 150, 136, 0.1);">
|
||||
<strong>✅ Validation</strong>
|
||||
<p>Built-in validation for all input types</p>
|
||||
</div>
|
||||
<div class="feature-card" style="background: rgba(33, 150, 243, 0.1);">
|
||||
<strong>🔄 Two-way Binding</strong>
|
||||
<p>Automatic data synchronization</p>
|
||||
</div>
|
||||
<div class="feature-card" style="background: rgba(156, 39, 176, 0.1);">
|
||||
<strong>📊 Data Collection</strong>
|
||||
<p>Easy form data extraction</p>
|
||||
</div>
|
||||
<div class="feature-card" style="background: rgba(255, 152, 0, 0.1);">
|
||||
<strong>🎨 Theming</strong>
|
||||
<p>Consistent styling across all inputs</p>
|
||||
</div>
|
||||
</div>
|
||||
</dees-panel>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
5
ts_web/pages/mainpage.ts
Normal file
5
ts_web/pages/mainpage.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { html } from '@design.estate/dees-element';
|
||||
|
||||
export const mainPage = () => html`
|
||||
<div>hello</div>
|
||||
`;
|
||||
Reference in New Issue
Block a user