feat(wcctools): Add section-based configuration API for setupWccTools, new Views, and section-aware routing/sidebar
This commit is contained in:
@@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-12-28 - 3.3.0 - feat(wcctools)
|
||||||
|
Add section-based configuration API for setupWccTools, new Views, and section-aware routing/sidebar
|
||||||
|
|
||||||
|
- Introduce IWccSection and IWccConfig types and migrate setupWccTools to accept a sections config while preserving legacy (elements, pages) format
|
||||||
|
- WccDashboard and WccSidebar updated to support sections, filtering, sorting, collapsed sections, and section-aware URL routing (uses sectionName in routes with legacy fallbacks)
|
||||||
|
- Add Views: view-dashboard, view-settings, view-empty-state plus test/views index exports and demo variations
|
||||||
|
- Add helpers: getSectionItems, convertLegacyToConfig and isWccConfig; update build URL and routing logic to be section-aware
|
||||||
|
- Update docs and README/readme.hints with sections API, examples, migration notes and UI/UX updates
|
||||||
|
|
||||||
## 2025-12-22 - 3.2.0 - feat(wcc-sidebar)
|
## 2025-12-22 - 3.2.0 - feat(wcc-sidebar)
|
||||||
auto-expand sidebar folder when selecting an element with multiple demos
|
auto-expand sidebar folder when selecting an element with multiple demos
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,32 @@
|
|||||||
import * as deesWccTools from '../ts_web/index.js';
|
import * as deesWccTools from '../ts_web/index.js';
|
||||||
import * as deesDomTools from '@design.estate/dees-domtools';
|
import * as deesDomTools from '@design.estate/dees-domtools';
|
||||||
|
|
||||||
// elements and pages
|
// elements, views and pages
|
||||||
import * as elements from '../test/elements/index.js';
|
import * as elements from '../test/elements/index.js';
|
||||||
|
import * as views from '../test/views/index.js';
|
||||||
import * as pages from '../test/pages/index.js';
|
import * as pages from '../test/pages/index.js';
|
||||||
|
|
||||||
deesWccTools.setupWccTools(elements as any, pages);
|
// Sections-based API with Views
|
||||||
|
deesWccTools.setupWccTools({
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
name: 'Pages',
|
||||||
|
type: 'pages',
|
||||||
|
items: pages,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Views',
|
||||||
|
type: 'elements',
|
||||||
|
items: views,
|
||||||
|
icon: 'web',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Elements',
|
||||||
|
type: 'elements',
|
||||||
|
items: elements,
|
||||||
|
sort: ([a], [b]) => a.localeCompare(b),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
deesDomTools.elementBasic.setup();
|
deesDomTools.elementBasic.setup();
|
||||||
|
|||||||
@@ -1,5 +1,67 @@
|
|||||||
# Project Hints and Findings
|
# Project Hints and Findings
|
||||||
|
|
||||||
|
## Section-based Configuration API (2025-12-27)
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
Refactored `setupWccTools` to accept a section-based configuration object instead of separate elements/pages arguments. This allows multiple custom sections with filtering, sorting, and collapsible headers.
|
||||||
|
|
||||||
|
### New API
|
||||||
|
```typescript
|
||||||
|
import * as deesWccTools from '@design.estate/dees-wcctools';
|
||||||
|
|
||||||
|
deesWccTools.setupWccTools({
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
name: 'Pages',
|
||||||
|
type: 'pages',
|
||||||
|
items: pages,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Elements',
|
||||||
|
type: 'elements',
|
||||||
|
items: elements,
|
||||||
|
filter: (name, item) => !name.startsWith('internal-'),
|
||||||
|
sort: ([a], [b]) => a.localeCompare(b),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Views',
|
||||||
|
type: 'elements',
|
||||||
|
items: elements,
|
||||||
|
filter: (name, item) => name.startsWith('view-'),
|
||||||
|
icon: 'web',
|
||||||
|
collapsed: true, // Start collapsed
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Section Properties
|
||||||
|
- `name`: Display name for the section header
|
||||||
|
- `type`: `'elements'` (shows demos) or `'pages'` (renders directly)
|
||||||
|
- `items`: Record of items (element classes or page factories)
|
||||||
|
- `filter`: Optional `(name, item) => boolean` to filter items
|
||||||
|
- `sort`: Optional `([name, item], [name, item]) => number` for ordering
|
||||||
|
- `icon`: Optional Material icon name for section header
|
||||||
|
- `collapsed`: Optional boolean to start section collapsed
|
||||||
|
|
||||||
|
### Backwards Compatibility
|
||||||
|
Legacy format is still supported:
|
||||||
|
```typescript
|
||||||
|
deesWccTools.setupWccTools(elements, pages); // Still works
|
||||||
|
```
|
||||||
|
|
||||||
|
### URL Routing
|
||||||
|
Changed from `/wcctools-route/:itemType/:itemName/...` to `/wcctools-route/:sectionName/:itemName/...`
|
||||||
|
Section names are URL-encoded. Legacy routes (`element`/`page` as section name) still work for backwards compatibility.
|
||||||
|
|
||||||
|
### Files Changed
|
||||||
|
- `ts_web/wcctools.interfaces.ts` - New types: `IWccSection`, `IWccConfig`
|
||||||
|
- `ts_web/index.ts` - Updated `setupWccTools` with backwards compat detection
|
||||||
|
- `ts_web/elements/wcc-dashboard.ts` - Uses sections, updated routing
|
||||||
|
- `ts_web/elements/wcc-sidebar.ts` - Dynamic section rendering
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## UI Redesign with Shadcn-like Styles (2025-06-27)
|
## UI Redesign with Shadcn-like Styles (2025-06-27)
|
||||||
|
|
||||||
### Changes Made
|
### Changes Made
|
||||||
|
|||||||
229
readme.md
229
readme.md
@@ -6,12 +6,13 @@
|
|||||||
|
|
||||||
`@design.estate/dees-wcctools` provides a comprehensive development environment for web components, featuring:
|
`@design.estate/dees-wcctools` provides a comprehensive development environment for web components, featuring:
|
||||||
|
|
||||||
- 🎨 **Interactive Component Catalogue** — Live preview with sidebar navigation
|
- 🎨 **Interactive Component Catalogue** — Live preview with customizable sidebar sections
|
||||||
- 🔧 **Real-time Property Editing** — Modify component props on the fly with auto-detected editors
|
- 🔧 **Real-time Property Editing** — Modify component props on the fly with auto-detected editors
|
||||||
- 🌓 **Theme Switching** — Test light/dark modes instantly
|
- 🌓 **Theme Switching** — Test light/dark modes instantly
|
||||||
- 📱 **Responsive Viewport Testing** — Phone, phablet, tablet, and desktop views
|
- 📱 **Responsive Viewport Testing** — Phone, phablet, tablet, and desktop views
|
||||||
- 🎬 **Screen Recording** — Record component demos with audio support and video trimming
|
- 🎬 **Screen Recording** — Record component demos with audio support and video trimming
|
||||||
- 🧪 **Advanced Demo Tools** — Post-render hooks for interactive testing
|
- 🧪 **Advanced Demo Tools** — Post-render hooks for interactive testing
|
||||||
|
- 📂 **Section-based Organization** — Group components into custom sections with filtering and sorting
|
||||||
- 🚀 **Zero-config Setup** — TypeScript and Lit support out of the box
|
- 🚀 **Zero-config Setup** — TypeScript and Lit support out of the box
|
||||||
|
|
||||||
## Issue Reporting and Security
|
## Issue Reporting and Security
|
||||||
@@ -57,29 +58,22 @@ export class MyButton extends DeesElement {
|
|||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
}
|
||||||
button.primary {
|
button.primary {
|
||||||
background: #007bff;
|
background: #3b82f6;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
button.secondary {
|
button.secondary {
|
||||||
background: #6c757d;
|
background: #6b7280;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
button:hover {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
];
|
];
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return html`
|
return html`
|
||||||
<button class="${this.variant}">
|
<button class="${this.variant}">${this.label}</button>
|
||||||
${this.label}
|
|
||||||
</button>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,33 +87,32 @@ import { setupWccTools } from '@design.estate/dees-wcctools';
|
|||||||
import { html } from 'lit';
|
import { html } from 'lit';
|
||||||
|
|
||||||
// Import your components
|
// Import your components
|
||||||
import { MyButton } from './components/my-button.js';
|
import * as elements from './components/index.js';
|
||||||
import { MyCard } from './components/my-card.js';
|
import * as views from './views/index.js';
|
||||||
|
import * as pages from './pages/index.js';
|
||||||
|
|
||||||
// Define elements for the catalogue
|
// Initialize with sections-based configuration
|
||||||
const elements = {
|
setupWccTools({
|
||||||
'my-button': MyButton,
|
sections: [
|
||||||
'my-card': MyCard,
|
{
|
||||||
};
|
name: 'Pages',
|
||||||
|
type: 'pages',
|
||||||
// Optionally define pages
|
items: pages,
|
||||||
const pages = {
|
},
|
||||||
'home': () => html`
|
{
|
||||||
<div style="padding: 20px;">
|
name: 'Views',
|
||||||
<h1>Welcome to My Component Library</h1>
|
type: 'elements',
|
||||||
<p>Browse components using the sidebar.</p>
|
items: views,
|
||||||
</div>
|
icon: 'web',
|
||||||
`,
|
},
|
||||||
'getting-started': () => html`
|
{
|
||||||
<div style="padding: 20px;">
|
name: 'Elements',
|
||||||
<h2>Getting Started</h2>
|
type: 'elements',
|
||||||
<p>Installation and usage instructions...</p>
|
items: elements,
|
||||||
</div>
|
sort: ([a], [b]) => a.localeCompare(b),
|
||||||
`,
|
},
|
||||||
};
|
],
|
||||||
|
});
|
||||||
// Initialize the catalogue
|
|
||||||
setupWccTools(elements, pages);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Create an HTML Entry Point
|
### 3. Create an HTML Entry Point
|
||||||
@@ -137,6 +130,69 @@ setupWccTools(elements, pages);
|
|||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 📂 Sections Configuration
|
||||||
|
|
||||||
|
The sections-based API gives you full control over how components are organized in the sidebar.
|
||||||
|
|
||||||
|
### Section Properties
|
||||||
|
|
||||||
|
| Property | Type | Description |
|
||||||
|
|----------|------|-------------|
|
||||||
|
| `name` | `string` | Display name for the section header |
|
||||||
|
| `type` | `'elements' \| 'pages'` | How items render (`elements` show demos, `pages` render directly) |
|
||||||
|
| `items` | `Record<string, any>` | Object containing element classes or page factories |
|
||||||
|
| `filter` | `(name, item) => boolean` | Optional filter function to include/exclude items |
|
||||||
|
| `sort` | `([a, itemA], [b, itemB]) => number` | Optional sort function for ordering items |
|
||||||
|
| `icon` | `string` | Optional Material Symbols icon name |
|
||||||
|
| `collapsed` | `boolean` | Start section collapsed (default: `false`) |
|
||||||
|
|
||||||
|
### Advanced Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { setupWccTools } from '@design.estate/dees-wcctools';
|
||||||
|
import * as allElements from './elements/index.js';
|
||||||
|
import * as pages from './pages/index.js';
|
||||||
|
|
||||||
|
setupWccTools({
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
name: 'Pages',
|
||||||
|
type: 'pages',
|
||||||
|
items: pages,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Form Controls',
|
||||||
|
type: 'elements',
|
||||||
|
items: allElements,
|
||||||
|
icon: 'edit_note',
|
||||||
|
filter: (name) => name.startsWith('form-') || name.includes('input'),
|
||||||
|
sort: ([a], [b]) => a.localeCompare(b),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Layout',
|
||||||
|
type: 'elements',
|
||||||
|
items: allElements,
|
||||||
|
icon: 'dashboard',
|
||||||
|
filter: (name) => name.startsWith('layout-') || name.startsWith('grid-'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Legacy',
|
||||||
|
type: 'elements',
|
||||||
|
items: allElements,
|
||||||
|
filter: (name) => name.startsWith('legacy-'),
|
||||||
|
collapsed: true, // Start collapsed
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Legacy API (Still Supported)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// The old format still works for simple use cases
|
||||||
|
setupWccTools(elements, pages);
|
||||||
|
```
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### 🎯 Live Property Editing
|
### 🎯 Live Property Editing
|
||||||
@@ -162,19 +218,7 @@ Test your components across different screen sizes:
|
|||||||
|
|
||||||
### 🌓 Theme Support
|
### 🌓 Theme Support
|
||||||
|
|
||||||
Components automatically adapt to light/dark themes using the `goBright` property:
|
Components automatically adapt to light/dark themes. Use CSS custom properties with the theme manager:
|
||||||
|
|
||||||
```typescript
|
|
||||||
public render() {
|
|
||||||
return html`
|
|
||||||
<div class="${this.goBright ? 'light-theme' : 'dark-theme'}">
|
|
||||||
<!-- Your component content -->
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Or use CSS custom properties with the theme manager:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { cssManager } from '@design.estate/dees-element';
|
import { cssManager } from '@design.estate/dees-element';
|
||||||
@@ -182,8 +226,8 @@ import { cssManager } from '@design.estate/dees-element';
|
|||||||
public static styles = [
|
public static styles = [
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
color: ${cssManager.bdTheme('#000', '#fff')};
|
color: ${cssManager.bdTheme('#1a1a1a', '#e5e5e5')};
|
||||||
background: ${cssManager.bdTheme('#fff', '#000')};
|
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
];
|
];
|
||||||
@@ -191,7 +235,7 @@ public static styles = [
|
|||||||
|
|
||||||
### 🎬 Screen Recording
|
### 🎬 Screen Recording
|
||||||
|
|
||||||
Record component demos directly from the catalogue! The built-in recorder supports:
|
Record component demos directly from the catalogue:
|
||||||
|
|
||||||
- **Viewport Recording** — Record just the component viewport
|
- **Viewport Recording** — Record just the component viewport
|
||||||
- **Full Screen Recording** — Capture the entire screen
|
- **Full Screen Recording** — Capture the entire screen
|
||||||
@@ -232,9 +276,26 @@ export class MyComponent extends DeesElement {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 🎭 Multiple Demos
|
||||||
|
|
||||||
|
Components can expose multiple demo variations:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@customElement('my-button')
|
||||||
|
export class MyButton extends DeesElement {
|
||||||
|
public static demo = [
|
||||||
|
() => html`<my-button variant="primary">Primary</my-button>`,
|
||||||
|
() => html`<my-button variant="secondary">Secondary</my-button>`,
|
||||||
|
() => html`<my-button variant="danger">Danger</my-button>`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each demo appears as a numbered item in an expandable folder in the sidebar.
|
||||||
|
|
||||||
### ⏳ Async Demos
|
### ⏳ Async Demos
|
||||||
|
|
||||||
Return a `Promise` from `demo` for async setup. The dashboard waits for resolution:
|
Return a `Promise` from `demo` for async setup:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
public static demo = async () => {
|
public static demo = async () => {
|
||||||
@@ -243,7 +304,7 @@ public static demo = async () => {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🎭 Container Queries
|
### 🎯 Container Queries
|
||||||
|
|
||||||
Components can respond to their container size using the `wccToolsViewport` container:
|
Components can respond to their container size using the `wccToolsViewport` container:
|
||||||
|
|
||||||
@@ -269,7 +330,7 @@ public static styles = [
|
|||||||
|
|
||||||
### Required for Catalogue Display
|
### Required for Catalogue Display
|
||||||
|
|
||||||
1. Components must expose a static `demo` property returning a Lit template
|
1. Components must expose a static `demo` property returning a Lit template (or array of templates)
|
||||||
2. Use `@property()` decorators with the `accessor` keyword for editable properties
|
2. Use `@property()` decorators with the `accessor` keyword for editable properties
|
||||||
3. Export component classes for proper detection
|
3. Export component classes for proper detection
|
||||||
|
|
||||||
@@ -278,7 +339,7 @@ public static styles = [
|
|||||||
```typescript
|
```typescript
|
||||||
@customElement('best-practice-component')
|
@customElement('best-practice-component')
|
||||||
export class BestPracticeComponent extends DeesElement {
|
export class BestPracticeComponent extends DeesElement {
|
||||||
// ✅ Static demo property
|
// ✅ Static demo property (single or array)
|
||||||
public static demo = () => html`
|
public static demo = () => html`
|
||||||
<best-practice-component
|
<best-practice-component
|
||||||
.complexProp=${{ key: 'value' }}
|
.complexProp=${{ key: 'value' }}
|
||||||
@@ -305,23 +366,40 @@ export class BestPracticeComponent extends DeesElement {
|
|||||||
The catalogue uses URL routing for deep linking:
|
The catalogue uses URL routing for deep linking:
|
||||||
|
|
||||||
```
|
```
|
||||||
/wcctools-route/:type/:name/:viewport/:theme
|
/wcctools-route/:sectionName/:itemName/:demoIndex/:viewport/:theme
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
/wcctools-route/element/my-button/desktop/dark
|
/wcctools-route/Elements/my-button/0/desktop/dark
|
||||||
/wcctools-route/page/home/tablet/bright
|
/wcctools-route/Views/view-dashboard/0/tablet/bright
|
||||||
|
/wcctools-route/Pages/home/0/desktop/dark
|
||||||
```
|
```
|
||||||
|
|
||||||
## API Reference
|
## API Reference
|
||||||
|
|
||||||
### `setupWccTools(elements, pages?)`
|
### `setupWccTools(config)`
|
||||||
|
|
||||||
Initialize the WCC Tools dashboard.
|
Initialize the WCC Tools dashboard with sections configuration.
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
```typescript
|
||||||
|-----------|------|-------------|
|
interface IWccSection {
|
||||||
| `elements` | `Record<string, typeof LitElement>` | Map of element names to classes |
|
name: string;
|
||||||
| `pages` | `Record<string, TTemplateFactory>` | Optional map of page names to template functions |
|
type: 'elements' | 'pages';
|
||||||
|
items: Record<string, any>;
|
||||||
|
filter?: (name: string, item: any) => boolean;
|
||||||
|
sort?: (a: [string, any], b: [string, any]) => number;
|
||||||
|
icon?: string;
|
||||||
|
collapsed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IWccConfig {
|
||||||
|
sections: IWccSection[];
|
||||||
|
}
|
||||||
|
|
||||||
|
setupWccTools(config: IWccConfig): void;
|
||||||
|
|
||||||
|
// Legacy (still supported)
|
||||||
|
setupWccTools(elements: Record<string, any>, pages?: Record<string, TTemplateFactory>): void;
|
||||||
|
```
|
||||||
|
|
||||||
### `DeesDemoWrapper`
|
### `DeesDemoWrapper`
|
||||||
|
|
||||||
@@ -357,14 +435,21 @@ recorder.stopRecording();
|
|||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
my-components/
|
my-component-library/
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── components/
|
│ ├── elements/ # UI components
|
||||||
│ │ ├── my-button.ts
|
│ │ ├── my-button.ts
|
||||||
│ │ └── my-card.ts
|
│ │ ├── my-card.ts
|
||||||
│ └── catalogue.ts
|
│ │ └── index.ts
|
||||||
├── dist/
|
│ ├── views/ # Full-page layouts
|
||||||
├── index.html
|
│ │ ├── view-dashboard.ts
|
||||||
|
│ │ └── index.ts
|
||||||
|
│ ├── pages/ # Documentation pages
|
||||||
|
│ │ ├── home.ts
|
||||||
|
│ │ └── index.ts
|
||||||
|
│ └── catalogue.ts # WCC Tools setup
|
||||||
|
├── html/
|
||||||
|
│ └── index.html
|
||||||
└── package.json
|
└── package.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
3
test/views/index.ts
Normal file
3
test/views/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './view-dashboard.js';
|
||||||
|
export * from './view-settings.js';
|
||||||
|
export * from './view-empty-state.js';
|
||||||
286
test/views/view-dashboard.ts
Normal file
286
test/views/view-dashboard.ts
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
import { DeesElement, customElement, html, css, property, cssManager } from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
@customElement('view-dashboard')
|
||||||
|
export class ViewDashboard extends DeesElement {
|
||||||
|
public static demo = () => html`<view-dashboard></view-dashboard>`;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor title: string = 'Dashboard';
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
accessor notificationCount: number = 3;
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
cssManager.defaultStyles,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
min-height: 100%;
|
||||||
|
background: ${cssManager.bdTheme('#f5f5f5', '#0a0a0a')};
|
||||||
|
color: ${cssManager.bdTheme('#1a1a1a', '#e5e5e5')};
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 240px 1fr;
|
||||||
|
grid-template-rows: 60px 1fr;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
background: ${cssManager.bdTheme('#fff', '#111')};
|
||||||
|
border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#222')};
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-badge {
|
||||||
|
position: relative;
|
||||||
|
padding: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-badge::after {
|
||||||
|
content: attr(data-count);
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 2px;
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
background: ${cssManager.bdTheme('#fff', '#0f0f0f')};
|
||||||
|
border-right: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
color: ${cssManager.bdTheme('#666', '#999')};
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:hover {
|
||||||
|
background: ${cssManager.bdTheme('#f5f5f5', '#1a1a1a')};
|
||||||
|
color: ${cssManager.bdTheme('#1a1a1a', '#fff')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.active {
|
||||||
|
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.15)')};
|
||||||
|
color: #3b82f6;
|
||||||
|
border-right: 3px solid #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 24px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-header h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-header p {
|
||||||
|
color: ${cssManager.bdTheme('#666', '#888')};
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: ${cssManager.bdTheme('#fff', '#111')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#222')};
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card .label {
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: ${cssManager.bdTheme('#666', '#888')};
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card .value {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card .change {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #22c55e;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card .change.negative {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-activity {
|
||||||
|
background: ${cssManager.bdTheme('#fff', '#111')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#222')};
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-activity h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid ${cssManager.bdTheme('#f0f0f0', '#1a1a1a')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-icon {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: ${cssManager.bdTheme('#f5f5f5', '#1a1a1a')};
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-content .title {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-content .time {
|
||||||
|
font-size: 12px;
|
||||||
|
color: ${cssManager.bdTheme('#888', '#666')};
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return html`
|
||||||
|
<div class="dashboard">
|
||||||
|
<header class="header">
|
||||||
|
<h1>${this.title}</h1>
|
||||||
|
<div class="header-actions">
|
||||||
|
<div class="notification-badge" data-count="${this.notificationCount}">
|
||||||
|
<span>Notifications</span>
|
||||||
|
</div>
|
||||||
|
<span>User</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<nav class="sidebar">
|
||||||
|
<div class="nav-item active">Overview</div>
|
||||||
|
<div class="nav-item">Analytics</div>
|
||||||
|
<div class="nav-item">Projects</div>
|
||||||
|
<div class="nav-item">Team</div>
|
||||||
|
<div class="nav-item">Settings</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="content">
|
||||||
|
<div class="content-header">
|
||||||
|
<h2>Overview</h2>
|
||||||
|
<p>Welcome back! Here's what's happening with your projects.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="label">Total Revenue</div>
|
||||||
|
<div class="value">$45,231</div>
|
||||||
|
<div class="change">+20.1% from last month</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="label">Active Users</div>
|
||||||
|
<div class="value">2,350</div>
|
||||||
|
<div class="change">+180 new users</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="label">Pending Tasks</div>
|
||||||
|
<div class="value">12</div>
|
||||||
|
<div class="change negative">-3 from yesterday</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="label">Completion Rate</div>
|
||||||
|
<div class="value">94.2%</div>
|
||||||
|
<div class="change">+2.4% this week</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="recent-activity">
|
||||||
|
<h3>Recent Activity</h3>
|
||||||
|
<div class="activity-item">
|
||||||
|
<div class="activity-icon">+</div>
|
||||||
|
<div class="activity-content">
|
||||||
|
<div class="title">New project created</div>
|
||||||
|
<div class="time">2 minutes ago</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="activity-item">
|
||||||
|
<div class="activity-icon">U</div>
|
||||||
|
<div class="activity-content">
|
||||||
|
<div class="title">User settings updated</div>
|
||||||
|
<div class="time">1 hour ago</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="activity-item">
|
||||||
|
<div class="activity-icon">D</div>
|
||||||
|
<div class="activity-content">
|
||||||
|
<div class="title">Deployment completed</div>
|
||||||
|
<div class="time">3 hours ago</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
262
test/views/view-empty-state.ts
Normal file
262
test/views/view-empty-state.ts
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
import { DeesElement, customElement, html, css, property, cssManager } from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
@customElement('view-empty-state')
|
||||||
|
export class ViewEmptyState extends DeesElement {
|
||||||
|
public static demo = [
|
||||||
|
() => html`<view-empty-state></view-empty-state>`,
|
||||||
|
() => html`<view-empty-state variant="no-results"></view-empty-state>`,
|
||||||
|
() => html`<view-empty-state variant="error"></view-empty-state>`,
|
||||||
|
];
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor variant: 'empty' | 'no-results' | 'error' = 'empty';
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor title: string = '';
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor description: string = '';
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
cssManager.defaultStyles,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100%;
|
||||||
|
background: ${cssManager.bdTheme('#f5f5f5', '#0a0a0a')};
|
||||||
|
color: ${cssManager.bdTheme('#1a1a1a', '#e5e5e5')};
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
padding: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
margin: 0 auto 24px;
|
||||||
|
background: ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 48px;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.error {
|
||||||
|
color: #ef4444;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: ${cssManager.bdTheme('#666', '#888')};
|
||||||
|
margin: 0 0 24px 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
|
||||||
|
color: ${cssManager.bdTheme('#1a1a1a', '#e5e5e5')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: ${cssManager.bdTheme('#ddd', '#222')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.illustration {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.illustration svg {
|
||||||
|
width: 200px;
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-icon {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-back {
|
||||||
|
position: absolute;
|
||||||
|
width: 80px;
|
||||||
|
height: 60px;
|
||||||
|
background: ${cssManager.bdTheme('#ddd', '#333')};
|
||||||
|
border-radius: 4px 4px 8px 8px;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-front {
|
||||||
|
position: absolute;
|
||||||
|
width: 80px;
|
||||||
|
height: 50px;
|
||||||
|
background: ${cssManager.bdTheme('#e5e5e5', '#444')};
|
||||||
|
border-radius: 0 4px 8px 8px;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-tab {
|
||||||
|
position: absolute;
|
||||||
|
width: 30px;
|
||||||
|
height: 12px;
|
||||||
|
background: ${cssManager.bdTheme('#ddd', '#333')};
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border: 4px solid ${cssManager.bdTheme('#ccc', '#444')};
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 4px;
|
||||||
|
height: 20px;
|
||||||
|
background: ${cssManager.bdTheme('#ccc', '#444')};
|
||||||
|
border-radius: 2px;
|
||||||
|
bottom: -18px;
|
||||||
|
right: -8px;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-icon {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border: 4px solid #ef4444;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-icon::before {
|
||||||
|
content: '!';
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
private getContent() {
|
||||||
|
switch (this.variant) {
|
||||||
|
case 'no-results':
|
||||||
|
return {
|
||||||
|
title: this.title || 'No results found',
|
||||||
|
description:
|
||||||
|
this.description ||
|
||||||
|
"We couldn't find what you're looking for. Try adjusting your search or filters.",
|
||||||
|
icon: 'search',
|
||||||
|
primaryAction: 'Clear Filters',
|
||||||
|
secondaryAction: 'Go Back',
|
||||||
|
};
|
||||||
|
case 'error':
|
||||||
|
return {
|
||||||
|
title: this.title || 'Something went wrong',
|
||||||
|
description:
|
||||||
|
this.description ||
|
||||||
|
"We're having trouble loading this page. Please try again or contact support if the problem persists.",
|
||||||
|
icon: 'error',
|
||||||
|
primaryAction: 'Try Again',
|
||||||
|
secondaryAction: 'Contact Support',
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
title: this.title || 'No items yet',
|
||||||
|
description:
|
||||||
|
this.description ||
|
||||||
|
"Get started by creating your first item. It only takes a few seconds.",
|
||||||
|
icon: 'folder',
|
||||||
|
primaryAction: 'Create New',
|
||||||
|
secondaryAction: 'Learn More',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const content = this.getContent();
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="empty-state">
|
||||||
|
${this.renderIcon(content.icon)}
|
||||||
|
<h2>${content.title}</h2>
|
||||||
|
<p>${content.description}</p>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="btn btn-primary">${content.primaryAction}</button>
|
||||||
|
<button class="btn btn-secondary">${content.secondaryAction}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderIcon(type: string) {
|
||||||
|
switch (type) {
|
||||||
|
case 'search':
|
||||||
|
return html`<div class="search-icon"></div>`;
|
||||||
|
case 'error':
|
||||||
|
return html`<div class="error-icon"></div>`;
|
||||||
|
default:
|
||||||
|
return html`
|
||||||
|
<div class="folder-icon">
|
||||||
|
<div class="folder-tab"></div>
|
||||||
|
<div class="folder-back"></div>
|
||||||
|
<div class="folder-front"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
436
test/views/view-settings.ts
Normal file
436
test/views/view-settings.ts
Normal file
@@ -0,0 +1,436 @@
|
|||||||
|
import { DeesElement, customElement, html, css, property, cssManager } from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
@customElement('view-settings')
|
||||||
|
export class ViewSettings extends DeesElement {
|
||||||
|
public static demo = [
|
||||||
|
() => html`<view-settings></view-settings>`,
|
||||||
|
() => html`<view-settings activeTab="notifications"></view-settings>`,
|
||||||
|
() => html`<view-settings activeTab="security"></view-settings>`,
|
||||||
|
];
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor activeTab: 'profile' | 'notifications' | 'security' = 'profile';
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor userName: string = 'John Doe';
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor userEmail: string = 'john@example.com';
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
cssManager.defaultStyles,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
min-height: 100%;
|
||||||
|
background: ${cssManager.bdTheme('#f5f5f5', '#0a0a0a')};
|
||||||
|
color: ${cssManager.bdTheme('#1a1a1a', '#e5e5e5')};
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-layout {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 40px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-header {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-header h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-header p {
|
||||||
|
color: ${cssManager.bdTheme('#666', '#888')};
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 200px 1fr;
|
||||||
|
gap: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-nav {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
color: ${cssManager.bdTheme('#666', '#999')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:hover {
|
||||||
|
background: ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
|
||||||
|
color: ${cssManager.bdTheme('#1a1a1a', '#fff')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.active {
|
||||||
|
background: ${cssManager.bdTheme('#1a1a1a', '#3b82f6')};
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-panel {
|
||||||
|
background: ${cssManager.bdTheme('#fff', '#111')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#222')};
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#222')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: ${cssManager.bdTheme('#666', '#888')};
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: 1px solid ${cssManager.bdTheme('#ddd', '#333')};
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
background: ${cssManager.bdTheme('#fff', '#0a0a0a')};
|
||||||
|
color: ${cssManager.bdTheme('#1a1a1a', '#e5e5e5')};
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #3b82f6;
|
||||||
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 0;
|
||||||
|
border-bottom: 1px solid ${cssManager.bdTheme('#f0f0f0', '#1a1a1a')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-group:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-label .title {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-label .description {
|
||||||
|
font-size: 13px;
|
||||||
|
color: ${cssManager.bdTheme('#666', '#888')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
width: 44px;
|
||||||
|
height: 24px;
|
||||||
|
background: ${cssManager.bdTheme('#ddd', '#333')};
|
||||||
|
border-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle.active {
|
||||||
|
background: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle.active::after {
|
||||||
|
transform: translateX(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 24px;
|
||||||
|
padding-top: 24px;
|
||||||
|
border-top: 1px solid ${cssManager.bdTheme('#e5e5e5', '#222')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: transparent;
|
||||||
|
color: ${cssManager.bdTheme('#666', '#999')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('#ddd', '#333')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: ${cssManager.bdTheme('#f5f5f5', '#1a1a1a')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px;
|
||||||
|
background: ${cssManager.bdTheme('#f9f9f9', '#0a0a0a')};
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-item .info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-item .title {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-item .status {
|
||||||
|
font-size: 13px;
|
||||||
|
color: ${cssManager.bdTheme('#666', '#888')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-item .status.enabled {
|
||||||
|
color: #22c55e;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
private handleTabClick(tab: 'profile' | 'notifications' | 'security') {
|
||||||
|
this.activeTab = tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return html`
|
||||||
|
<div class="settings-layout">
|
||||||
|
<div class="settings-header">
|
||||||
|
<h1>Settings</h1>
|
||||||
|
<p>Manage your account settings and preferences</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-content">
|
||||||
|
<nav class="settings-nav">
|
||||||
|
<div
|
||||||
|
class="nav-item ${this.activeTab === 'profile' ? 'active' : ''}"
|
||||||
|
@click=${() => this.handleTabClick('profile')}
|
||||||
|
>
|
||||||
|
Profile
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="nav-item ${this.activeTab === 'notifications' ? 'active' : ''}"
|
||||||
|
@click=${() => this.handleTabClick('notifications')}
|
||||||
|
>
|
||||||
|
Notifications
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="nav-item ${this.activeTab === 'security' ? 'active' : ''}"
|
||||||
|
@click=${() => this.handleTabClick('security')}
|
||||||
|
>
|
||||||
|
Security
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="settings-panel">
|
||||||
|
${this.activeTab === 'profile' ? this.renderProfile() : null}
|
||||||
|
${this.activeTab === 'notifications' ? this.renderNotifications() : null}
|
||||||
|
${this.activeTab === 'security' ? this.renderSecurity() : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderProfile() {
|
||||||
|
return html`
|
||||||
|
<div class="panel-header">
|
||||||
|
<h2>Profile Information</h2>
|
||||||
|
<p>Update your personal details and email address</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>First Name</label>
|
||||||
|
<input type="text" value="John" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Last Name</label>
|
||||||
|
<input type="text" value="Doe" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Email Address</label>
|
||||||
|
<input type="email" value="${this.userEmail}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Bio</label>
|
||||||
|
<input type="text" placeholder="Tell us about yourself..." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button class="btn btn-primary">Save Changes</button>
|
||||||
|
<button class="btn btn-secondary">Cancel</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderNotifications() {
|
||||||
|
return html`
|
||||||
|
<div class="panel-header">
|
||||||
|
<h2>Notification Preferences</h2>
|
||||||
|
<p>Choose what notifications you want to receive</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-group">
|
||||||
|
<div class="toggle-label">
|
||||||
|
<span class="title">Email Notifications</span>
|
||||||
|
<span class="description">Receive email updates about your account activity</span>
|
||||||
|
</div>
|
||||||
|
<div class="toggle active"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-group">
|
||||||
|
<div class="toggle-label">
|
||||||
|
<span class="title">Push Notifications</span>
|
||||||
|
<span class="description">Receive push notifications on your device</span>
|
||||||
|
</div>
|
||||||
|
<div class="toggle active"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-group">
|
||||||
|
<div class="toggle-label">
|
||||||
|
<span class="title">Weekly Digest</span>
|
||||||
|
<span class="description">Get a weekly summary of your activity</span>
|
||||||
|
</div>
|
||||||
|
<div class="toggle"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-group">
|
||||||
|
<div class="toggle-label">
|
||||||
|
<span class="title">Marketing Emails</span>
|
||||||
|
<span class="description">Receive tips, updates, and promotions</span>
|
||||||
|
</div>
|
||||||
|
<div class="toggle"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button class="btn btn-primary">Save Preferences</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderSecurity() {
|
||||||
|
return html`
|
||||||
|
<div class="panel-header">
|
||||||
|
<h2>Security Settings</h2>
|
||||||
|
<p>Manage your password and security options</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="security-item">
|
||||||
|
<div class="info">
|
||||||
|
<span class="title">Password</span>
|
||||||
|
<span class="status">Last changed 30 days ago</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-secondary">Change Password</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="security-item">
|
||||||
|
<div class="info">
|
||||||
|
<span class="title">Two-Factor Authentication</span>
|
||||||
|
<span class="status enabled">Enabled</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-secondary">Manage</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="security-item">
|
||||||
|
<div class="info">
|
||||||
|
<span class="title">Active Sessions</span>
|
||||||
|
<span class="status">3 devices</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-secondary">View All</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button class="btn btn-danger">Delete Account</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@design.estate/dees-wcctools',
|
name: '@design.estate/dees-wcctools',
|
||||||
version: '3.2.0',
|
version: '3.3.0',
|
||||||
description: 'A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.'
|
description: 'A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { DeesElement, property, html, customElement, type TemplateResult, queryAsync, render, domtools } from '@design.estate/dees-element';
|
import { DeesElement, property, html, customElement, type TemplateResult, queryAsync, render, domtools } from '@design.estate/dees-element';
|
||||||
import { resolveTemplateFactory, getDemoAtIndex, getDemoCount, hasMultipleDemos } from './wcctools.helpers.js';
|
import { resolveTemplateFactory, getDemoAtIndex, getDemoCount, hasMultipleDemos } from './wcctools.helpers.js';
|
||||||
import type { TTemplateFactory } from './wcctools.helpers.js';
|
import type { TTemplateFactory } from './wcctools.helpers.js';
|
||||||
|
import type { IWccConfig, IWccSection, TElementType } from '../wcctools.interfaces.js';
|
||||||
|
|
||||||
import * as plugins from '../wcctools.plugins.js';
|
import * as plugins from '../wcctools.plugins.js';
|
||||||
|
|
||||||
@@ -9,13 +10,37 @@ import './wcc-frame.js';
|
|||||||
import './wcc-sidebar.js';
|
import './wcc-sidebar.js';
|
||||||
import './wcc-properties.js';
|
import './wcc-properties.js';
|
||||||
import { type TTheme } from './wcc-properties.js';
|
import { type TTheme } from './wcc-properties.js';
|
||||||
import { type TElementType } from './wcc-sidebar.js';
|
|
||||||
import { breakpoints } from '@design.estate/dees-domtools';
|
import { breakpoints } from '@design.estate/dees-domtools';
|
||||||
import { WccFrame } from './wcc-frame.js';
|
import { WccFrame } from './wcc-frame.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get filtered and sorted items from a section
|
||||||
|
*/
|
||||||
|
export const getSectionItems = (section: IWccSection): Array<[string, any]> => {
|
||||||
|
let entries = Object.entries(section.items);
|
||||||
|
|
||||||
|
// Apply filter if provided
|
||||||
|
if (section.filter) {
|
||||||
|
entries = entries.filter(([name, item]) => section.filter(name, item));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply sort if provided
|
||||||
|
if (section.sort) {
|
||||||
|
entries.sort(section.sort);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
};
|
||||||
|
|
||||||
@customElement('wcc-dashboard')
|
@customElement('wcc-dashboard')
|
||||||
export class WccDashboard extends DeesElement {
|
export class WccDashboard extends DeesElement {
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor sections: IWccSection[] = [];
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor selectedSection: IWccSection | null = null;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
accessor selectedType: TElementType;
|
accessor selectedType: TElementType;
|
||||||
|
|
||||||
@@ -39,12 +64,6 @@ export class WccDashboard extends DeesElement {
|
|||||||
return this.selectedViewport === 'native';
|
return this.selectedViewport === 'native';
|
||||||
}
|
}
|
||||||
|
|
||||||
@property()
|
|
||||||
accessor pages: Record<string, TTemplateFactory> = {};
|
|
||||||
|
|
||||||
@property()
|
|
||||||
accessor elements: { [key: string]: DeesElement } = {};
|
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
accessor warning: string = null;
|
accessor warning: string = null;
|
||||||
|
|
||||||
@@ -55,21 +74,34 @@ export class WccDashboard extends DeesElement {
|
|||||||
@queryAsync('wcc-frame')
|
@queryAsync('wcc-frame')
|
||||||
accessor wccFrame: Promise<WccFrame>;
|
accessor wccFrame: Promise<WccFrame>;
|
||||||
|
|
||||||
constructor(
|
constructor(config?: IWccConfig) {
|
||||||
elementsArg?: { [key: string]: DeesElement },
|
|
||||||
pagesArg?: Record<string, TTemplateFactory>
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
if (elementsArg) {
|
if (config && config.sections) {
|
||||||
this.elements = elementsArg;
|
this.sections = config.sections;
|
||||||
console.log('got elements:');
|
console.log('got sections:', this.sections.map(s => s.name));
|
||||||
console.log(this.elements);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pagesArg) {
|
/**
|
||||||
this.pages = pagesArg;
|
* Find an item by name across all sections, returns the item and its section
|
||||||
|
*/
|
||||||
|
public findItemByName(name: string): { item: any; section: IWccSection } | null {
|
||||||
|
for (const section of this.sections) {
|
||||||
|
const entries = getSectionItems(section);
|
||||||
|
const found = entries.find(([itemName]) => itemName === name);
|
||||||
|
if (found) {
|
||||||
|
return { item: found[1], section };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a section by name (URL-decoded)
|
||||||
|
*/
|
||||||
|
public findSectionByName(name: string): IWccSection | null {
|
||||||
|
return this.sections.find(s => s.name === name) || null;
|
||||||
|
}
|
||||||
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
@@ -159,19 +191,37 @@ export class WccDashboard extends DeesElement {
|
|||||||
this.setupScrollListeners();
|
this.setupScrollListeners();
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
// Route with demo index (new format)
|
// New route format with section name
|
||||||
this.domtools.router.on(
|
this.domtools.router.on(
|
||||||
'/wcctools-route/:itemType/:itemName/:demoIndex/:viewport/:theme',
|
'/wcctools-route/:sectionName/:itemName/:demoIndex/:viewport/:theme',
|
||||||
async (routeInfo) => {
|
async (routeInfo) => {
|
||||||
this.selectedType = routeInfo.params.itemType as TElementType;
|
const sectionName = decodeURIComponent(routeInfo.params.sectionName);
|
||||||
|
this.selectedSection = this.findSectionByName(sectionName);
|
||||||
this.selectedItemName = routeInfo.params.itemName;
|
this.selectedItemName = routeInfo.params.itemName;
|
||||||
this.selectedDemoIndex = parseInt(routeInfo.params.demoIndex) || 0;
|
this.selectedDemoIndex = parseInt(routeInfo.params.demoIndex) || 0;
|
||||||
this.selectedViewport = routeInfo.params.viewport as breakpoints.TViewport;
|
this.selectedViewport = routeInfo.params.viewport as breakpoints.TViewport;
|
||||||
this.selectedTheme = routeInfo.params.theme as TTheme;
|
this.selectedTheme = routeInfo.params.theme as TTheme;
|
||||||
if (routeInfo.params.itemType === 'element') {
|
|
||||||
this.selectedItem = this.elements[routeInfo.params.itemName];
|
if (this.selectedSection) {
|
||||||
} else if (routeInfo.params.itemType === 'page') {
|
// Find item within the section
|
||||||
this.selectedItem = this.pages[routeInfo.params.itemName];
|
const entries = getSectionItems(this.selectedSection);
|
||||||
|
const found = entries.find(([name]) => name === routeInfo.params.itemName);
|
||||||
|
if (found) {
|
||||||
|
this.selectedItem = found[1];
|
||||||
|
this.selectedType = this.selectedSection.type === 'elements' ? 'element' : 'page';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback: try legacy format (element/page as section name)
|
||||||
|
const legacyType = routeInfo.params.sectionName;
|
||||||
|
if (legacyType === 'element' || legacyType === 'page') {
|
||||||
|
this.selectedType = legacyType as TElementType;
|
||||||
|
// Find item in any matching section
|
||||||
|
const result = this.findItemByName(routeInfo.params.itemName);
|
||||||
|
if (result) {
|
||||||
|
this.selectedItem = result.item;
|
||||||
|
this.selectedSection = result.section;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore scroll positions from query parameters
|
// Restore scroll positions from query parameters
|
||||||
@@ -201,17 +251,33 @@ export class WccDashboard extends DeesElement {
|
|||||||
|
|
||||||
// Legacy route without demo index (for backwards compatibility)
|
// Legacy route without demo index (for backwards compatibility)
|
||||||
this.domtools.router.on(
|
this.domtools.router.on(
|
||||||
'/wcctools-route/:itemType/:itemName/:viewport/:theme',
|
'/wcctools-route/:sectionName/:itemName/:viewport/:theme',
|
||||||
async (routeInfo) => {
|
async (routeInfo) => {
|
||||||
this.selectedType = routeInfo.params.itemType as TElementType;
|
const sectionName = decodeURIComponent(routeInfo.params.sectionName);
|
||||||
|
this.selectedSection = this.findSectionByName(sectionName);
|
||||||
this.selectedItemName = routeInfo.params.itemName;
|
this.selectedItemName = routeInfo.params.itemName;
|
||||||
this.selectedDemoIndex = 0; // Default to first demo
|
this.selectedDemoIndex = 0;
|
||||||
this.selectedViewport = routeInfo.params.viewport as breakpoints.TViewport;
|
this.selectedViewport = routeInfo.params.viewport as breakpoints.TViewport;
|
||||||
this.selectedTheme = routeInfo.params.theme as TTheme;
|
this.selectedTheme = routeInfo.params.theme as TTheme;
|
||||||
if (routeInfo.params.itemType === 'element') {
|
|
||||||
this.selectedItem = this.elements[routeInfo.params.itemName];
|
if (this.selectedSection) {
|
||||||
} else if (routeInfo.params.itemType === 'page') {
|
const entries = getSectionItems(this.selectedSection);
|
||||||
this.selectedItem = this.pages[routeInfo.params.itemName];
|
const found = entries.find(([name]) => name === routeInfo.params.itemName);
|
||||||
|
if (found) {
|
||||||
|
this.selectedItem = found[1];
|
||||||
|
this.selectedType = this.selectedSection.type === 'elements' ? 'element' : 'page';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback: try legacy format
|
||||||
|
const legacyType = routeInfo.params.sectionName;
|
||||||
|
if (legacyType === 'element' || legacyType === 'page') {
|
||||||
|
this.selectedType = legacyType as TElementType;
|
||||||
|
const result = this.findItemByName(routeInfo.params.itemName);
|
||||||
|
if (result) {
|
||||||
|
this.selectedItem = result.item;
|
||||||
|
this.selectedSection = result.section;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore scroll positions from query parameters
|
// Restore scroll positions from query parameters
|
||||||
@@ -297,7 +363,10 @@ export class WccDashboard extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public buildUrl() {
|
public buildUrl() {
|
||||||
const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedDemoIndex}/${this.selectedViewport}/${this.selectedTheme}`;
|
const sectionName = this.selectedSection
|
||||||
|
? encodeURIComponent(this.selectedSection.name)
|
||||||
|
: this.selectedType; // Fallback for legacy
|
||||||
|
const baseUrl = `/wcctools-route/${sectionName}/${this.selectedItemName}/${this.selectedDemoIndex}/${this.selectedViewport}/${this.selectedTheme}`;
|
||||||
const queryParams = new URLSearchParams();
|
const queryParams = new URLSearchParams();
|
||||||
|
|
||||||
if (this.frameScrollY > 0) {
|
if (this.frameScrollY > 0) {
|
||||||
@@ -351,7 +420,10 @@ export class WccDashboard extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateUrlWithScrollState() {
|
private updateUrlWithScrollState() {
|
||||||
const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedDemoIndex}/${this.selectedViewport}/${this.selectedTheme}`;
|
const sectionName = this.selectedSection
|
||||||
|
? encodeURIComponent(this.selectedSection.name)
|
||||||
|
: this.selectedType; // Fallback for legacy
|
||||||
|
const baseUrl = `/wcctools-route/${sectionName}/${this.selectedItemName}/${this.selectedDemoIndex}/${this.selectedViewport}/${this.selectedTheme}`;
|
||||||
const queryParams = new URLSearchParams();
|
const queryParams = new URLSearchParams();
|
||||||
|
|
||||||
if (this.frameScrollY > 0) {
|
if (this.frameScrollY > 0) {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import * as plugins from '../wcctools.plugins.js';
|
import * as plugins from '../wcctools.plugins.js';
|
||||||
import { DeesElement, property, html, customElement, type TemplateResult, state } from '@design.estate/dees-element';
|
import { DeesElement, property, html, customElement, type TemplateResult, state } from '@design.estate/dees-element';
|
||||||
import { WccDashboard } from './wcc-dashboard.js';
|
import { WccDashboard, getSectionItems } from './wcc-dashboard.js';
|
||||||
import type { TTemplateFactory } from './wcctools.helpers.js';
|
import type { TTemplateFactory } from './wcctools.helpers.js';
|
||||||
import { getDemoCount, hasMultipleDemos } from './wcctools.helpers.js';
|
import { getDemoCount, hasMultipleDemos } from './wcctools.helpers.js';
|
||||||
|
import type { IWccSection, TElementType } from '../wcctools.interfaces.js';
|
||||||
export type TElementType = 'element' | 'page';
|
|
||||||
|
|
||||||
@customElement('wcc-sidebar')
|
@customElement('wcc-sidebar')
|
||||||
export class WccSidebar extends DeesElement {
|
export class WccSidebar extends DeesElement {
|
||||||
@@ -24,6 +23,12 @@ export class WccSidebar extends DeesElement {
|
|||||||
@state()
|
@state()
|
||||||
accessor expandedElements: Set<string> = new Set();
|
accessor expandedElements: Set<string> = new Set();
|
||||||
|
|
||||||
|
// Track which sections are collapsed
|
||||||
|
@state()
|
||||||
|
accessor collapsedSections: Set<string> = new Set();
|
||||||
|
|
||||||
|
private sectionsInitialized = false;
|
||||||
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" rel="stylesheet" />
|
||||||
@@ -65,7 +70,7 @@ export class WccSidebar extends DeesElement {
|
|||||||
padding: 0.5rem 0;
|
padding: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
.section-header {
|
||||||
padding: 0.3rem 0.75rem;
|
padding: 0.3rem 0.75rem;
|
||||||
font-size: 0.65rem;
|
font-size: 0.65rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -77,12 +82,45 @@ export class WccSidebar extends DeesElement {
|
|||||||
background: rgba(59, 130, 246, 0.03);
|
background: rgba(59, 130, 246, 0.03);
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
border-top: 1px solid var(--border);
|
border-top: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: all 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3:first-child {
|
.section-header:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-header:hover {
|
||||||
|
background: rgba(59, 130, 246, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header .expand-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header.collapsed .expand-icon {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header .section-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content.collapsed {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.material-symbols-outlined {
|
.material-symbols-outlined {
|
||||||
font-family: 'Material Symbols Outlined';
|
font-family: 'Material Symbols Outlined';
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@@ -216,17 +254,67 @@ export class WccSidebar extends DeesElement {
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<h3>Pages</h3>
|
${this.renderSections()}
|
||||||
${(() => {
|
</div>
|
||||||
const pages = Object.keys(this.dashboardRef.pages);
|
`;
|
||||||
return pages.map(pageName => {
|
}
|
||||||
const item = this.dashboardRef.pages[pageName];
|
|
||||||
|
/**
|
||||||
|
* Initialize collapsed sections from section config
|
||||||
|
*/
|
||||||
|
private initCollapsedSections() {
|
||||||
|
if (this.sectionsInitialized) return;
|
||||||
|
|
||||||
|
const collapsed = new Set<string>();
|
||||||
|
for (const section of this.dashboardRef.sections) {
|
||||||
|
if (section.collapsed) {
|
||||||
|
collapsed.add(section.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.collapsedSections = collapsed;
|
||||||
|
this.sectionsInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render all sections
|
||||||
|
*/
|
||||||
|
private renderSections() {
|
||||||
|
this.initCollapsedSections();
|
||||||
|
|
||||||
|
return this.dashboardRef.sections.map((section, index) => {
|
||||||
|
const isCollapsed = this.collapsedSections.has(section.name);
|
||||||
|
const sectionIcon = section.icon || (section.type === 'pages' ? 'insert_drive_file' : 'widgets');
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="selectOption ${this.selectedItem === item ? 'selected' : null}"
|
class="section-header ${isCollapsed ? 'collapsed' : ''}"
|
||||||
|
@click=${() => this.toggleSectionCollapsed(section.name)}
|
||||||
|
>
|
||||||
|
<i class="material-symbols-outlined expand-icon">expand_more</i>
|
||||||
|
${section.icon ? html`<i class="material-symbols-outlined section-icon">${section.icon}</i>` : null}
|
||||||
|
<span>${section.name}</span>
|
||||||
|
</div>
|
||||||
|
<div class="section-content ${isCollapsed ? 'collapsed' : ''}">
|
||||||
|
${this.renderSectionItems(section)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render items for a section
|
||||||
|
*/
|
||||||
|
private renderSectionItems(section: IWccSection) {
|
||||||
|
const entries = getSectionItems(section);
|
||||||
|
|
||||||
|
if (section.type === 'pages') {
|
||||||
|
return entries.map(([pageName, item]) => {
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="selectOption ${this.selectedItem === item ? 'selected' : ''}"
|
||||||
@click=${async () => {
|
@click=${async () => {
|
||||||
const domtools = await plugins.deesDomtools.DomTools.setupDomTools();
|
await plugins.deesDomtools.DomTools.setupDomTools();
|
||||||
this.selectItem('page', pageName, item, 0);
|
this.selectItem('page', pageName, item, 0, section);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<i class="material-symbols-outlined">insert_drive_file</i>
|
<i class="material-symbols-outlined">insert_drive_file</i>
|
||||||
@@ -234,14 +322,12 @@ export class WccSidebar extends DeesElement {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
})()}
|
} else {
|
||||||
<h3>Elements</h3>
|
// type === 'elements'
|
||||||
${(() => {
|
return entries.map(([elementName, item]) => {
|
||||||
const elements = Object.keys(this.dashboardRef.elements);
|
const anonItem = item as any;
|
||||||
return elements.map(elementName => {
|
const demoCount = anonItem.demo ? getDemoCount(anonItem.demo) : 0;
|
||||||
const item = this.dashboardRef.elements[elementName] as any;
|
const isMultiDemo = anonItem.demo && hasMultipleDemos(anonItem.demo);
|
||||||
const demoCount = item.demo ? getDemoCount(item.demo) : 0;
|
|
||||||
const isMultiDemo = item.demo && hasMultipleDemos(item.demo);
|
|
||||||
const isExpanded = this.expandedElements.has(elementName);
|
const isExpanded = this.expandedElements.has(elementName);
|
||||||
const isSelected = this.selectedItem === item;
|
const isSelected = this.selectedItem === item;
|
||||||
|
|
||||||
@@ -266,7 +352,7 @@ export class WccSidebar extends DeesElement {
|
|||||||
class="demo-child ${isThisDemoSelected ? 'selected' : ''}"
|
class="demo-child ${isThisDemoSelected ? 'selected' : ''}"
|
||||||
@click=${async () => {
|
@click=${async () => {
|
||||||
await plugins.deesDomtools.DomTools.setupDomTools();
|
await plugins.deesDomtools.DomTools.setupDomTools();
|
||||||
this.selectItem('element', elementName, item, demoIndex);
|
this.selectItem('element', elementName, item, demoIndex, section);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<i class="material-symbols-outlined">play_circle</i>
|
<i class="material-symbols-outlined">play_circle</i>
|
||||||
@@ -278,13 +364,13 @@ export class WccSidebar extends DeesElement {
|
|||||||
` : null}
|
` : null}
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
// Single demo element - render as normal
|
// Single demo element
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="selectOption ${isSelected ? 'selected' : null}"
|
class="selectOption ${isSelected ? 'selected' : ''}"
|
||||||
@click=${async () => {
|
@click=${async () => {
|
||||||
await plugins.deesDomtools.DomTools.setupDomTools();
|
await plugins.deesDomtools.DomTools.setupDomTools();
|
||||||
this.selectItem('element', elementName, item, 0);
|
this.selectItem('element', elementName, item, 0, section);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<i class="material-symbols-outlined">featured_video</i>
|
<i class="material-symbols-outlined">featured_video</i>
|
||||||
@@ -293,9 +379,17 @@ export class WccSidebar extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})()}
|
}
|
||||||
</div>
|
}
|
||||||
`;
|
|
||||||
|
private toggleSectionCollapsed(sectionName: string) {
|
||||||
|
const newSet = new Set(this.collapsedSections);
|
||||||
|
if (newSet.has(sectionName)) {
|
||||||
|
newSet.delete(sectionName);
|
||||||
|
} else {
|
||||||
|
newSet.add(sectionName);
|
||||||
|
}
|
||||||
|
this.collapsedSections = newSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
private toggleExpanded(elementName: string) {
|
private toggleExpanded(elementName: string) {
|
||||||
@@ -313,30 +407,50 @@ export class WccSidebar extends DeesElement {
|
|||||||
|
|
||||||
// Auto-expand folder when a multi-demo element is selected
|
// Auto-expand folder when a multi-demo element is selected
|
||||||
if (changedProperties.has('selectedItem') && this.selectedItem) {
|
if (changedProperties.has('selectedItem') && this.selectedItem) {
|
||||||
const elementName = Object.keys(this.dashboardRef.elements).find(
|
// Find the element in any section
|
||||||
name => this.dashboardRef.elements[name] === this.selectedItem
|
for (const section of this.dashboardRef.sections) {
|
||||||
);
|
if (section.type !== 'elements') continue;
|
||||||
if (elementName) {
|
|
||||||
const item = this.dashboardRef.elements[elementName] as any;
|
const entries = getSectionItems(section);
|
||||||
if (item.demo && hasMultipleDemos(item.demo)) {
|
const found = entries.find(([_, item]) => item === this.selectedItem);
|
||||||
|
if (found) {
|
||||||
|
const [elementName, item] = found;
|
||||||
|
const anonItem = item as any;
|
||||||
|
if (anonItem.demo && hasMultipleDemos(anonItem.demo)) {
|
||||||
if (!this.expandedElements.has(elementName)) {
|
if (!this.expandedElements.has(elementName)) {
|
||||||
const newSet = new Set(this.expandedElements);
|
const newSet = new Set(this.expandedElements);
|
||||||
newSet.add(elementName);
|
newSet.add(elementName);
|
||||||
this.expandedElements = newSet;
|
this.expandedElements = newSet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public selectItem(typeArg: TElementType, itemNameArg: string, itemArg: TTemplateFactory | DeesElement, demoIndex: number = 0) {
|
public selectItem(
|
||||||
|
typeArg: TElementType,
|
||||||
|
itemNameArg: string,
|
||||||
|
itemArg: TTemplateFactory | DeesElement,
|
||||||
|
demoIndex: number = 0,
|
||||||
|
section?: IWccSection
|
||||||
|
) {
|
||||||
console.log('selected item');
|
console.log('selected item');
|
||||||
console.log(itemNameArg);
|
console.log(itemNameArg);
|
||||||
console.log(itemArg);
|
console.log(itemArg);
|
||||||
console.log('demo index:', demoIndex);
|
console.log('demo index:', demoIndex);
|
||||||
|
console.log('section:', section?.name);
|
||||||
|
|
||||||
this.selectedItem = itemArg;
|
this.selectedItem = itemArg;
|
||||||
this.selectedType = typeArg;
|
this.selectedType = typeArg;
|
||||||
this.dashboardRef.selectedDemoIndex = demoIndex;
|
this.dashboardRef.selectedDemoIndex = demoIndex;
|
||||||
|
|
||||||
|
// Set the selected section on dashboard
|
||||||
|
if (section) {
|
||||||
|
this.dashboardRef.selectedSection = section;
|
||||||
|
}
|
||||||
|
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent('selectedType', {
|
new CustomEvent('selectedType', {
|
||||||
detail: typeArg
|
detail: typeArg
|
||||||
@@ -356,7 +470,6 @@ export class WccSidebar extends DeesElement {
|
|||||||
this.dashboardRef.buildUrl();
|
this.dashboardRef.buildUrl();
|
||||||
|
|
||||||
// Force re-render to update demo child selection indicator
|
// Force re-render to update demo child selection indicator
|
||||||
// (needed when switching between demos of the same element)
|
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,86 @@
|
|||||||
import { WccDashboard } from './elements/wcc-dashboard.js';
|
import { WccDashboard } from './elements/wcc-dashboard.js';
|
||||||
import { LitElement } from 'lit';
|
import { LitElement } from 'lit';
|
||||||
import type { TTemplateFactory } from './elements/wcctools.helpers.js';
|
import type { TTemplateFactory } from './elements/wcctools.helpers.js';
|
||||||
|
import type { IWccConfig, IWccSection } from './wcctools.interfaces.js';
|
||||||
|
|
||||||
// Export recording components and service
|
// Export recording components and service
|
||||||
export { RecorderService, type IRecorderEvents, type IRecordingOptions } from './services/recorder.service.js';
|
export { RecorderService, type IRecorderEvents, type IRecordingOptions } from './services/recorder.service.js';
|
||||||
export { WccRecordButton } from './elements/wcc-record-button.js';
|
export { WccRecordButton } from './elements/wcc-record-button.js';
|
||||||
export { WccRecordingPanel } from './elements/wcc-recording-panel.js';
|
export { WccRecordingPanel } from './elements/wcc-recording-panel.js';
|
||||||
|
|
||||||
const setupWccTools = (
|
// Export types for external use
|
||||||
|
export type { IWccConfig, IWccSection } from './wcctools.interfaces.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts legacy (elements, pages) format to new sections config
|
||||||
|
*/
|
||||||
|
const convertLegacyToConfig = (
|
||||||
elementsArg?: { [key: string]: LitElement },
|
elementsArg?: { [key: string]: LitElement },
|
||||||
pagesArg?: Record<string, TTemplateFactory>
|
pagesArg?: Record<string, TTemplateFactory>
|
||||||
|
): IWccConfig => {
|
||||||
|
const sections: IWccSection[] = [];
|
||||||
|
|
||||||
|
if (pagesArg && Object.keys(pagesArg).length > 0) {
|
||||||
|
sections.push({
|
||||||
|
name: 'Pages',
|
||||||
|
type: 'pages',
|
||||||
|
items: pagesArg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elementsArg && Object.keys(elementsArg).length > 0) {
|
||||||
|
sections.push({
|
||||||
|
name: 'Elements',
|
||||||
|
type: 'elements',
|
||||||
|
items: elementsArg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { sections };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the argument is the new config format
|
||||||
|
*/
|
||||||
|
const isWccConfig = (arg: any): arg is IWccConfig => {
|
||||||
|
return arg && typeof arg === 'object' && 'sections' in arg && Array.isArray(arg.sections);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup WCC Tools dashboard
|
||||||
|
*
|
||||||
|
* New format (recommended):
|
||||||
|
* ```typescript
|
||||||
|
* setupWccTools({
|
||||||
|
* sections: [
|
||||||
|
* { name: 'Elements', type: 'elements', items: elements },
|
||||||
|
* { name: 'Pages', type: 'pages', items: pages },
|
||||||
|
* ]
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Legacy format (still supported):
|
||||||
|
* ```typescript
|
||||||
|
* setupWccTools(elements, pages);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
const setupWccTools = (
|
||||||
|
configOrElements?: IWccConfig | { [key: string]: LitElement },
|
||||||
|
pagesArg?: Record<string, TTemplateFactory>
|
||||||
) => {
|
) => {
|
||||||
|
let config: IWccConfig;
|
||||||
|
|
||||||
|
if (isWccConfig(configOrElements)) {
|
||||||
|
config = configOrElements;
|
||||||
|
} else {
|
||||||
|
config = convertLegacyToConfig(configOrElements, pagesArg);
|
||||||
|
}
|
||||||
|
|
||||||
let hasRun = false;
|
let hasRun = false;
|
||||||
const runWccToolsSetup = async () => {
|
const runWccToolsSetup = async () => {
|
||||||
if (document.readyState === 'complete' && !hasRun) {
|
if (document.readyState === 'complete' && !hasRun) {
|
||||||
hasRun = true;
|
hasRun = true;
|
||||||
const wccTools = new WccDashboard(elementsArg as any, pagesArg);
|
const wccTools = new WccDashboard(config);
|
||||||
document.querySelector('body').append(wccTools);
|
document.querySelector('body').append(wccTools);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,40 @@ pnpm add -D @design.estate/dees-wcctools
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
### Sections-based Configuration (Recommended)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { setupWccTools } from '@design.estate/dees-wcctools';
|
||||||
|
import * as elements from './elements/index.js';
|
||||||
|
import * as views from './views/index.js';
|
||||||
|
import * as pages from './pages/index.js';
|
||||||
|
|
||||||
|
setupWccTools({
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
name: 'Pages',
|
||||||
|
type: 'pages',
|
||||||
|
items: pages,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Views',
|
||||||
|
type: 'elements',
|
||||||
|
items: views,
|
||||||
|
icon: 'web',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Elements',
|
||||||
|
type: 'elements',
|
||||||
|
items: elements,
|
||||||
|
filter: (name) => !name.startsWith('internal-'),
|
||||||
|
sort: ([a], [b]) => a.localeCompare(b),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Legacy Format (Still Supported)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { setupWccTools } from '@design.estate/dees-wcctools';
|
import { setupWccTools } from '@design.estate/dees-wcctools';
|
||||||
import { MyButton } from './components/my-button.js';
|
import { MyButton } from './components/my-button.js';
|
||||||
@@ -30,6 +64,8 @@ setupWccTools({
|
|||||||
| Export | Description |
|
| Export | Description |
|
||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| `setupWccTools` | Initialize the component catalogue dashboard |
|
| `setupWccTools` | Initialize the component catalogue dashboard |
|
||||||
|
| `IWccConfig` | TypeScript interface for sections configuration |
|
||||||
|
| `IWccSection` | TypeScript interface for individual section |
|
||||||
|
|
||||||
### Recording Components
|
### Recording Components
|
||||||
|
|
||||||
@@ -41,6 +77,18 @@ setupWccTools({
|
|||||||
| `IRecorderEvents` | TypeScript interface for recorder callbacks |
|
| `IRecorderEvents` | TypeScript interface for recorder callbacks |
|
||||||
| `IRecordingOptions` | TypeScript interface for recording options |
|
| `IRecordingOptions` | TypeScript interface for recording options |
|
||||||
|
|
||||||
|
## Section Configuration
|
||||||
|
|
||||||
|
| Property | Type | Description |
|
||||||
|
|----------|------|-------------|
|
||||||
|
| `name` | `string` | Display name for the section header |
|
||||||
|
| `type` | `'elements' \| 'pages'` | Rendering behavior |
|
||||||
|
| `items` | `Record<string, any>` | Element classes or page factories |
|
||||||
|
| `filter` | `(name, item) => boolean` | Optional filter function |
|
||||||
|
| `sort` | `([a, itemA], [b, itemB]) => number` | Optional sort function |
|
||||||
|
| `icon` | `string` | Material Symbols icon name |
|
||||||
|
| `collapsed` | `boolean` | Start section collapsed (default: false) |
|
||||||
|
|
||||||
## Internal Components
|
## Internal Components
|
||||||
|
|
||||||
The module includes these internal web components:
|
The module includes these internal web components:
|
||||||
@@ -48,8 +96,8 @@ The module includes these internal web components:
|
|||||||
| Component | Description |
|
| Component | Description |
|
||||||
|-----------|-------------|
|
|-----------|-------------|
|
||||||
| `wcc-dashboard` | Main dashboard container with routing |
|
| `wcc-dashboard` | Main dashboard container with routing |
|
||||||
| `wcc-sidebar` | Navigation sidebar with element/page listing |
|
| `wcc-sidebar` | Navigation sidebar with collapsible sections |
|
||||||
| `wcc-frame` | Iframe viewport with responsive sizing |
|
| `wcc-frame` | Responsive viewport with size controls |
|
||||||
| `wcc-properties` | Property panel with live editing |
|
| `wcc-properties` | Property panel with live editing |
|
||||||
| `wcc-record-button` | Recording state indicator button |
|
| `wcc-record-button` | Recording state indicator button |
|
||||||
| `wcc-recording-panel` | Recording workflow UI |
|
| `wcc-recording-panel` | Recording workflow UI |
|
||||||
@@ -72,14 +120,14 @@ const events: IRecorderEvents = {
|
|||||||
const recorder = new RecorderService(events);
|
const recorder = new RecorderService(events);
|
||||||
|
|
||||||
// Load available microphones
|
// Load available microphones
|
||||||
const mics = await recorder.loadMicrophones(true); // true = request permission
|
const mics = await recorder.loadMicrophones(true);
|
||||||
|
|
||||||
// Start audio level monitoring
|
// Start audio level monitoring
|
||||||
await recorder.startAudioMonitoring(mics[0].deviceId);
|
await recorder.startAudioMonitoring(mics[0].deviceId);
|
||||||
|
|
||||||
// Start recording
|
// Start recording
|
||||||
await recorder.startRecording({
|
await recorder.startRecording({
|
||||||
mode: 'viewport', // or 'screen'
|
mode: 'viewport',
|
||||||
audioDeviceId: mics[0].deviceId,
|
audioDeviceId: mics[0].deviceId,
|
||||||
viewportElement: document.querySelector('.viewport'),
|
viewportElement: document.querySelector('.viewport'),
|
||||||
});
|
});
|
||||||
@@ -99,10 +147,11 @@ recorder.dispose();
|
|||||||
```
|
```
|
||||||
ts_web/
|
ts_web/
|
||||||
├── index.ts # Main exports
|
├── index.ts # Main exports
|
||||||
|
├── wcctools.interfaces.ts # Type definitions
|
||||||
├── elements/
|
├── elements/
|
||||||
│ ├── wcc-dashboard.ts # Root dashboard component
|
│ ├── wcc-dashboard.ts # Root dashboard component
|
||||||
│ ├── wcc-sidebar.ts # Navigation sidebar
|
│ ├── wcc-sidebar.ts # Navigation sidebar
|
||||||
│ ├── wcc-frame.ts # Responsive iframe viewport
|
│ ├── wcc-frame.ts # Responsive viewport
|
||||||
│ ├── wcc-properties.ts # Property editing panel
|
│ ├── wcc-properties.ts # Property editing panel
|
||||||
│ ├── wcc-record-button.ts # Recording button
|
│ ├── wcc-record-button.ts # Recording button
|
||||||
│ ├── wcc-recording-panel.ts # Recording options/preview
|
│ ├── wcc-recording-panel.ts # Recording options/preview
|
||||||
@@ -116,6 +165,7 @@ ts_web/
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- 🎨 Interactive component preview
|
- 🎨 Interactive component preview
|
||||||
|
- 📂 Section-based sidebar with filtering & sorting
|
||||||
- 🔧 Real-time property editing with type detection
|
- 🔧 Real-time property editing with type detection
|
||||||
- 🌓 Theme switching (light/dark)
|
- 🌓 Theme switching (light/dark)
|
||||||
- 📱 Responsive viewport testing
|
- 📱 Responsive viewport testing
|
||||||
|
|||||||
31
ts_web/wcctools.interfaces.ts
Normal file
31
ts_web/wcctools.interfaces.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Configuration for a section in the WCC Tools sidebar
|
||||||
|
*/
|
||||||
|
export interface IWccSection {
|
||||||
|
/** Display name for the section header */
|
||||||
|
name: string;
|
||||||
|
/** How items in this section are rendered - 'elements' show demos, 'pages' render directly */
|
||||||
|
type: 'elements' | 'pages';
|
||||||
|
/** The items in this section - either element classes or page factory functions */
|
||||||
|
items: Record<string, any>;
|
||||||
|
/** Optional filter function to include/exclude items */
|
||||||
|
filter?: (name: string, item: any) => boolean;
|
||||||
|
/** Optional sort function for ordering items */
|
||||||
|
sort?: (a: [string, any], b: [string, any]) => number;
|
||||||
|
/** Optional Material icon name for the section header */
|
||||||
|
icon?: string;
|
||||||
|
/** Whether this section should start collapsed (default: false) */
|
||||||
|
collapsed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration object for setupWccTools
|
||||||
|
*/
|
||||||
|
export interface IWccConfig {
|
||||||
|
sections: IWccSection[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type for element selection types - now section-based
|
||||||
|
*/
|
||||||
|
export type TElementType = 'element' | 'page';
|
||||||
Reference in New Issue
Block a user