This commit is contained in:
2026-01-06 02:24:12 +00:00
commit edea37a856
64 changed files with 18007 additions and 0 deletions

View 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',
}

View 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;
`);

View 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
View 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;
}
`;

View 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;

View 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;
}
}

View File

@@ -0,0 +1 @@
export * from './dees-screensaver.js';

View File

@@ -0,0 +1,3 @@
export class FormController {
}

View File

@@ -0,0 +1 @@
export * from './formcontroller.js';

5
ts_web/elements/index.ts Normal file
View File

@@ -0,0 +1,5 @@
export * from './00zindex.js';
export * from './00theme.js';
// Standalone Components
export * from './dees-screensaver/index.js';

View 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;
}

View 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;
}

View 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';

View File

@@ -0,0 +1,8 @@
import type { IMenuItem } from './tab.js';
export interface IMenuGroup {
name: string;
items: IMenuItem[];
collapsed?: boolean;
iconName?: string;
}

View 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[];
}

View 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;
}