Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aedd4a041c | |||
| 1f37474d3f | |||
| 76748a81c9 | |||
| 1e432ca92e | |||
| 965d328559 | |||
| e38d3cd42a | |||
| 9175799ec6 | |||
| eeb863b197 | |||
| e697419843 | |||
| a69f504c2f | |||
| 2d1d9d901b | |||
| 811ec492d8 | |||
| 761b0b678b | |||
| 12a7156928 | |||
| 59a870c3bc | |||
| 13fa654c0f | |||
| 3616bbb9a7 | |||
| 27c071f7dc | |||
| ac1ef4e497 | |||
| 9c61c0542b | |||
| 5c099c8057 | |||
| 82b4afa95a | |||
| 888430d55a | |||
| 85424d07cd | |||
| 24d3afe85d | |||
| 9735af05c8 | |||
| 9471c419fa | |||
| 778f457ed5 | |||
| a91098527c | |||
| 8f8aedc6b0 | |||
| f67be189eb | |||
| 4b8b1fa446 |
146
changelog.md
146
changelog.md
@@ -1,5 +1,151 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-12-29 - 3.6.0 - feat(dees-appui)
|
||||||
|
add visibility toggles for main/secondary menus and ability to show/hide content tabs; expose corresponding setters and appconfig entries
|
||||||
|
|
||||||
|
- ts_web/elements/00group-appui/dees-appui-base: added boolean properties mainmenuVisible, secondarymenuVisible, maincontentTabsVisible; render main and secondary menus conditionally; pass showTabs to dees-appui-maincontent; added setter methods: setMainMenuVisible, setSecondaryMenuCollapsed, setSecondaryMenuVisible, setContentTabsVisible.
|
||||||
|
- ts_web/elements/00group-appui/dees-appui-maincontent: added showTabs property, support for a notabs attribute via styles, updated() and firstUpdated() to apply notabs state so tabs can be hidden/shown dynamically.
|
||||||
|
- ts_web/elements/interfaces/appconfig.ts: expanded appconfig interface to include setMainMenuVisible, setSecondaryMenuCollapsed, setSecondaryMenuVisible, setContentTabsVisible so host app can control visibility programmatically.
|
||||||
|
- No breaking changes: defaults preserve existing behavior (menus and tabs remain visible by default).
|
||||||
|
|
||||||
|
## 2025-12-29 - 3.5.1 - fix(dees-appui-view)
|
||||||
|
remove DeesAppuiView component, its demo, documentation snippet, and related exports
|
||||||
|
|
||||||
|
- Deleted component implementation: ts_web/elements/00group-appui/dees-appui-view/dees-appui-view.ts
|
||||||
|
- Deleted demo file: ts_web/elements/00group-appui/dees-appui-view/dees-appui-view.demo.ts
|
||||||
|
- Removed index re-export: ts_web/elements/00group-appui/dees-appui-view/index.ts (deleted) and removed export from ts_web/elements/00group-appui/index.ts
|
||||||
|
- Removed documentation section for DeesAppuiView from readme.md
|
||||||
|
- Breaking change: any consumers using the <dees-appui-view> component or its public API (selectTab, getMenuItems, getTabs) must be migrated to alternate components/approach
|
||||||
|
|
||||||
|
## 2025-12-29 - 3.5.0 - feat(theme,interfaces)
|
||||||
|
Introduce a global theming system and unify menu/tab interfaces; migrate components to use themeDefaultStyles and update APIs accordingly
|
||||||
|
|
||||||
|
- Add a new theme module and component (00theme.ts + dees-theme) that provides CSS tokens and themeDefaultStyles to import into components
|
||||||
|
- Migrate many components to include themeDefaultStyles in their static styles and add TODOs to replace hardcoded values with CSS variables
|
||||||
|
- Rename ITab -> IMenuItem and replace group.tabs with group.items across interfaces and components (IMenuGroup shape changed) — this is a breaking API change
|
||||||
|
- Remove legacy interfaces (ISecondaryMenuGroup, ISelectionOption) and update method and property types in DeesAppui* components and app config to use IMenuItem/IMenuGroup
|
||||||
|
- Move @design.estate/dees-wcctools from dependencies to devDependencies and bump its version to ^3.3.0
|
||||||
|
- Add numerous demo files and expand README with usage, examples and theme documentation
|
||||||
|
|
||||||
|
## 2025-12-19 - 3.4.0 - feat(dees-appui-base)
|
||||||
|
overhaul AppUI core: replace simple view rendering with a full-featured ViewRegistry (caching, hide/show lifecycle, async lazy-loading), introduce view lifecycle hooks and activation context, add activity log API/component, remove built-in router and state manager, and update configuration interfaces and demos
|
||||||
|
|
||||||
|
- Removed files: app.router.ts and state.manager.ts — routing and state-persistence internals were removed (breaking).
|
||||||
|
- ViewRegistry rewritten: supports cached instances, activate/deactivate lifecycle, canDeactivate checks, async content loading, parameterized routes, and legacy renderView kept as deprecated compatibility.
|
||||||
|
- New interfaces added/changed: IViewActivationContext, IViewLifecycle, IActivityEntry, IActivityLogAPI, IViewLifecycleEvent; IViewDefinition.content now accepts async loaders and a cache flag; IMainMenuConfig and ITab expanded (logo, groups, badges).
|
||||||
|
- Activity log: dees-appui-activitylog now implements IActivityLogAPI and exposes reactive entries; demo and readme updated with usage and examples.
|
||||||
|
- App config changed: routing and statePersistence config entries removed/adjusted; defaultView moved into IAppConfig; view change and lifecycle event shapes changed (breaking).
|
||||||
|
- Demos and documentation: dees-appui-base demo and readme added/updated to showcase new lifecycle hooks, secondary menu behavior, activity log and new APIs.
|
||||||
|
|
||||||
|
## 2025-12-19 - 3.3.3 - fix(tests)
|
||||||
|
update test imports to new dees-input-wysiwyg paths
|
||||||
|
|
||||||
|
- Updated imports in test/test.wysiwyg-registry.both.ts to point to ts_web/elements/00group-input/dees-input-wysiwyg/*
|
||||||
|
- Aligns test references with relocated WYSIWYG block handlers and block registration module; no behavior changes to implementation
|
||||||
|
|
||||||
|
## 2025-12-19 - 3.3.2 - fix(build)
|
||||||
|
update build config, bump dependencies, and adjust test import paths after element reorganization
|
||||||
|
|
||||||
|
- npmextra.json: renamed gitzone entry to @git.zone/cli, moved tsdoc key to @git.zone/tsdoc, added @ship.zone/szci entry and added release registries + accessLevel
|
||||||
|
- package.json: bumped @design.estate/dees-wcctools ^2.0.1 -> ^3.1.0, lucide ^0.560.0 -> ^0.562.0, @git.zone/tsbuild ^3.1.2 -> ^4.0.2, @types/node ^25.0.0 -> ^25.0.3
|
||||||
|
- tests: updated import paths to follow reorganized source layout (wysiwyg files moved under elements/00group-input/dees-input-wysiwyg and dees-contextmenu moved to elements/dees-contextmenu/dees-contextmenu.js); updated BlockRegistry and blockregistration import paths
|
||||||
|
- Purpose: align tests and build metadata with refactored element file locations and updated tool/dependency versions
|
||||||
|
|
||||||
|
## 2025-12-11 - 3.3.1 - fix(dees-pdf-viewer)
|
||||||
|
Scroll active PDF thumbnail into view after rendering and on page changes; update dependency versions
|
||||||
|
|
||||||
|
- Ensure the active thumbnail is scrolled into view after thumbnails are rendered (improves sidebar navigation for dees-pdf-viewer).
|
||||||
|
- Scroll the thumbnail into view when navigating pages if the sidebar is visible (prevents the active page from being off-screen).
|
||||||
|
- Retain re-setup of the intersection observer for lazy-loading pages after thumbnail re-render.
|
||||||
|
- Bumped dependencies in package.json: @design.estate/dees-wcctools -> ^2.0.1, lucide -> ^0.560.0, @git.zone/tswatch -> ^2.3.13, @types/node -> ^25.0.0.
|
||||||
|
|
||||||
|
## 2025-12-09 - 3.3.0 - feat(dees-appui-base)
|
||||||
|
Add unified App UI API to dees-appui-base with ViewRegistry, AppRouter and StateManager
|
||||||
|
|
||||||
|
- Introduce ViewRegistry for declarative view registration and rendering (supports tag names, element classes and template functions).
|
||||||
|
- Add AppRouter with hash/history/external/none modes, URL synchronization, navigate/back/forward and onRouteChange listener support.
|
||||||
|
- Add StateManager to persist UI state (localStorage, sessionStorage or in-memory) with save/load/update/clear APIs.
|
||||||
|
- Extend interfaces (interfaces/appconfig.ts) with IAppConfig, IViewDefinition, IRoutingConfig, IStatePersistenceConfig and IAppUIState.
|
||||||
|
- Expose new public DeesAppuiBase methods: configure, navigateToView, getCurrentView, getUIState, restoreUIState, saveState, loadState, getViewRegistry, getRouter.
|
||||||
|
- Maintain backward compatibility with existing property-based API and slot usage.
|
||||||
|
- Export new modules (view.registry, app.router, state.manager) from dees-appui-base index and update element exports.
|
||||||
|
|
||||||
|
## 2025-12-08 - 3.2.0 - feat(dees-simple-appdash,dees-simple-login,dees-terminal)
|
||||||
|
Revamp UI: dashboard & login styling, standardize icons to Lucide, and add terminal background/config
|
||||||
|
|
||||||
|
- Standardize icon usage to Lucide prefixes in dees-simple-appdash; add fallback handling for legacy icon names
|
||||||
|
- Revamped dees-simple-appdash sidebar: updated spacing, typography, header icon wrapper, scrollbar styling, section labels, hover/selected states, and visual indicators
|
||||||
|
- Change 'Logout' label to 'Sign out' in dees-simple-appdash and add explicit status classes for controlbar (connected, terminal)
|
||||||
|
- Improve terminal UX: smoother launch/close animations, updated shadow and sizing logic in dees-simple-appdash
|
||||||
|
- Add background property to dees-terminal, sync it to a CSS variable and apply it to xterm theme for configurable terminal background
|
||||||
|
- Redesign dees-simple-login: new header/subheader, card layout, spacing, and updated submit text to 'Sign in'
|
||||||
|
- Bump devDependency @git.zone/tswatch to ^2.3.5
|
||||||
|
|
||||||
|
## 2025-12-08 - 3.1.2 - fix(DeesAppuiMainmenu, DeesAppuiSecondarymenu)
|
||||||
|
Add position: relative to main and secondary app UI menus to fix positioning of overlays and tooltips
|
||||||
|
|
||||||
|
- ts_web/elements/00group-appui/dees-appui-mainmenu/dees-appui-mainmenu.ts: add `position: relative` to host styles
|
||||||
|
- ts_web/elements/00group-appui/dees-appui-secondarymenu/dees-appui-secondarymenu.ts: add `position: relative` to host styles
|
||||||
|
- Fixes incorrect positioning for absolutely positioned children (tooltips, overlays, badges) inside the main and secondary menus
|
||||||
|
|
||||||
|
## 2025-12-08 - 3.1.1 - fix(dees-appui)
|
||||||
|
Extract demos for main and secondary app menus, adjust collapsed styles and toggle placement, bump devDependency
|
||||||
|
|
||||||
|
- Extracted inline demo markup into separate demo files: ts_web/elements/00group-appui/dees-appui-mainmenu/dees-appui-mainmenu.demo.ts and ts_web/elements/00group-appui/dees-appui-secondarymenu/dees-appui-secondarymenu.demo.ts and wired them up via imported demoFunc to reduce component size.
|
||||||
|
- Moved collapse toggle button markup in both dees-appui-mainmenu and dees-appui-secondarymenu templates to after the main container to improve layout/stacking and focus behavior.
|
||||||
|
- Adjusted collapsed logo/heading styles: removed extra padding/gap and hide logo text using display:none for a cleaner collapsed state.
|
||||||
|
- Bumped devDependency @git.zone/tswatch from ^2.3.1 to ^2.3.2 in package.json.
|
||||||
|
|
||||||
|
## 2025-12-08 - 3.1.0 - feat(dees-appui)
|
||||||
|
Add collapsible/compact mode to AppUI sidebars (mainmenu & secondarymenu) with toggles, tooltips and improved z-index stacking
|
||||||
|
|
||||||
|
- Add collapsed property to dees-appui-mainmenu and dees-appui-secondarymenu (reflect: true) to enable compact horizontal mode.
|
||||||
|
- Add floating collapse toggle buttons and public toggleCollapse() methods on mainmenu and secondarymenu; these dispatch 'collapse-change' events (bubbles & composed).
|
||||||
|
- Expose and track collapse state in dees-appui-base via mainmenuCollapsed and secondarymenuCollapsed properties; bind states to child components and re-emit collapse-change events as mainmenu-collapse-change and secondarymenu-collapse-change.
|
||||||
|
- Implement collapsed styles and animations: reduced sidebar widths, hide/compact labels and headers, center icons, hide badges, and add smooth width/opacity transitions.
|
||||||
|
- Add tooltips that appear for tabs/items when sidebars are collapsed to preserve discoverability.
|
||||||
|
- Adjust layout grid in DeesAppuiBase (use auto columns) and add explicit z-index layering to ensure proper stacking order of mainmenu, secondarymenu, maincontent and activitylog.
|
||||||
|
|
||||||
|
## 2025-12-08 - 3.0.1 - fix(dees-appui)
|
||||||
|
Normalize header heights and box-sizing for App UI components
|
||||||
|
|
||||||
|
- Set topbar/header heights to 48px (was 40px) and adjusted dependent offsets (activity container top, topShadow position) in dees-appui-activitylog.
|
||||||
|
- Make logo and secondary menu headers fixed 48px tall and replace vertical padding with horizontal padding for consistent vertical alignment (dees-appui-mainmenu, dees-appui-secondarymenu).
|
||||||
|
- Ensure tabs wrapper uses explicit 48px height and tabsContainer fills height (height:100%) to keep tab items vertically centered (dees-appui-tabs).
|
||||||
|
- Add box-sizing: border-box to affected header/logo containers to prevent overflow and ensure correct sizing.
|
||||||
|
- Minor CSS alignment and overflow fixes to improve consistent layout and scrolling behavior across the app UI components.
|
||||||
|
|
||||||
|
## 2025-12-08 - 3.0.0 - BREAKING CHANGE(dees-appui-secondarymenu)
|
||||||
|
Add SecondaryMenu component and replace Mainselector with new SecondaryMenu in AppUI
|
||||||
|
|
||||||
|
- Add dees-appui-secondarymenu component: collapsible groups, badges, dynamic heading, context menu and legacy flat-options support
|
||||||
|
- Introduce interfaces ISecondaryMenuItem and ISecondaryMenuGroup under elements/interfaces
|
||||||
|
- Replace dees-appui-mainselector usage with dees-appui-secondarymenu in DeesAppuiBase (props/events updated: secondarymenuGroups, secondarymenuHeading, secondarymenuOptions, item-select / secondarymenu-item-select)
|
||||||
|
- Remove dees-appui-mainselector implementation and its index export; update group exports and imports to expose secondarymenu
|
||||||
|
- Update demos and pages to showcase the new SecondaryMenu and adjust import paths for grouped components
|
||||||
|
- Bump devDependency @git.zone/tswatch to ^2.3.1
|
||||||
|
|
||||||
|
## 2025-12-08 - 2.0.7 - fix(structure)
|
||||||
|
Add many new UI components, input controls, charts, editors, and demos
|
||||||
|
|
||||||
|
- Introduce App UI components: dees-appui-appbar, dees-appui-mainmenu, dees-appui-mainselector, dees-appui-maincontent, dees-appui-activitylog, dees-appui-profiledropdown, dees-appui-tabs, dees-appui-base, dees-appui-view (templates, styles and demos included).
|
||||||
|
- Add a comprehensive set of input components: dees-input-text, dees-input-checkbox, dees-input-dropdown, dees-input-fileupload, dees-input-datepicker, dees-input-phone, dees-input-iban, dees-input-quantityselector, dees-input-list, dees-input-typelist, dees-input-tags, dees-input-multitoggle, dees-input-radiogroup, dees-input-richtext and supporting demos/styles/templates.
|
||||||
|
- Add form primitives and integration: dees-form and dees-form-submit with validation, collection and demo pages showcasing usage.
|
||||||
|
- Add button family and utilities: dees-button (with updated variants, sizes, status handling and demo), dees-button-group and dees-button-exit.
|
||||||
|
- Add charting components: dees-chart-area (ApexCharts integration) and dees-chart-log (log viewer) plus rich demo scenarios and realtime features.
|
||||||
|
- Add data display components: dees-dataview-codebox (highlight.js integration) and dees-dataview-statusobject with copy/context behaviours and demos.
|
||||||
|
- Add editor tooling: dees-editor (Monaco loader/version management), dees-editor-markdown and dees-editor-markdownoutlet; also TipTap-based richtext input with toolbar and link handling.
|
||||||
|
- Add global utilities and infra: dees-toast (programmatic toast API and containers), z-index registry and theme/font helpers (fonts, color tokens), plus many styles and accessibility/keyboard improvements across components.
|
||||||
|
- Export and index updates: new group exports added to ts_web/elements index and many index.ts files to expose the new components and demos.
|
||||||
|
- Extensive demos and showcase pages added (input-showcase, component demos) to illustrate integration, keyboard navigation, theming and form flows.
|
||||||
|
|
||||||
|
## 2025-12-06 - 2.0.6 - fix(dees-input-richtext)
|
||||||
|
Initialize editor and link input element references in firstUpdated to ensure they exist before editor initialization.
|
||||||
|
|
||||||
|
- Assign editorElement from shadowRoot.querySelector('.editor-content') in firstUpdated.
|
||||||
|
- Assign linkInputElement from shadowRoot.querySelector('.link-input input') in firstUpdated.
|
||||||
|
- Call initializeEditor() after DOM references are set to avoid undefined-element runtime errors.
|
||||||
|
|
||||||
## 2025-12-06 - 2.0.5 - fix(build)
|
## 2025-12-06 - 2.0.5 - fix(build)
|
||||||
Bump devDependencies: update @git.zone/tsbundle and @git.zone/tswatch to patched versions
|
Bump devDependencies: update @git.zone/tsbundle and @git.zone/tswatch to patched versions
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"gitzone": {
|
"@git.zone/cli": {
|
||||||
"projectType": "wcc",
|
"projectType": "wcc",
|
||||||
"module": {
|
"module": {
|
||||||
"githost": "code.foss.global",
|
"githost": "code.foss.global",
|
||||||
@@ -35,13 +35,19 @@
|
|||||||
"Modern Web",
|
"Modern Web",
|
||||||
"Frontend Development"
|
"Frontend Development"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"registries": [
|
||||||
|
"https://verdaccio.lossless.digital",
|
||||||
|
"https://registry.npmjs.org"
|
||||||
|
],
|
||||||
|
"accessLevel": "public"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmci": {
|
"@git.zone/tsdoc": {
|
||||||
"npmGlobalTools": [],
|
|
||||||
"npmAccessLevel": "public"
|
|
||||||
},
|
|
||||||
"tsdoc": {
|
|
||||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||||
|
},
|
||||||
|
"@ship.zone/szci": {
|
||||||
|
"npmGlobalTools": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12
package.json
12
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@design.estate/dees-catalog",
|
"name": "@design.estate/dees-catalog",
|
||||||
"version": "2.0.5",
|
"version": "3.6.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
|
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
|
||||||
"main": "dist_ts_web/index.js",
|
"main": "dist_ts_web/index.js",
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@design.estate/dees-domtools": "^2.3.6",
|
"@design.estate/dees-domtools": "^2.3.6",
|
||||||
"@design.estate/dees-element": "^2.1.3",
|
"@design.estate/dees-element": "^2.1.3",
|
||||||
"@design.estate/dees-wcctools": "^1.2.1",
|
|
||||||
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
||||||
"@fortawesome/free-brands-svg-icons": "^7.1.0",
|
"@fortawesome/free-brands-svg-icons": "^7.1.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
||||||
@@ -38,20 +37,21 @@
|
|||||||
"highlight.js": "11.11.1",
|
"highlight.js": "11.11.1",
|
||||||
"ibantools": "^4.5.1",
|
"ibantools": "^4.5.1",
|
||||||
"lit": "^3.3.1",
|
"lit": "^3.3.1",
|
||||||
"lucide": "^0.555.0",
|
"lucide": "^0.562.0",
|
||||||
"monaco-editor": "0.52.2",
|
"monaco-editor": "0.52.2",
|
||||||
"pdfjs-dist": "^4.10.38",
|
"pdfjs-dist": "^4.10.38",
|
||||||
"xterm": "^5.3.0",
|
"xterm": "^5.3.0",
|
||||||
"xterm-addon-fit": "^0.8.0"
|
"xterm-addon-fit": "^0.8.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^3.1.2",
|
"@design.estate/dees-wcctools": "^3.3.0",
|
||||||
|
"@git.zone/tsbuild": "^4.0.2",
|
||||||
"@git.zone/tsbundle": "^2.6.3",
|
"@git.zone/tsbundle": "^2.6.3",
|
||||||
"@git.zone/tstest": "^3.1.3",
|
"@git.zone/tstest": "^3.1.3",
|
||||||
"@git.zone/tswatch": "^2.2.3",
|
"@git.zone/tswatch": "^2.3.13",
|
||||||
"@push.rocks/projectinfo": "^5.0.2",
|
"@push.rocks/projectinfo": "^5.0.2",
|
||||||
"@push.rocks/tapbundle": "^6.0.3",
|
"@push.rocks/tapbundle": "^6.0.3",
|
||||||
"@types/node": "^24.10.1"
|
"@types/node": "^25.0.3"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
|
|||||||
4291
pnpm-lock.yaml
generated
4291
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
120
readme.hints.md
120
readme.hints.md
@@ -681,3 +681,123 @@ According to Lit's documentation (https://lit.dev/docs/components/decorators/#de
|
|||||||
- Manual testing of key components verified
|
- Manual testing of key components verified
|
||||||
- No regressions detected
|
- No regressions detected
|
||||||
- Focus management and interactions working correctly
|
- Focus management and interactions working correctly
|
||||||
|
|
||||||
|
## Enhanced AppUI API (2025-12-08)
|
||||||
|
|
||||||
|
The `dees-appui-base` component has been enhanced with a unified configuration API for building real-world applications.
|
||||||
|
|
||||||
|
### New Modules:
|
||||||
|
|
||||||
|
1. **ViewRegistry** (`view.registry.ts`)
|
||||||
|
- Manages view definitions and their lifecycle
|
||||||
|
- Supports tag names, element classes, and template functions as view content
|
||||||
|
- Methods: register, get, renderView, findByRoute
|
||||||
|
|
||||||
|
2. **AppRouter** (`app.router.ts`)
|
||||||
|
- Built-in routing with hash or history mode
|
||||||
|
- External router support for framework integration
|
||||||
|
- Methods: navigate, back, forward, onRouteChange
|
||||||
|
|
||||||
|
3. **StateManager** (`state.manager.ts`)
|
||||||
|
- Persists UI state (collapsed menus, selections, current view)
|
||||||
|
- Supports localStorage, sessionStorage, or memory storage
|
||||||
|
- Methods: save, load, update, clear
|
||||||
|
|
||||||
|
### New Interfaces (in `interfaces/appconfig.ts`):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IAppConfig {
|
||||||
|
branding?: { logoIcon?: string; logoText?: string };
|
||||||
|
appBar?: IAppBarConfig;
|
||||||
|
views: IViewDefinition[];
|
||||||
|
mainMenu?: IMainMenuConfig;
|
||||||
|
routing?: IRoutingConfig;
|
||||||
|
statePersistence?: IStatePersistenceConfig;
|
||||||
|
onViewChange?: (viewId: string, view: IViewDefinition) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IViewDefinition {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
iconName?: string;
|
||||||
|
content: string | (new () => HTMLElement) | (() => TemplateResult);
|
||||||
|
secondaryMenu?: ISecondaryMenuGroup[];
|
||||||
|
contentTabs?: ITab[];
|
||||||
|
route?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IRoutingConfig {
|
||||||
|
mode: 'hash' | 'history' | 'external' | 'none';
|
||||||
|
basePath?: string;
|
||||||
|
defaultView?: string;
|
||||||
|
syncUrl?: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### New Public Methods on DeesAppuiBase:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Configure with unified config
|
||||||
|
configure(config: IAppConfig): void
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
navigateToView(viewId: string): boolean
|
||||||
|
getCurrentView(): IViewDefinition | undefined
|
||||||
|
|
||||||
|
// State management
|
||||||
|
getUIState(): IAppUIState
|
||||||
|
restoreUIState(state: IAppUIState): void
|
||||||
|
saveState(): void
|
||||||
|
loadState(): boolean
|
||||||
|
|
||||||
|
// Access internals
|
||||||
|
getViewRegistry(): ViewRegistry
|
||||||
|
getRouter(): AppRouter | null
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage Example (New Unified Config API):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { IAppConfig } from '@design.estate/dees-catalog';
|
||||||
|
|
||||||
|
const config: IAppConfig = {
|
||||||
|
branding: { logoIcon: 'lucide:box', logoText: 'My App' },
|
||||||
|
views: [
|
||||||
|
{ id: 'dashboard', name: 'Dashboard', iconName: 'lucide:home', content: 'my-dashboard' },
|
||||||
|
{ id: 'settings', name: 'Settings', iconName: 'lucide:settings', content: 'my-settings' },
|
||||||
|
],
|
||||||
|
mainMenu: {
|
||||||
|
sections: [{ views: ['dashboard'] }],
|
||||||
|
bottomItems: ['settings'],
|
||||||
|
},
|
||||||
|
routing: { mode: 'hash', defaultView: 'dashboard' },
|
||||||
|
statePersistence: { enabled: true, storage: 'localStorage' },
|
||||||
|
};
|
||||||
|
|
||||||
|
html`<dees-appui-base .config=${config}></dees-appui-base>`;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backward Compatibility:
|
||||||
|
|
||||||
|
The existing property-based API still works:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
html`
|
||||||
|
<dees-appui-base
|
||||||
|
.mainmenuGroups=${groups}
|
||||||
|
.secondarymenuGroups=${secondaryGroups}
|
||||||
|
@mainmenu-tab-select=${handler}
|
||||||
|
>
|
||||||
|
<div slot="maincontent">...</div>
|
||||||
|
</dees-appui-base>
|
||||||
|
`;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Features:
|
||||||
|
|
||||||
|
- **Declarative View Registry**: Map menu items to view components
|
||||||
|
- **Built-in Routing**: Hash or history mode with URL synchronization
|
||||||
|
- **External Router Support**: Integrate with Angular Router or other frameworks
|
||||||
|
- **State Persistence**: Save/restore collapsed menus, selections, and current view
|
||||||
|
- **View-specific Menus**: Each view can define its own secondary menu and tabs
|
||||||
|
- **Full Backward Compatibility**: Existing code continues to work
|
||||||
631
readme.md
631
readme.md
@@ -1,32 +1,71 @@
|
|||||||
# @design.estate/dees-catalog
|
# @design.estate/dees-catalog
|
||||||
A comprehensive web components library built with TypeScript and LitElement, providing 75+ UI components for building modern web applications with consistent design and behavior.
|
|
||||||
|
|
||||||
## Development Guide
|
A comprehensive web components library built with TypeScript and LitElement, providing **75+ UI components** for building modern web applications with consistent design and behavior. 🚀
|
||||||
For developers working on this library, please refer to the [UI Components Playbook](readme.playbook.md) for comprehensive patterns, best practices, and architectural guidelines.
|
|
||||||
|
|
||||||
## Install
|
[](https://www.typescriptlang.org/)
|
||||||
To install the `@design.estate/dees-catalog` library, you can use npm or any other compatible JavaScript package manager:
|
[](https://lit.dev/)
|
||||||
|
|
||||||
|
## Issue Reporting and Security
|
||||||
|
|
||||||
|
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
- 🎨 **Consistent Design System** - Beautiful, cohesive components following modern UI/UX principles
|
||||||
|
- 🌙 **Dark/Light Theme Support** - All components automatically adapt to your theme
|
||||||
|
- ⌨️ **Keyboard Accessible** - Full keyboard navigation and ARIA support
|
||||||
|
- 📱 **Responsive** - Mobile-first design that works across all screen sizes
|
||||||
|
- 🔧 **TypeScript-First** - Fully typed APIs with excellent IDE support
|
||||||
|
- 🧩 **Modular** - Use only what you need, tree-shakeable architecture
|
||||||
|
|
||||||
|
## 📦 Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install @design.estate/dees-catalog
|
npm install @design.estate/dees-catalog
|
||||||
|
# or
|
||||||
|
pnpm add @design.estate/dees-catalog
|
||||||
```
|
```
|
||||||
|
|
||||||
## Components Overview
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { html, DeesElement, customElement } from '@design.estate/dees-element';
|
||||||
|
import '@design.estate/dees-catalog';
|
||||||
|
|
||||||
|
@customElement('my-app')
|
||||||
|
class MyApp extends DeesElement {
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<dees-button type="highlighted" @click=${() => alert('Hello!')}>
|
||||||
|
Click me!
|
||||||
|
</dees-button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📖 Development Guide
|
||||||
|
|
||||||
|
For developers working on this library, please refer to the [UI Components Playbook](readme.playbook.md) for comprehensive patterns, best practices, and architectural guidelines.
|
||||||
|
|
||||||
|
## 📚 Components Overview
|
||||||
|
|
||||||
| Category | Components |
|
| Category | Components |
|
||||||
|----------|------------|
|
|----------|------------|
|
||||||
| Core UI | [`DeesButton`](#deesbutton), [`DeesButtonExit`](#deesbuttonexit), [`DeesButtonGroup`](#deesbuttongroup), [`DeesBadge`](#deesbadge), [`DeesChips`](#deeschips), [`DeesHeading`](#deesheading), [`DeesHint`](#deeshint), [`DeesIcon`](#deesicon), [`DeesLabel`](#deeslabel), [`DeesPanel`](#deespanel), [`DeesSearchbar`](#deessearchbar), [`DeesSpinner`](#deesspinner), [`DeesToast`](#deestoast), [`DeesWindowcontrols`](#deeswindowcontrols) |
|
| **Core UI** | [`DeesButton`](#deesbutton), [`DeesButtonExit`](#deesbuttonexit), [`DeesButtonGroup`](#deesbuttongroup), [`DeesBadge`](#deesbadge), [`DeesChips`](#deeschips), [`DeesHeading`](#deesheading), [`DeesHint`](#deeshint), [`DeesIcon`](#deesicon), [`DeesLabel`](#deeslabel), [`DeesPanel`](#deespanel), [`DeesSearchbar`](#deessearchbar), [`DeesSpinner`](#deesspinner), [`DeesToast`](#deestoast), [`DeesWindowcontrols`](#deeswindowcontrols) |
|
||||||
| Forms | [`DeesForm`](#deesform), [`DeesInputText`](#deesinputtext), [`DeesInputCheckbox`](#deesinputcheckbox), [`DeesInputDropdown`](#deesinputdropdown), [`DeesInputRadiogroup`](#deesinputradiogroup), [`DeesInputFileupload`](#deesinputfileupload), [`DeesInputIban`](#deesinputiban), [`DeesInputPhone`](#deesinputphone), [`DeesInputQuantitySelector`](#deesinputquantityselector), [`DeesInputMultitoggle`](#deesinputmultitoggle), [`DeesInputTags`](#deesinputtags), [`DeesInputTypelist`](#deesinputtypelist), [`DeesInputRichtext`](#deesinputrichtext), [`DeesInputWysiwyg`](#deesinputwysiwyg), [`DeesInputDatepicker`](#deesinputdatepicker), [`DeesInputSearchselect`](#deesinputsearchselect), [`DeesFormSubmit`](#deesformsubmit) |
|
| **Forms** | [`DeesForm`](#deesform), [`DeesInputText`](#deesinputtext), [`DeesInputCheckbox`](#deesinputcheckbox), [`DeesInputDropdown`](#deesinputdropdown), [`DeesInputRadiogroup`](#deesinputradiogroup), [`DeesInputFileupload`](#deesinputfileupload), [`DeesInputIban`](#deesinputiban), [`DeesInputPhone`](#deesinputphone), [`DeesInputQuantitySelector`](#deesinputquantityselector), [`DeesInputMultitoggle`](#deesinputmultitoggle), [`DeesInputTags`](#deesinputtags), [`DeesInputTypelist`](#deesinputtypelist), [`DeesInputRichtext`](#deesinputrichtext), [`DeesInputWysiwyg`](#deesinputwysiwyg), [`DeesInputDatepicker`](#deesinputdatepicker), [`DeesInputSearchselect`](#deesinputsearchselect), [`DeesFormSubmit`](#deesformsubmit) |
|
||||||
| Layout | [`DeesAppuiBase`](#deesappuibase), [`DeesAppuiMainmenu`](#deesappuimainmenu), [`DeesAppuiMainselector`](#deesappuimainselector), [`DeesAppuiMaincontent`](#deesappuimaincontent), [`DeesAppuiAppbar`](#deesappuiappbar), [`DeesAppuiActivitylog`](#deesappuiactivitylog), [`DeesAppuiProfiledropdown`](#deesappuiprofiledropdown), [`DeesAppuiTabs`](#deesappuitabs), [`DeesAppuiView`](#deesappuiview), [`DeesMobileNavigation`](#deesmobilenavigation), [`DeesDashboardGrid`](#deesdashboardgrid) |
|
| **Layout** | [`DeesAppuiBase`](#deesappuibase), [`DeesAppuiMainmenu`](#deesappuimainmenu), [`DeesAppuiSecondarymenu`](#deesappuisecondarymenu), [`DeesAppuiMaincontent`](#deesappuimaincontent), [`DeesAppuiAppbar`](#deesappuiappbar), [`DeesAppuiActivitylog`](#deesappuiactivitylog), [`DeesAppuiProfiledropdown`](#deesappuiprofiledropdown), [`DeesAppuiTabs`](#deesappuitabs), [`DeesAppuiView`](#deesappuiview), [`DeesMobileNavigation`](#deesmobilenavigation), [`DeesDashboardGrid`](#deesdashboardgrid) |
|
||||||
| Data Display | [`DeesTable`](#deestable), [`DeesDataviewCodebox`](#deesdataviewcodebox), [`DeesDataviewStatusobject`](#deesdataviewstatusobject), [`DeesPdf`](#deespdf), [`DeesStatsGrid`](#deesstatsgrid), [`DeesPagination`](#deespagination) |
|
| **Data Display** | [`DeesTable`](#deestable), [`DeesDataviewCodebox`](#deesdataviewcodebox), [`DeesDataviewStatusobject`](#deesdataviewstatusobject), [`DeesPdf`](#deespdf), [`DeesStatsGrid`](#deesstatsgrid), [`DeesPagination`](#deespagination) |
|
||||||
| Visualization | [`DeesChartArea`](#deeschartarea), [`DeesChartLog`](#deeschartlog) |
|
| **Visualization** | [`DeesChartArea`](#deeschartarea), [`DeesChartLog`](#deeschartlog) |
|
||||||
| Dialogs & Overlays | [`DeesModal`](#deesmodal), [`DeesContextmenu`](#deescontextmenu), [`DeesSpeechbubble`](#deesspeechbubble), [`DeesWindowlayer`](#deeswindowlayer) |
|
| **Dialogs & Overlays** | [`DeesModal`](#deesmodal), [`DeesContextmenu`](#deescontextmenu), [`DeesSpeechbubble`](#deesspeechbubble), [`DeesWindowlayer`](#deeswindowlayer) |
|
||||||
| Navigation | [`DeesStepper`](#deesstepper), [`DeesProgressbar`](#deesprogressbar) |
|
| **Navigation** | [`DeesStepper`](#deesstepper), [`DeesProgressbar`](#deesprogressbar) |
|
||||||
| Development | [`DeesEditor`](#deeseditor), [`DeesEditorMarkdown`](#deeseditormarkdown), [`DeesEditorMarkdownoutlet`](#deeseditormarkdownoutlet), [`DeesTerminal`](#deesterminal), [`DeesUpdater`](#deesupdater) |
|
| **Development** | [`DeesEditor`](#deeseditor), [`DeesEditorMarkdown`](#deeseditormarkdown), [`DeesEditorMarkdownoutlet`](#deeseditormarkdownoutlet), [`DeesTerminal`](#deesterminal), [`DeesUpdater`](#deesupdater) |
|
||||||
| Auth & Utilities | [`DeesSimpleAppdash`](#deessimpleappdash), [`DeesSimpleLogin`](#deessimplelogin) |
|
| **Auth & Utilities** | [`DeesSimpleAppdash`](#deessimpleappdash), [`DeesSimpleLogin`](#deessimplelogin) |
|
||||||
| Shopping | [`DeesShoppingProductcard`](#deesshoppingproductcard) |
|
| **Shopping** | [`DeesShoppingProductcard`](#deesshoppingproductcard) |
|
||||||
|
|
||||||
## Detailed Component Documentation
|
---
|
||||||
|
|
||||||
|
## 🎯 Detailed Component Documentation
|
||||||
|
|
||||||
### Core UI Components
|
### Core UI Components
|
||||||
|
|
||||||
@@ -148,16 +187,9 @@ const toast = await DeesToast.show({
|
|||||||
|
|
||||||
// Later dismiss programmatically
|
// Later dismiss programmatically
|
||||||
toast.dismiss();
|
toast.dismiss();
|
||||||
|
|
||||||
// Component usage (not typically used directly)
|
|
||||||
<dees-toast
|
|
||||||
message="Changes saved"
|
|
||||||
type="success"
|
|
||||||
duration="3000"
|
|
||||||
></dees-toast>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Key Features:
|
**Key Features:**
|
||||||
- Multiple toast types with distinct icons and colors
|
- Multiple toast types with distinct icons and colors
|
||||||
- 6 position options for flexible placement
|
- 6 position options for flexible placement
|
||||||
- Auto-dismiss with visual progress indicator
|
- Auto-dismiss with visual progress indicator
|
||||||
@@ -254,6 +286,8 @@ Window control buttons (minimize, maximize, close) for desktop-like applications
|
|||||||
></dees-windowcontrols>
|
></dees-windowcontrols>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Form Components
|
### Form Components
|
||||||
|
|
||||||
#### `DeesForm`
|
#### `DeesForm`
|
||||||
@@ -427,7 +461,7 @@ Tag input component for managing lists of tags with auto-complete and validation
|
|||||||
></dees-input-tags>
|
></dees-input-tags>
|
||||||
```
|
```
|
||||||
|
|
||||||
Key Features:
|
**Key Features:**
|
||||||
- Add tags by pressing Enter or typing comma/semicolon
|
- Add tags by pressing Enter or typing comma/semicolon
|
||||||
- Remove tags with click or backspace
|
- Remove tags with click or backspace
|
||||||
- Auto-complete suggestions with keyboard navigation
|
- Auto-complete suggestions with keyboard navigation
|
||||||
@@ -473,7 +507,7 @@ Date and time picker component with calendar interface and manual typing support
|
|||||||
></dees-input-datepicker>
|
></dees-input-datepicker>
|
||||||
```
|
```
|
||||||
|
|
||||||
Key Features:
|
**Key Features:**
|
||||||
- Interactive calendar popup
|
- Interactive calendar popup
|
||||||
- Manual date typing with multiple formats
|
- Manual date typing with multiple formats
|
||||||
- Optional time selection
|
- Optional time selection
|
||||||
@@ -487,7 +521,7 @@ Key Features:
|
|||||||
- Theme-aware styling
|
- Theme-aware styling
|
||||||
- Live parsing and validation
|
- Live parsing and validation
|
||||||
|
|
||||||
Manual Input Formats:
|
**Manual Input Formats:**
|
||||||
```typescript
|
```typescript
|
||||||
// Date formats supported
|
// Date formats supported
|
||||||
"2023-12-20" // ISO format (YYYY-MM-DD)
|
"2023-12-20" // ISO format (YYYY-MM-DD)
|
||||||
@@ -500,8 +534,6 @@ Manual Input Formats:
|
|||||||
"12/20/2023 16:00"
|
"12/20/2023 16:00"
|
||||||
```
|
```
|
||||||
|
|
||||||
The component automatically parses and validates input as you type, updating the internal date value when a valid date is recognized.
|
|
||||||
|
|
||||||
#### `DeesInputSearchselect`
|
#### `DeesInputSearchselect`
|
||||||
Search-enabled dropdown selection component.
|
Search-enabled dropdown selection component.
|
||||||
|
|
||||||
@@ -535,7 +567,7 @@ Rich text editor with formatting toolbar powered by TipTap.
|
|||||||
></dees-input-richtext>
|
></dees-input-richtext>
|
||||||
```
|
```
|
||||||
|
|
||||||
Key Features:
|
**Key Features:**
|
||||||
- Full formatting toolbar (bold, italic, underline, strike, etc.)
|
- Full formatting toolbar (bold, italic, underline, strike, etc.)
|
||||||
- Heading levels (H1-H6)
|
- Heading levels (H1-H6)
|
||||||
- Lists (bullet, ordered)
|
- Lists (bullet, ordered)
|
||||||
@@ -560,7 +592,7 @@ Advanced block-based editor with slash commands and rich content blocks.
|
|||||||
></dees-input-wysiwyg>
|
></dees-input-wysiwyg>
|
||||||
```
|
```
|
||||||
|
|
||||||
Key Features:
|
**Key Features:**
|
||||||
- Slash commands for quick formatting
|
- Slash commands for quick formatting
|
||||||
- Block-based editing (paragraphs, headings, lists, etc.)
|
- Block-based editing (paragraphs, headings, lists, etc.)
|
||||||
- Drag and drop block reordering
|
- Drag and drop block reordering
|
||||||
@@ -579,122 +611,100 @@ Submit button component specifically designed for `DeesForm`.
|
|||||||
>Submit Form</dees-form-submit>
|
>Submit Form</dees-form-submit>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Layout Components
|
### Layout Components
|
||||||
|
|
||||||
#### `DeesAppuiBase`
|
#### `DeesAppuiBase`
|
||||||
Base container component for application layout structure with integrated appbar, menu system, and content areas.
|
A comprehensive application shell component providing a complete UI framework with navigation, menus, activity logging, and view management.
|
||||||
|
|
||||||
|
> **Full API Documentation**: See [ts_web/elements/00group-appui/dees-appui-base/readme.md](./ts_web/elements/00group-appui/dees-appui-base/readme.md) for complete documentation including all programmatic APIs, view lifecycle hooks, and TypeScript interfaces.
|
||||||
|
|
||||||
|
**Quick Start:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
<dees-appui-base
|
import { html, DeesElement, customElement } from '@design.estate/dees-element';
|
||||||
// Appbar configuration
|
import { DeesAppuiBase } from '@design.estate/dees-catalog';
|
||||||
.appbarMenuItems=${[
|
|
||||||
{
|
|
||||||
name: 'File',
|
|
||||||
action: async () => {}, // No-op for parent menu items
|
|
||||||
submenu: [
|
|
||||||
{ name: 'New File', shortcut: 'Cmd+N', iconName: 'file-plus', action: async () => {} },
|
|
||||||
{ name: 'Open...', shortcut: 'Cmd+O', iconName: 'folder-open', action: async () => {} },
|
|
||||||
{ divider: true },
|
|
||||||
{ name: 'Save', shortcut: 'Cmd+S', iconName: 'save', action: async () => {} }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Edit',
|
|
||||||
action: async () => {},
|
|
||||||
submenu: [
|
|
||||||
{ name: 'Undo', shortcut: 'Cmd+Z', iconName: 'undo', action: async () => {} },
|
|
||||||
{ name: 'Redo', shortcut: 'Cmd+Shift+Z', iconName: 'redo', action: async () => {} }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
.appbarBreadcrumbs=${'Dashboard > Overview'}
|
|
||||||
.appbarTheme=${'dark'}
|
|
||||||
.appbarUser=${{ name: 'John Doe', status: 'online' }}
|
|
||||||
.appbarShowSearch=${true}
|
|
||||||
.appbarShowWindowControls=${true}
|
|
||||||
|
|
||||||
// Main menu configuration (left sidebar)
|
@customElement('my-app')
|
||||||
.mainmenuTabs=${[
|
class MyApp extends DeesElement {
|
||||||
{ key: 'dashboard', iconName: 'lucide:home', action: () => {} },
|
private appui: DeesAppuiBase;
|
||||||
{ key: 'projects', iconName: 'lucide:folder', action: () => {} },
|
|
||||||
{ key: 'settings', iconName: 'lucide:settings', action: () => {} }
|
|
||||||
]}
|
|
||||||
.mainmenuSelectedTab=${selectedTab}
|
|
||||||
|
|
||||||
// Selector configuration (second sidebar)
|
async firstUpdated() {
|
||||||
.mainselectorOptions=${[
|
this.appui = this.shadowRoot.querySelector('dees-appui-base');
|
||||||
{ key: 'Overview', action: () => {} },
|
|
||||||
{ key: 'Components', action: () => {} },
|
|
||||||
{ key: 'Services', action: () => {} }
|
|
||||||
]}
|
|
||||||
.mainselectorSelectedOption=${selectedOption}
|
|
||||||
|
|
||||||
// Main content tabs
|
// Configure with views and menu
|
||||||
.maincontentTabs=${[
|
this.appui.configure({
|
||||||
{ key: 'tab1', iconName: 'lucide:file', action: () => {} }
|
branding: { logoIcon: 'lucide:box', logoText: 'My App' },
|
||||||
]}
|
views: [
|
||||||
|
{ id: 'dashboard', name: 'Dashboard', iconName: 'lucide:home', content: 'my-dashboard' },
|
||||||
|
{ id: 'settings', name: 'Settings', iconName: 'lucide:settings', content: 'my-settings' },
|
||||||
|
],
|
||||||
|
mainMenu: {
|
||||||
|
sections: [{ name: 'Main', views: ['dashboard', 'settings'] }]
|
||||||
|
},
|
||||||
|
defaultView: 'dashboard'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Event handlers
|
render() {
|
||||||
@appbar-menu-select=${(e) => handleMenuSelect(e.detail)}
|
return html`<dees-appui-base></dees-appui-base>`;
|
||||||
@appbar-breadcrumb-navigate=${(e) => handleBreadcrumbNav(e.detail)}
|
}
|
||||||
@appbar-search-click=${() => handleSearch()}
|
}
|
||||||
@appbar-user-menu-open=${() => handleUserMenu()}
|
|
||||||
@mainmenu-tab-select=${(e) => handleTabSelect(e.detail)}
|
|
||||||
@mainselector-option-select=${(e) => handleOptionSelect(e.detail)}
|
|
||||||
>
|
|
||||||
<div slot="maincontent">
|
|
||||||
<!-- Your main application content goes here -->
|
|
||||||
</div>
|
|
||||||
</dees-appui-base>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Key Features:
|
**Key Features:**
|
||||||
- **Integrated Layout System**: Automatically arranges appbar, sidebars, and content area
|
- **Configure API**: Single `configure()` method for complete app setup
|
||||||
- **Centralized Configuration**: Pass properties to all child components from one place
|
- **View Management**: Automatic view caching, lazy loading, and lifecycle hooks
|
||||||
- **Event Propagation**: All child component events are re-emitted for easy handling
|
- **Programmatic APIs**: Full control over AppBar, Main Menu, Secondary Menu, Content Tabs, and Activity Log
|
||||||
- **Responsive Grid**: Uses CSS Grid for flexible, responsive layout
|
- **View Lifecycle Hooks**: `onActivate()`, `onDeactivate()`, and `canDeactivate()` for view components
|
||||||
- **Slot Support**: Main content area supports custom content via slots
|
- **Hash-based Routing**: Automatic URL synchronization with view navigation
|
||||||
|
- **RxJS Observables**: `viewChanged$` and `viewLifecycle$` for reactive programming
|
||||||
|
- **TypeScript-first**: Typed `IViewActivationContext` passed to views on activation
|
||||||
|
|
||||||
|
**Programmatic APIs include:**
|
||||||
|
- `navigateToView(viewId, params?)` - Navigate between views
|
||||||
|
- `setAppBarMenus()`, `setBreadcrumbs()`, `setUser()` - Control the app bar
|
||||||
|
- `setMainMenu()`, `setMainMenuSelection()`, `setMainMenuBadge()` - Control main navigation
|
||||||
|
- `setSecondaryMenu()`, `setContentTabs()` - Control view-specific UI
|
||||||
|
- `activityLog.add()`, `activityLog.addMany()`, `activityLog.clear()` - Manage activity entries
|
||||||
|
|
||||||
#### `DeesAppuiMainmenu`
|
#### `DeesAppuiMainmenu`
|
||||||
Main navigation menu component for application-wide navigation.
|
Main navigation menu component for application-wide navigation.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
<dees-appui-mainmenu
|
<dees-appui-mainmenu
|
||||||
.menuItems=${[
|
.menuGroups=${[
|
||||||
{
|
{
|
||||||
key: 'dashboard',
|
name: 'Main',
|
||||||
label: 'Dashboard',
|
items: [
|
||||||
icon: 'home',
|
{ key: 'dashboard', iconName: 'lucide:home', action: () => navigate('dashboard') },
|
||||||
action: () => handleNavigation('dashboard')
|
{ key: 'settings', iconName: 'lucide:settings', action: () => navigate('settings') }
|
||||||
},
|
]
|
||||||
{
|
|
||||||
key: 'settings',
|
|
||||||
label: 'Settings',
|
|
||||||
icon: 'cog',
|
|
||||||
action: () => handleNavigation('settings')
|
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
collapsed // Optional: show collapsed version
|
collapsed // Optional: show collapsed version
|
||||||
position="left" // Options: left, right
|
|
||||||
></dees-appui-mainmenu>
|
></dees-appui-mainmenu>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `DeesAppuiMainselector`
|
#### `DeesAppuiSecondarymenu`
|
||||||
Secondary navigation component for sub-section selection.
|
Secondary navigation component for sub-section selection with collapsible groups and badges.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
<dees-appui-mainselector
|
<dees-appui-secondarymenu
|
||||||
.items=${[
|
.heading=${'Projects'}
|
||||||
|
.groups=${[
|
||||||
{
|
{
|
||||||
key: 'section1',
|
name: 'Active',
|
||||||
label: 'Section 1',
|
iconName: 'lucide:folder',
|
||||||
icon: 'folder',
|
items: [
|
||||||
action: () => selectSection('section1')
|
{ key: 'Frontend App', iconName: 'lucide:code', action: () => select('frontend'), badge: 3, badgeVariant: 'warning' },
|
||||||
|
{ key: 'API Server', iconName: 'lucide:server', action: () => select('api') }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
selectedKey="section1" // Currently selected section
|
@item-select=${handleSectionChange}
|
||||||
@selection-change=${handleSectionChange}
|
></dees-appui-secondarymenu>
|
||||||
></dees-appui-mainselector>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `DeesAppuiMaincontent`
|
#### `DeesAppuiMaincontent`
|
||||||
@@ -703,16 +713,13 @@ Main content area with tab management support.
|
|||||||
```typescript
|
```typescript
|
||||||
<dees-appui-maincontent
|
<dees-appui-maincontent
|
||||||
.tabs=${[
|
.tabs=${[
|
||||||
{
|
{ key: 'Overview', iconName: 'lucide:home', action: () => selectTab('overview') },
|
||||||
key: 'tab1',
|
{ key: 'Details', iconName: 'lucide:info', action: () => selectTab('details') }
|
||||||
label: 'Tab 1',
|
|
||||||
content: html`<div>Tab 1 Content</div>`,
|
|
||||||
action: () => handleTabAction('tab1')
|
|
||||||
}
|
|
||||||
]}
|
]}
|
||||||
selectedTab="tab1" // Currently active tab
|
@tab-select=${handleTabChange}
|
||||||
@tab-change=${handleTabChange}
|
>
|
||||||
></dees-appui-maincontent>
|
<!-- Content goes here -->
|
||||||
|
</dees-appui-maincontent>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `DeesAppuiAppbar`
|
#### `DeesAppuiAppbar`
|
||||||
@@ -746,70 +753,44 @@ Professional application bar component with hierarchical menus, breadcrumb navig
|
|||||||
disabled: true // Disabled state
|
disabled: true // Disabled state
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Edit',
|
|
||||||
action: async () => {},
|
|
||||||
submenu: [
|
|
||||||
{ name: 'Undo', shortcut: 'Cmd+Z', iconName: 'undo', action: async () => handleUndo() },
|
|
||||||
{ name: 'Redo', shortcut: 'Cmd+Shift+Z', iconName: 'redo', action: async () => handleRedo() }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
.breadcrumbs=${'Project > src > components > AppBar.ts'}
|
.breadcrumbs=${'Project > src > components'}
|
||||||
.breadcrumbSeparator=${' > '}
|
|
||||||
.showWindowControls=${true}
|
.showWindowControls=${true}
|
||||||
.showSearch=${true}
|
.showSearch=${true}
|
||||||
.theme=${'dark'} // Options: 'light' | 'dark'
|
|
||||||
.user=${{
|
.user=${{
|
||||||
name: 'John Doe',
|
name: 'John Doe',
|
||||||
avatar: '/path/to/avatar.jpg', // Optional
|
avatar: '/path/to/avatar.jpg',
|
||||||
status: 'online' // Options: 'online' | 'offline' | 'busy' | 'away'
|
status: 'online' // Options: 'online' | 'offline' | 'busy' | 'away'
|
||||||
}}
|
}}
|
||||||
@menu-select=${(e) => handleMenuSelect(e.detail.item)}
|
@menu-select=${(e) => handleMenuSelect(e.detail.item)}
|
||||||
@breadcrumb-navigate=${(e) => handleBreadcrumbClick(e.detail)}
|
@breadcrumb-navigate=${(e) => handleBreadcrumbClick(e.detail)}
|
||||||
@search-click=${() => handleSearchClick()}
|
|
||||||
@user-menu-open=${() => handleUserMenuOpen()}
|
|
||||||
></dees-appui-appbar>
|
></dees-appui-appbar>
|
||||||
```
|
```
|
||||||
|
|
||||||
Key Features:
|
**Key Features:**
|
||||||
- **Hierarchical Menu System**
|
- **Hierarchical Menu System** - Top-level menus with dropdown submenus, icons, and keyboard shortcuts
|
||||||
- Top-level text-only menus (following desktop UI standards)
|
- **Keyboard Navigation** - Full keyboard support (Tab, Arrow keys, Enter, Escape)
|
||||||
- Dropdown submenus with icons and keyboard shortcuts
|
- **Breadcrumb Navigation** - Customizable breadcrumb trail with click events
|
||||||
- Support for nested submenus
|
- **User Account Section** - Avatar with status indicator
|
||||||
- Menu dividers for visual grouping
|
- **Accessibility** - Full ARIA support with menubar roles
|
||||||
- Disabled state support
|
|
||||||
|
|
||||||
- **Keyboard Navigation**
|
#### `DeesAppuiTabs`
|
||||||
- Tab navigation between top-level items
|
Reusable tab component with horizontal/vertical layout support.
|
||||||
- Arrow keys for dropdown navigation (Up/Down in dropdowns, Left/Right between top items)
|
|
||||||
- Enter to select items
|
|
||||||
- Escape to close dropdowns
|
|
||||||
- Home/End keys for first/last item
|
|
||||||
|
|
||||||
- **Breadcrumb Navigation**
|
```typescript
|
||||||
- Customizable breadcrumb trail
|
<dees-appui-tabs
|
||||||
- Configurable separator
|
.tabs=${[
|
||||||
- Click events for navigation
|
{ key: 'Home', iconName: 'lucide:home', action: () => console.log('Home') },
|
||||||
|
{ key: 'Settings', iconName: 'lucide:settings', action: () => console.log('Settings') }
|
||||||
|
]}
|
||||||
|
tabStyle="horizontal" // Options: horizontal, vertical
|
||||||
|
showTabIndicator={true}
|
||||||
|
@tab-select=${handleTabSelect}
|
||||||
|
></dees-appui-tabs>
|
||||||
|
```
|
||||||
|
|
||||||
- **User Account Section**
|
---
|
||||||
- User avatar with fallback to initials
|
|
||||||
- Status indicator (online, offline, busy, away)
|
|
||||||
- Click handler for user menu
|
|
||||||
|
|
||||||
- **Visual Features**
|
|
||||||
- Light and dark theme support
|
|
||||||
- Smooth animations and transitions
|
|
||||||
- Window controls integration
|
|
||||||
- Search icon with click handler
|
|
||||||
- Responsive layout using CSS Grid
|
|
||||||
|
|
||||||
- **Accessibility**
|
|
||||||
- Full ARIA support (menubar, menuitem roles)
|
|
||||||
- Keyboard navigation
|
|
||||||
- Focus management
|
|
||||||
- Screen reader compatible
|
|
||||||
|
|
||||||
### Data Display Components
|
### Data Display Components
|
||||||
|
|
||||||
@@ -845,9 +826,7 @@ Advanced table component with sorting, filtering, and action support.
|
|||||||
></dees-table>
|
></dees-table>
|
||||||
```
|
```
|
||||||
|
|
||||||
##### DeesTable (Updated)
|
**Advanced Features:**
|
||||||
|
|
||||||
Newer features available in `dees-table`:
|
|
||||||
- Schema-first columns or `displayFunction` rendering
|
- Schema-first columns or `displayFunction` rendering
|
||||||
- Sorting via header clicks with `aria-sort` + `sortChange`
|
- Sorting via header clicks with `aria-sort` + `sortChange`
|
||||||
- Global search with Lucene-like syntax; modes: `table`, `data`, `server`
|
- Global search with Lucene-like syntax; modes: `table`, `data`, `server`
|
||||||
@@ -868,7 +847,7 @@ Code display component with syntax highlighting and line numbers.
|
|||||||
import { html } from '@design.estate/dees-element';
|
import { html } from '@design.estate/dees-element';
|
||||||
|
|
||||||
export const myComponent = () => {
|
export const myComponent = () => {
|
||||||
return html`<div>Hello World</div>`;
|
return html\`<div>Hello World</div>\`;
|
||||||
};
|
};
|
||||||
`}
|
`}
|
||||||
></dees-dataview-codebox>
|
></dees-dataview-codebox>
|
||||||
@@ -885,18 +864,8 @@ Status display component for complex objects with nested status indicators.
|
|||||||
combinedStatus: 'partly_ok',
|
combinedStatus: 'partly_ok',
|
||||||
combinedStatusText: 'Partially OK',
|
combinedStatusText: 'Partially OK',
|
||||||
details: [
|
details: [
|
||||||
{
|
{ name: 'Database', value: 'Connected', status: 'ok', statusText: 'OK' },
|
||||||
name: 'Database',
|
{ name: 'API Service', value: 'Degraded', status: 'partly_ok', statusText: 'Partially OK' }
|
||||||
value: 'Connected',
|
|
||||||
status: 'ok',
|
|
||||||
statusText: 'OK'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'API Service',
|
|
||||||
value: 'Degraded',
|
|
||||||
status: 'partly_ok',
|
|
||||||
statusText: 'Partially OK'
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}}
|
}}
|
||||||
></dees-dataview-statusobject>
|
></dees-dataview-statusobject>
|
||||||
@@ -910,19 +879,13 @@ PDF viewer component with navigation and zoom controls.
|
|||||||
source="path/to/document.pdf" // URL or base64 encoded PDF
|
source="path/to/document.pdf" // URL or base64 encoded PDF
|
||||||
page={1} // Current page number
|
page={1} // Current page number
|
||||||
scale={1.0} // Zoom level
|
scale={1.0} // Zoom level
|
||||||
.controls=${[ // Optional: customize available controls
|
.controls=${['zoom', 'download', 'print', 'navigation']}
|
||||||
'zoom',
|
|
||||||
'download',
|
|
||||||
'print',
|
|
||||||
'navigation'
|
|
||||||
]}
|
|
||||||
@page-change=${handlePageChange}
|
@page-change=${handlePageChange}
|
||||||
@document-loaded=${handleDocumentLoaded}
|
|
||||||
></dees-pdf>
|
></dees-pdf>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `DeesStatsGrid`
|
#### `DeesStatsGrid`
|
||||||
A responsive grid component for displaying statistical data with various visualization types including numbers, gauges, percentages, and trends.
|
A responsive grid component for displaying statistical data with various visualization types.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
<dees-statsgrid
|
<dees-statsgrid
|
||||||
@@ -935,23 +898,7 @@ A responsive grid component for displaying statistical data with various visuali
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
icon: 'faDollarSign',
|
icon: 'faDollarSign',
|
||||||
description: '+12.5% from last month',
|
description: '+12.5% from last month',
|
||||||
color: '#22c55e',
|
color: '#22c55e'
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
name: 'View Details',
|
|
||||||
iconName: 'faChartLine',
|
|
||||||
action: async () => {
|
|
||||||
console.log('Viewing revenue details');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Export Data',
|
|
||||||
iconName: 'faFileExport',
|
|
||||||
action: async () => {
|
|
||||||
console.log('Exporting revenue data');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'cpu',
|
id: 'cpu',
|
||||||
@@ -960,8 +907,7 @@ A responsive grid component for displaying statistical data with various visuali
|
|||||||
type: 'gauge',
|
type: 'gauge',
|
||||||
icon: 'faMicrochip',
|
icon: 'faMicrochip',
|
||||||
gaugeOptions: {
|
gaugeOptions: {
|
||||||
min: 0,
|
min: 0, max: 100,
|
||||||
max: 100,
|
|
||||||
thresholds: [
|
thresholds: [
|
||||||
{ value: 0, color: '#22c55e' },
|
{ value: 0, color: '#22c55e' },
|
||||||
{ value: 60, color: '#f59e0b' },
|
{ value: 60, color: '#f59e0b' },
|
||||||
@@ -969,15 +915,6 @@ A responsive grid component for displaying statistical data with various visuali
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'storage',
|
|
||||||
title: 'Storage Used',
|
|
||||||
value: 65,
|
|
||||||
type: 'percentage',
|
|
||||||
icon: 'faHardDrive',
|
|
||||||
description: '650 GB of 1 TB',
|
|
||||||
color: '#3b82f6'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'requests',
|
id: 'requests',
|
||||||
title: 'API Requests',
|
title: 'API Requests',
|
||||||
@@ -986,35 +923,10 @@ A responsive grid component for displaying statistical data with various visuali
|
|||||||
type: 'trend',
|
type: 'trend',
|
||||||
icon: 'faServer',
|
icon: 'faServer',
|
||||||
trendData: [45, 52, 38, 65, 72, 68, 75, 82, 79, 85, 88, 92]
|
trendData: [45, 52, 38, 65, 72, 68, 75, 82, 79, 85, 88, 92]
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'uptime',
|
|
||||||
title: 'System Uptime',
|
|
||||||
value: '99.95%',
|
|
||||||
type: 'text',
|
|
||||||
icon: 'faCheckCircle',
|
|
||||||
color: '#22c55e',
|
|
||||||
description: 'Last 30 days'
|
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
.gridActions=${[
|
.minTileWidth=${250}
|
||||||
{
|
.gap=${16}
|
||||||
name: 'Refresh',
|
|
||||||
iconName: 'faSync',
|
|
||||||
action: async () => {
|
|
||||||
console.log('Refreshing stats...');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Export Report',
|
|
||||||
iconName: 'faFileExport',
|
|
||||||
action: async () => {
|
|
||||||
console.log('Exporting stats report...');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
.minTileWidth=${250} // Minimum tile width in pixels
|
|
||||||
.gap=${16} // Gap between tiles in pixels
|
|
||||||
></dees-statsgrid>
|
></dees-statsgrid>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1026,11 +938,13 @@ Pagination component for navigating through large datasets.
|
|||||||
totalItems={500}
|
totalItems={500}
|
||||||
itemsPerPage={20}
|
itemsPerPage={20}
|
||||||
currentPage={1}
|
currentPage={1}
|
||||||
maxVisiblePages={7} // Maximum page numbers to display
|
maxVisiblePages={7}
|
||||||
@page-change=${handlePageChange}
|
@page-change=${handlePageChange}
|
||||||
></dees-pagination>
|
></dees-pagination>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Visualization Components
|
### Visualization Components
|
||||||
|
|
||||||
#### `DeesChartArea`
|
#### `DeesChartArea`
|
||||||
@@ -1038,7 +952,7 @@ Area chart component built on ApexCharts for visualizing time-series data.
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
<dees-chart-area
|
<dees-chart-area
|
||||||
label="System Usage" // Chart title
|
label="System Usage"
|
||||||
.series=${[
|
.series=${[
|
||||||
{
|
{
|
||||||
name: 'CPU',
|
name: 'CPU',
|
||||||
@@ -1047,14 +961,6 @@ Area chart component built on ApexCharts for visualizing time-series data.
|
|||||||
{ x: '2025-01-15T07:00:00', y: 30 },
|
{ x: '2025-01-15T07:00:00', y: 30 },
|
||||||
{ x: '2025-01-15T11:00:00', y: 20 }
|
{ x: '2025-01-15T11:00:00', y: 20 }
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Memory',
|
|
||||||
data: [
|
|
||||||
{ x: '2025-01-15T03:00:00', y: 10 },
|
|
||||||
{ x: '2025-01-15T07:00:00', y: 12 },
|
|
||||||
{ x: '2025-01-15T11:00:00', y: 10 }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
></dees-chart-area>
|
></dees-chart-area>
|
||||||
@@ -1067,22 +973,16 @@ Specialized chart component for visualizing log data and events.
|
|||||||
<dees-chart-log
|
<dees-chart-log
|
||||||
label="System Events"
|
label="System Events"
|
||||||
.data=${[
|
.data=${[
|
||||||
{
|
{ timestamp: '2025-01-15T03:00:00', event: 'Server Start', type: 'info' },
|
||||||
timestamp: '2025-01-15T03:00:00',
|
{ timestamp: '2025-01-15T03:15:00', event: 'Error Detected', type: 'error' }
|
||||||
event: 'Server Start',
|
|
||||||
type: 'info'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timestamp: '2025-01-15T03:15:00',
|
|
||||||
event: 'Error Detected',
|
|
||||||
type: 'error'
|
|
||||||
}
|
|
||||||
]}
|
]}
|
||||||
.filters=${['info', 'warning', 'error']} // Event types to display
|
.filters=${['info', 'warning', 'error']}
|
||||||
@event-click=${handleEventClick}
|
@event-click=${handleEventClick}
|
||||||
></dees-chart-log>
|
></dees-chart-log>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Dialogs & Overlays Components
|
### Dialogs & Overlays Components
|
||||||
|
|
||||||
#### `DeesModal`
|
#### `DeesModal`
|
||||||
@@ -1094,36 +994,14 @@ DeesModal.createAndShow({
|
|||||||
heading: 'Confirm Action',
|
heading: 'Confirm Action',
|
||||||
content: html`
|
content: html`
|
||||||
<dees-form>
|
<dees-form>
|
||||||
<dees-input-text
|
<dees-input-text .label=${'Enter reason'}></dees-input-text>
|
||||||
.label=${'Enter reason'}
|
|
||||||
></dees-input-text>
|
|
||||||
</dees-form>
|
</dees-form>
|
||||||
`,
|
`,
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
{
|
{ name: 'Cancel', action: async (modal) => { modal.destroy(); return null; } },
|
||||||
name: 'Cancel',
|
{ name: 'Confirm', action: async (modal) => { /* handle */ modal.destroy(); return null; } }
|
||||||
action: async (modal) => {
|
|
||||||
modal.destroy();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Confirm',
|
|
||||||
action: async (modal) => {
|
|
||||||
// Handle confirmation
|
|
||||||
modal.destroy();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Component usage
|
|
||||||
<dees-modal
|
|
||||||
heading="Settings"
|
|
||||||
.content=${settingsContent}
|
|
||||||
.menuOptions=${actions}
|
|
||||||
></dees-modal>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `DeesContextmenu`
|
#### `DeesContextmenu`
|
||||||
@@ -1132,19 +1010,10 @@ Context menu component for right-click actions.
|
|||||||
```typescript
|
```typescript
|
||||||
<dees-contextmenu
|
<dees-contextmenu
|
||||||
.items=${[
|
.items=${[
|
||||||
{
|
{ label: 'Edit', icon: 'edit', action: () => handleEdit() },
|
||||||
label: 'Edit',
|
{ label: 'Delete', icon: 'trash', action: () => handleDelete() }
|
||||||
icon: 'edit',
|
|
||||||
action: () => handleEdit()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Delete',
|
|
||||||
icon: 'trash',
|
|
||||||
action: () => handleDelete()
|
|
||||||
}
|
|
||||||
]}
|
]}
|
||||||
position="right" // Options: right, left, auto
|
position="right"
|
||||||
@item-click=${handleMenuItemClick}
|
|
||||||
></dees-contextmenu>
|
></dees-contextmenu>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1154,39 +1023,22 @@ Tooltip-style speech bubble component for contextual information.
|
|||||||
```typescript
|
```typescript
|
||||||
// Programmatic usage
|
// Programmatic usage
|
||||||
const bubble = await DeesSpeechbubble.createAndShow(
|
const bubble = await DeesSpeechbubble.createAndShow(
|
||||||
referenceElement, // Element to attach to
|
referenceElement,
|
||||||
'Helpful information about this feature'
|
'Helpful information about this feature'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Component usage
|
|
||||||
<dees-speechbubble
|
|
||||||
.reffedElement=${targetElement}
|
|
||||||
text="Click here to continue"
|
|
||||||
></dees-speechbubble>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `DeesWindowlayer`
|
#### `DeesWindowlayer`
|
||||||
Base overlay component used by modal dialogs and other overlay components.
|
Base overlay component used by modal dialogs and other overlay components.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Programmatic usage
|
|
||||||
const layer = await DeesWindowLayer.createAndShow({
|
const layer = await DeesWindowLayer.createAndShow({
|
||||||
blur: true, // Enable backdrop blur
|
blur: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Component usage
|
|
||||||
<dees-windowlayer
|
|
||||||
.options=${{
|
|
||||||
blur: true
|
|
||||||
}}
|
|
||||||
@clicked=${handleOverlayClick}
|
|
||||||
>
|
|
||||||
<div class="content">
|
|
||||||
<!-- Overlay content -->
|
|
||||||
</div>
|
|
||||||
</dees-windowlayer>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Navigation Components
|
### Navigation Components
|
||||||
|
|
||||||
#### `DeesStepper`
|
#### `DeesStepper`
|
||||||
@@ -1195,23 +1047,9 @@ Multi-step navigation component for guided user flows.
|
|||||||
```typescript
|
```typescript
|
||||||
<dees-stepper
|
<dees-stepper
|
||||||
.steps=${[
|
.steps=${[
|
||||||
{
|
{ key: 'personal', label: 'Personal Info', content: html`<div>Form 1</div>` },
|
||||||
key: 'personal',
|
{ key: 'address', label: 'Address', content: html`<div>Form 2</div>` },
|
||||||
label: 'Personal Info',
|
{ key: 'confirm', label: 'Confirmation', content: html`<div>Review</div>` }
|
||||||
content: html`<div>Personal Information Form</div>`,
|
|
||||||
validation: () => validatePersonalInfo()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'address',
|
|
||||||
label: 'Address',
|
|
||||||
content: html`<div>Address Form</div>`,
|
|
||||||
validation: () => validateAddress()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'confirm',
|
|
||||||
label: 'Confirmation',
|
|
||||||
content: html`<div>Review and Confirm</div>`
|
|
||||||
}
|
|
||||||
]}
|
]}
|
||||||
currentStep="personal"
|
currentStep="personal"
|
||||||
@step-change=${handleStepChange}
|
@step-change=${handleStepChange}
|
||||||
@@ -1224,15 +1062,16 @@ Progress indicator component for tracking completion status.
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
<dees-progressbar
|
<dees-progressbar
|
||||||
value={75} // Current progress (0-100)
|
value={75}
|
||||||
label="Uploading" // Optional label
|
label="Uploading"
|
||||||
showPercentage // Display percentage
|
showPercentage
|
||||||
type="determinate" // Options: determinate, indeterminate
|
type="determinate" // Options: determinate, indeterminate
|
||||||
status="normal" // Options: normal, success, warning, error
|
status="normal" // Options: normal, success, warning, error
|
||||||
@progress=${handleProgress}
|
|
||||||
></dees-progressbar>
|
></dees-progressbar>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Development Components
|
### Development Components
|
||||||
|
|
||||||
#### `DeesEditor`
|
#### `DeesEditor`
|
||||||
@@ -1259,12 +1098,7 @@ Markdown editor component with live preview.
|
|||||||
<dees-editor-markdown
|
<dees-editor-markdown
|
||||||
.value=${markdown}
|
.value=${markdown}
|
||||||
@change=${handleMarkdownChange}
|
@change=${handleMarkdownChange}
|
||||||
.options=${{
|
.options=${{ preview: true, toolbar: true, spellcheck: true }}
|
||||||
preview: true,
|
|
||||||
toolbar: true,
|
|
||||||
spellcheck: true,
|
|
||||||
autosave: true
|
|
||||||
}}
|
|
||||||
></dees-editor-markdown>
|
></dees-editor-markdown>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1274,9 +1108,8 @@ Markdown preview component for rendering markdown content.
|
|||||||
```typescript
|
```typescript
|
||||||
<dees-editor-markdownoutlet
|
<dees-editor-markdownoutlet
|
||||||
.markdown=${markdownContent}
|
.markdown=${markdownContent}
|
||||||
.theme=${'github'} // Options: github, dark, custom
|
.theme=${'github'}
|
||||||
.plugins=${['mermaid', 'highlight']} // Optional plugins
|
allowHtml={false}
|
||||||
allowHtml={false} // Security: disable raw HTML
|
|
||||||
></dees-editor-markdownoutlet>
|
></dees-editor-markdownoutlet>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1291,8 +1124,6 @@ Terminal emulator component for command-line interface.
|
|||||||
}}
|
}}
|
||||||
.prompt=${'$'}
|
.prompt=${'$'}
|
||||||
.welcomeMessage=${'Welcome! Type "help" for available commands.'}
|
.welcomeMessage=${'Welcome! Type "help" for available commands.'}
|
||||||
.historySize=${100}
|
|
||||||
.autoFocus={true}
|
|
||||||
></dees-terminal>
|
></dees-terminal>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1302,13 +1133,14 @@ Component for managing application updates and version control.
|
|||||||
```typescript
|
```typescript
|
||||||
<dees-updater
|
<dees-updater
|
||||||
.currentVersion=${'1.5.2'}
|
.currentVersion=${'1.5.2'}
|
||||||
.checkInterval=${3600000} // Check every hour
|
.checkInterval=${3600000}
|
||||||
.autoUpdate=${false}
|
.autoUpdate=${false}
|
||||||
@update-available=${handleUpdateAvailable}
|
@update-available=${handleUpdateAvailable}
|
||||||
@update-progress=${handleUpdateProgress}
|
|
||||||
></dees-updater>
|
></dees-updater>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Auth & Utilities Components
|
### Auth & Utilities Components
|
||||||
|
|
||||||
#### `DeesSimpleAppdash`
|
#### `DeesSimpleAppdash`
|
||||||
@@ -1321,10 +1153,7 @@ Simple application dashboard component for quick prototyping.
|
|||||||
{ name: 'Dashboard', icon: 'home', route: '/dashboard' },
|
{ name: 'Dashboard', icon: 'home', route: '/dashboard' },
|
||||||
{ name: 'Settings', icon: 'settings', route: '/settings' }
|
{ name: 'Settings', icon: 'settings', route: '/settings' }
|
||||||
]}
|
]}
|
||||||
.user=${{
|
.user=${{ name: 'John Doe', role: 'Administrator' }}
|
||||||
name: 'John Doe',
|
|
||||||
role: 'Administrator'
|
|
||||||
}}
|
|
||||||
@menu-select=${handleMenuSelect}
|
@menu-select=${handleMenuSelect}
|
||||||
>
|
>
|
||||||
<!-- Dashboard content -->
|
<!-- Dashboard content -->
|
||||||
@@ -1339,7 +1168,7 @@ Simple login form component with validation and customization.
|
|||||||
.appName=${'My Application'}
|
.appName=${'My Application'}
|
||||||
.logo=${'./assets/logo.png'}
|
.logo=${'./assets/logo.png'}
|
||||||
.backgroundImage=${'./assets/background.jpg'}
|
.backgroundImage=${'./assets/background.jpg'}
|
||||||
.fields=${['username', 'password']} // Options: username, email, password
|
.fields=${['username', 'password']}
|
||||||
showForgotPassword
|
showForgotPassword
|
||||||
showRememberMe
|
showRememberMe
|
||||||
@login=${handleLogin}
|
@login=${handleLogin}
|
||||||
@@ -1347,6 +1176,8 @@ Simple login form component with validation and customization.
|
|||||||
></dees-simple-login>
|
></dees-simple-login>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Shopping Components
|
### Shopping Components
|
||||||
|
|
||||||
#### `DeesShoppingProductcard`
|
#### `DeesShoppingProductcard`
|
||||||
@@ -1359,37 +1190,61 @@ Product card component for e-commerce applications.
|
|||||||
category: 'Electronics',
|
category: 'Electronics',
|
||||||
description: 'High-quality wireless headphones with noise cancellation',
|
description: 'High-quality wireless headphones with noise cancellation',
|
||||||
price: 199.99,
|
price: 199.99,
|
||||||
originalPrice: 249.99, // Shows strikethrough price
|
originalPrice: 249.99,
|
||||||
currency: '$',
|
currency: '$',
|
||||||
inStock: true,
|
inStock: true,
|
||||||
stockText: 'In Stock', // Custom stock text
|
imageUrl: '/images/headphones.jpg'
|
||||||
imageUrl: '/images/headphones.jpg',
|
|
||||||
iconName: 'lucide:headphones' // Fallback icon if no image
|
|
||||||
}}
|
}}
|
||||||
quantity={1} // Current quantity
|
quantity={1}
|
||||||
showQuantitySelector={true} // Show quantity selector
|
showQuantitySelector={true}
|
||||||
selectable={false} // Enable selection mode
|
@quantityChange=${handleQuantityChange}
|
||||||
selected={false} // Selection state
|
|
||||||
@quantityChange=${(e) => handleQuantityChange(e.detail)}
|
|
||||||
@selectionChange=${(e) => handleSelectionChange(e.detail)}
|
|
||||||
></dees-shopping-productcard>
|
></dees-shopping-productcard>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 TypeScript Interfaces
|
||||||
|
|
||||||
|
The library exports unified interfaces for consistent API patterns:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Base menu item interface (used by tabs, menus, etc.)
|
||||||
|
interface IMenuItem {
|
||||||
|
key: string;
|
||||||
|
iconName?: string;
|
||||||
|
action: () => void;
|
||||||
|
badge?: string | number;
|
||||||
|
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menu group interface for organized menus
|
||||||
|
interface IMenuGroup {
|
||||||
|
name: string;
|
||||||
|
items: IMenuItem[];
|
||||||
|
collapsed?: boolean;
|
||||||
|
iconName?: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## License and Legal Information
|
## License and Legal Information
|
||||||
|
|
||||||
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the license file within this repository.
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
||||||
|
|
||||||
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
### Trademarks
|
### Trademarks
|
||||||
|
|
||||||
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
|
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
||||||
|
|
||||||
|
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
||||||
|
|
||||||
### Company Information
|
### Company Information
|
||||||
|
|
||||||
Task Venture Capital GmbH
|
Task Venture Capital GmbH
|
||||||
Registered at District court Bremen HRB 35230 HB, Germany
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||||
|
|
||||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
|
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||||
import { demoFunc } from '../ts_web/elements/dees-contextmenu.demo.js';
|
import { demoFunc } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.demo.js';
|
||||||
|
|
||||||
tap.test('should render context menu demo', async () => {
|
tap.test('should render context menu demo', async () => {
|
||||||
// Create demo container
|
// Create demo container
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
|
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||||
|
|
||||||
tap.test('should close all parent menus when clicking action in nested submenu', async () => {
|
tap.test('should close all parent menus when clicking action in nested submenu', async () => {
|
||||||
let actionCalled = false;
|
let actionCalled = false;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
|
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||||
import { DeesElement, customElement, html } from '@design.estate/dees-element';
|
import { DeesElement, customElement, html } from '@design.estate/dees-element';
|
||||||
|
|
||||||
// Create a test element with shadow DOM
|
// Create a test element with shadow DOM
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
|
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||||
|
|
||||||
tap.test('should show context menu with nested submenu', async () => {
|
tap.test('should show context menu with nested submenu', async () => {
|
||||||
// Create a test element with context menu items
|
// Create a test element with context menu items
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap, webhelpers } from '@push.rocks/tapbundle';
|
import { expect, tap, webhelpers } from '@push.rocks/tapbundle';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
import { WysiwygSelection } from '../ts_web/elements/wysiwyg/wysiwyg.selection.js';
|
import { WysiwygSelection } from '../ts_web/elements/00group-input/dees-input-wysiwyg/wysiwyg.selection.js';
|
||||||
|
|
||||||
tap.test('Shadow DOM containment should work correctly', async () => {
|
tap.test('Shadow DOM containment should work correctly', async () => {
|
||||||
console.log('=== Testing Shadow DOM Containment ===');
|
console.log('=== Testing Shadow DOM Containment ===');
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
tap.test('should create wysiwyg editor', async () => {
|
tap.test('should create wysiwyg editor', async () => {
|
||||||
const editor = new DeesInputWysiwyg();
|
const editor = new DeesInputWysiwyg();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
// Initialize the element
|
// Initialize the element
|
||||||
DeesInputWysiwyg;
|
DeesInputWysiwyg;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { tap, expect, webhelpers } from '@push.rocks/tapbundle';
|
import { tap, expect, webhelpers } from '@push.rocks/tapbundle';
|
||||||
|
|
||||||
import * as deesCatalog from '../ts_web/index.js';
|
import * as deesCatalog from '../ts_web/index.js';
|
||||||
import { BlockRegistry } from '../ts_web/elements/wysiwyg/blocks/block.registry.js';
|
import { BlockRegistry } from '../ts_web/elements/00group-input/dees-input-wysiwyg/blocks/block.registry.js';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
// Import block registration to ensure handlers are registered
|
// Import block registration to ensure handlers are registered
|
||||||
import '../ts_web/elements/wysiwyg/wysiwyg.blockregistration.js';
|
import '../ts_web/elements/00group-input/dees-input-wysiwyg/wysiwyg.blockregistration.js';
|
||||||
|
|
||||||
tap.test('Debug: should create empty wysiwyg block component', async () => {
|
tap.test('Debug: should create empty wysiwyg block component', async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { tap, expect, webhelpers } from '@push.rocks/tapbundle';
|
import { tap, expect, webhelpers } from '@push.rocks/tapbundle';
|
||||||
|
|
||||||
import * as deesCatalog from '../ts_web/index.js';
|
import * as deesCatalog from '../ts_web/index.js';
|
||||||
import { BlockRegistry } from '../ts_web/elements/wysiwyg/blocks/block.registry.js';
|
import { BlockRegistry } from '../ts_web/elements/00group-input/dees-input-wysiwyg/blocks/block.registry.js';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
// Import block registration to ensure handlers are registered
|
// Import block registration to ensure handlers are registered
|
||||||
import '../ts_web/elements/wysiwyg/wysiwyg.blockregistration.js';
|
import '../ts_web/elements/00group-input/dees-input-wysiwyg/wysiwyg.blockregistration.js';
|
||||||
|
|
||||||
tap.test('BlockRegistry should have registered handlers', async () => {
|
tap.test('BlockRegistry should have registered handlers', async () => {
|
||||||
// Test divider handler
|
// Test divider handler
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
|
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||||
|
|
||||||
tap.test('should change block type via context menu', async () => {
|
tap.test('should change block type via context menu', async () => {
|
||||||
// Create WYSIWYG editor with a paragraph
|
// Create WYSIWYG editor with a paragraph
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
|
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||||
|
|
||||||
tap.test('should show context menu on WYSIWYG blocks', async () => {
|
tap.test('should show context menu on WYSIWYG blocks', async () => {
|
||||||
// Create WYSIWYG editor
|
// Create WYSIWYG editor
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
// Initialize the element
|
// Initialize the element
|
||||||
DeesInputWysiwyg;
|
DeesInputWysiwyg;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
// Initialize the element
|
// Initialize the element
|
||||||
DeesInputWysiwyg;
|
DeesInputWysiwyg;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
// Initialize the element
|
// Initialize the element
|
||||||
DeesInputWysiwyg;
|
DeesInputWysiwyg;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
// Initialize the element
|
// Initialize the element
|
||||||
DeesInputWysiwyg;
|
DeesInputWysiwyg;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
// Initialize the element
|
// Initialize the element
|
||||||
DeesInputWysiwyg;
|
DeesInputWysiwyg;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
// Initialize the element
|
// Initialize the element
|
||||||
DeesInputWysiwyg;
|
DeesInputWysiwyg;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
tap.test('Keyboard: Arrow navigation between blocks', async () => {
|
tap.test('Keyboard: Arrow navigation between blocks', async () => {
|
||||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
tap.test('Phase 3: Quote block should render and work correctly', async () => {
|
tap.test('Phase 3: Quote block should render and work correctly', async () => {
|
||||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { tap, expect, webhelpers } from '@git.zone/tstest/tapbundle';
|
import { tap, expect, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
import { BlockRegistry } from '../ts_web/elements/wysiwyg/blocks/block.registry.js';
|
import { BlockRegistry } from '../ts_web/elements/00group-input/dees-input-wysiwyg/blocks/block.registry.js';
|
||||||
import { DividerBlockHandler } from '../ts_web/elements/wysiwyg/blocks/content/divider.block.js';
|
import { DividerBlockHandler } from '../ts_web/elements/00group-input/dees-input-wysiwyg/blocks/content/divider.block.js';
|
||||||
import { ParagraphBlockHandler } from '../ts_web/elements/wysiwyg/blocks/text/paragraph.block.js';
|
import { ParagraphBlockHandler } from '../ts_web/elements/00group-input/dees-input-wysiwyg/blocks/text/paragraph.block.js';
|
||||||
import { HeadingBlockHandler } from '../ts_web/elements/wysiwyg/blocks/text/heading.block.js';
|
import { HeadingBlockHandler } from '../ts_web/elements/00group-input/dees-input-wysiwyg/blocks/text/heading.block.js';
|
||||||
|
|
||||||
// Import block registration to ensure handlers are registered
|
// Import block registration to ensure handlers are registered
|
||||||
import '../ts_web/elements/wysiwyg/wysiwyg.blockregistration.js';
|
import '../ts_web/elements/00group-input/dees-input-wysiwyg/wysiwyg.blockregistration.js';
|
||||||
|
|
||||||
tap.test('BlockRegistry should register and retrieve handlers', async () => {
|
tap.test('BlockRegistry should register and retrieve handlers', async () => {
|
||||||
// Test divider handler
|
// Test divider handler
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
tap.test('Selection highlighting should work consistently for all block types', async () => {
|
tap.test('Selection highlighting should work consistently for all block types', async () => {
|
||||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
tap.test('Selection highlighting basic test', async () => {
|
tap.test('Selection highlighting basic test', async () => {
|
||||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { tap, expect, webhelpers } from '@git.zone/tstest/tapbundle';
|
import { tap, expect, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
tap.test('should split paragraph content on Enter key', async () => {
|
tap.test('should split paragraph content on Enter key', async () => {
|
||||||
// Create the wysiwyg editor
|
// Create the wysiwyg editor
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@design.estate/dees-catalog',
|
name: '@design.estate/dees-catalog',
|
||||||
version: '2.0.5',
|
version: '3.6.0',
|
||||||
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { html, cssManager } from '@design.estate/dees-element';
|
||||||
|
import '@design.estate/dees-wcctools/demotools';
|
||||||
|
import type { DeesAppuiActivitylog } from './dees-appui-activitylog.js';
|
||||||
|
|
||||||
|
export const demoFunc = () => {
|
||||||
|
// Create the activity log element
|
||||||
|
const activityLog = document.createElement('dees-appui-activitylog') as DeesAppuiActivitylog;
|
||||||
|
|
||||||
|
// Add demo entries after the element is connected
|
||||||
|
setTimeout(() => {
|
||||||
|
activityLog.addMany([
|
||||||
|
{ type: 'login', user: 'John Doe', message: 'logged in from Chrome on macOS' },
|
||||||
|
{ type: 'create', user: 'John Doe', message: 'created a new project "Frontend App"' },
|
||||||
|
{ type: 'update', user: 'Jane Smith', message: 'updated API documentation' },
|
||||||
|
{ type: 'view', user: 'John Doe', message: 'viewed dashboard analytics' },
|
||||||
|
{ type: 'delete', user: 'Admin', message: 'removed deprecated endpoint' },
|
||||||
|
{ type: 'custom', user: 'System', message: 'scheduled backup completed', iconName: 'lucide:database' },
|
||||||
|
{ type: 'logout', user: 'Alice Brown', message: 'logged out' },
|
||||||
|
{ type: 'create', user: 'Jane Smith', message: 'created invoice #1234' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Subscribe to updates
|
||||||
|
activityLog.entries$.subscribe((entries) => {
|
||||||
|
console.log('Activity log updated:', entries.length, 'entries');
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<dees-demowrapper>
|
||||||
|
<style>
|
||||||
|
.demo-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 600px;
|
||||||
|
background: ${cssManager.bdTheme('#f4f4f5', '#09090b')};
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="demo-container">
|
||||||
|
${activityLog}
|
||||||
|
</div>
|
||||||
|
</dees-demowrapper>
|
||||||
|
`;
|
||||||
|
};
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as plugins from '../00plugins.js';
|
|
||||||
import {
|
import {
|
||||||
DeesElement,
|
DeesElement,
|
||||||
type TemplateResult,
|
type TemplateResult,
|
||||||
@@ -7,35 +6,40 @@ import {
|
|||||||
html,
|
html,
|
||||||
css,
|
css,
|
||||||
cssManager,
|
cssManager,
|
||||||
|
state,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
import { DeesContextmenu } from '../dees-contextmenu/dees-contextmenu.js';
|
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
|
||||||
import '../dees-icon/dees-icon.js';
|
import '../../dees-icon/dees-icon.js';
|
||||||
|
import type { IActivityEntry, IActivityLogAPI } from '../../interfaces/appconfig.js';
|
||||||
|
import { demoFunc } from './dees-appui-activitylog.demo.js';
|
||||||
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
|
|
||||||
@customElement('dees-appui-activitylog')
|
@customElement('dees-appui-activitylog')
|
||||||
export class DeesAppuiActivitylog extends DeesElement {
|
export class DeesAppuiActivitylog extends DeesElement implements IActivityLogAPI {
|
||||||
// STATIC
|
// STATIC
|
||||||
public static demo = () => html`
|
public static demo = demoFunc;
|
||||||
<style>
|
|
||||||
.demo-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 600px;
|
|
||||||
background: ${cssManager.bdTheme('#f4f4f5', '#09090b')};
|
|
||||||
padding: 32px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="demo-container">
|
|
||||||
<dees-appui-activitylog></dees-appui-activitylog>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// INSTANCE
|
// INSTANCE PROPERTIES
|
||||||
|
@state()
|
||||||
|
accessor entries: IActivityEntry[] = [];
|
||||||
|
|
||||||
|
@state()
|
||||||
|
accessor searchQuery: string = '';
|
||||||
|
|
||||||
|
@state()
|
||||||
|
accessor filterCriteria: { user?: string; type?: IActivityEntry['type'] } = {};
|
||||||
|
|
||||||
|
// RxJS Subject for reactive updates
|
||||||
|
public entries$ = new domtools.plugins.smartrx.rxjs.Subject<IActivityEntry[]>();
|
||||||
|
|
||||||
|
// STYLES
|
||||||
public static styles = [
|
public static styles = [
|
||||||
|
themeDefaultStyles,
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
|
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||||
:host {
|
:host {
|
||||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -63,13 +67,14 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
.topbar {
|
.topbar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
height: 40px;
|
height: 48px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0px 16px;
|
padding: 0px 16px;
|
||||||
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
|
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
|
||||||
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topbar .heading {
|
.topbar .heading {
|
||||||
@@ -81,7 +86,7 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
|
|
||||||
.activityContainer {
|
.activityContainer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 40px;
|
top: 48px;
|
||||||
bottom: 48px;
|
bottom: 48px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px 0px;
|
padding: 12px 0px;
|
||||||
@@ -107,6 +112,14 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
background: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
|
background: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 32px 16px;
|
||||||
|
color: ${cssManager.bdTheme('#71717a', '#71717a')};
|
||||||
|
font-family: 'Geist Sans', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
.streamingIndicator {
|
.streamingIndicator {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -136,9 +149,18 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
50% { opacity: 1; transform: scale(1.2); }
|
50% { opacity: 1; transform: scale(1.2); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.streamingIndicator.bottom {
|
.date-separator {
|
||||||
padding-top: 8px;
|
padding: 12px 16px 8px;
|
||||||
padding-bottom: 16px;
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: ${cssManager.bdTheme('#71717a', '#71717a')};
|
||||||
|
background: ${cssManager.bdTheme('#f9fafb', '#09090b')};
|
||||||
|
border-bottom: 1px solid ${cssManager.bdTheme('#f4f4f5', '#18181b')};
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activityentry {
|
.activityentry {
|
||||||
@@ -219,6 +241,16 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
color: ${cssManager.bdTheme('#ea580c', '#fb923c')};
|
color: ${cssManager.bdTheme('#ea580c', '#fb923c')};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.activity-icon.delete {
|
||||||
|
background: ${cssManager.bdTheme('rgba(239, 68, 68, 0.1)', 'rgba(239, 68, 68, 0.1)')};
|
||||||
|
color: ${cssManager.bdTheme('#dc2626', '#ef4444')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-icon.custom {
|
||||||
|
background: ${cssManager.bdTheme('rgba(100, 116, 139, 0.1)', 'rgba(100, 116, 139, 0.1)')};
|
||||||
|
color: ${cssManager.bdTheme('#475569', '#94a3b8')};
|
||||||
|
}
|
||||||
|
|
||||||
.activity-text {
|
.activity-text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
color: ${cssManager.bdTheme('#18181b', '#e4e4e7')};
|
color: ${cssManager.bdTheme('#18181b', '#e4e4e7')};
|
||||||
@@ -229,20 +261,6 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-separator {
|
|
||||||
padding: 12px 16px 8px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
color: ${cssManager.bdTheme('#71717a', '#71717a')};
|
|
||||||
background: ${cssManager.bdTheme('#f9fafb', '#09090b')};
|
|
||||||
border-bottom: 1px solid ${cssManager.bdTheme('#f4f4f5', '#18181b')};
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.searchbox {
|
.searchbox {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
@@ -315,7 +333,7 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
top: 40px;
|
top: 48px;
|
||||||
background: ${cssManager.bdTheme(
|
background: ${cssManager.bdTheme(
|
||||||
'linear-gradient(0deg, transparent 0%, #fafafa 100%)',
|
'linear-gradient(0deg, transparent 0%, #fafafa 100%)',
|
||||||
'linear-gradient(0deg, transparent 0%, #0a0a0a 100%)'
|
'linear-gradient(0deg, transparent 0%, #0a0a0a 100%)'
|
||||||
@@ -326,7 +344,11 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// RENDER
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
|
const filteredEntries = this.getFilteredEntries();
|
||||||
|
const groupedEntries = this.groupEntriesByDate(filteredEntries);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${domtools.elementBasic.styles}
|
${domtools.elementBasic.styles}
|
||||||
<style></style>
|
<style></style>
|
||||||
@@ -335,173 +357,28 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
<div class="heading">Activity Log</div>
|
<div class="heading">Activity Log</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="activityContainer">
|
<div class="activityContainer">
|
||||||
<div class="streamingIndicator">Live Updates</div>
|
${filteredEntries.length > 0
|
||||||
|
? html`<div class="streamingIndicator">Live Updates</div>`
|
||||||
|
: ''}
|
||||||
|
|
||||||
<div class="date-separator">Today</div>
|
${filteredEntries.length === 0
|
||||||
|
? html`<div class="empty-state">No activity entries</div>`
|
||||||
<div class="activityentry" @contextmenu=${async eventArg => {
|
: groupedEntries.map(
|
||||||
DeesContextmenu.openContextMenuWithOptions(eventArg, [
|
(group) => html`
|
||||||
{
|
<div class="date-separator">${group.label}</div>
|
||||||
name: 'Copy activity',
|
${group.entries.map((entry) => this.renderActivityEntry(entry))}
|
||||||
action: async () => {},
|
`
|
||||||
},
|
)}
|
||||||
{
|
|
||||||
name: 'View details',
|
|
||||||
action: async () => {},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Filter by user',
|
|
||||||
action: async () => {},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}}>
|
|
||||||
<span class="timestamp">22:20</span>
|
|
||||||
<div class="activity-icon logout">
|
|
||||||
<dees-icon .icon=${'lucide:logOut'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> logged out
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">22:19</span>
|
|
||||||
<div class="activity-icon update">
|
|
||||||
<dees-icon .icon=${'lucide:checkCircle'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> approved a payment
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">22:18</span>
|
|
||||||
<div class="activity-icon view">
|
|
||||||
<dees-icon .icon=${'lucide:archive'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> archived an invoice
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">22:17</span>
|
|
||||||
<div class="activity-icon login">
|
|
||||||
<dees-icon .icon=${'lucide:logIn'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> logged in
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">22:16</span>
|
|
||||||
<div class="activity-icon logout">
|
|
||||||
<dees-icon .icon=${'lucide:logOut'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> logged out
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">22:15</span>
|
|
||||||
<div class="activity-icon update">
|
|
||||||
<dees-icon .icon=${'lucide:key'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> changed password
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">22:14</span>
|
|
||||||
<div class="activity-icon create">
|
|
||||||
<dees-icon .icon=${'lucide:userPlus'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> added a new user
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">22:13</span>
|
|
||||||
<div class="activity-icon view">
|
|
||||||
<dees-icon .icon=${'lucide:messageCircle'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> contacted support
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="date-separator">Yesterday</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">18:45</span>
|
|
||||||
<div class="activity-icon update">
|
|
||||||
<dees-icon .icon=${'lucide:trash2'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> deleted an invoice
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">17:30</span>
|
|
||||||
<div class="activity-icon login">
|
|
||||||
<dees-icon .icon=${'lucide:logIn'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> logged in
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">16:15</span>
|
|
||||||
<div class="activity-icon logout">
|
|
||||||
<dees-icon .icon=${'lucide:logOut'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> logged out
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">14:20</span>
|
|
||||||
<div class="activity-icon view">
|
|
||||||
<dees-icon .icon=${'lucide:barChart'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> viewed reports
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">13:45</span>
|
|
||||||
<div class="activity-icon create">
|
|
||||||
<dees-icon .icon=${'lucide:send'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> sent an invoice
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">13:30</span>
|
|
||||||
<div class="activity-icon create">
|
|
||||||
<dees-icon .icon=${'lucide:filePlus'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> created a new invoice
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="streamingIndicator bottom">Loading History</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="searchbox">
|
<div class="searchbox">
|
||||||
<div class="search-wrapper">
|
<div class="search-wrapper">
|
||||||
<dees-icon class="search-icon" .icon=${'lucide:search'}></dees-icon>
|
<dees-icon class="search-icon" .icon=${'lucide:search'}></dees-icon>
|
||||||
<input type="text" placeholder="Search activities, users..." />
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search activities, users..."
|
||||||
|
.value=${this.searchQuery}
|
||||||
|
@input=${this.handleSearchInput}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="topShadow"></div>
|
<div class="topShadow"></div>
|
||||||
@@ -509,4 +386,205 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderActivityEntry(entry: IActivityEntry): TemplateResult {
|
||||||
|
const timestamp = entry.timestamp || new Date();
|
||||||
|
const timeStr = this.formatTime(timestamp);
|
||||||
|
const iconName = entry.iconName || this.getIconForType(entry.type);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="activityentry"
|
||||||
|
@contextmenu=${(e: MouseEvent) => this.handleContextMenu(e, entry)}
|
||||||
|
>
|
||||||
|
<span class="timestamp">${timeStr}</span>
|
||||||
|
<div class="activity-icon ${entry.type}">
|
||||||
|
<dees-icon .icon=${iconName}></dees-icon>
|
||||||
|
</div>
|
||||||
|
<div class="activity-text">
|
||||||
|
<span class="activity-user">${entry.user}</span> ${entry.message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// API METHODS
|
||||||
|
public add(entry: IActivityEntry): void {
|
||||||
|
const newEntry: IActivityEntry = {
|
||||||
|
...entry,
|
||||||
|
id: entry.id || this.generateId(),
|
||||||
|
timestamp: entry.timestamp || new Date(),
|
||||||
|
};
|
||||||
|
this.entries = [newEntry, ...this.entries];
|
||||||
|
this.entries$.next(this.entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addMany(entries: IActivityEntry[]): void {
|
||||||
|
const newEntries = entries.map((entry) => ({
|
||||||
|
...entry,
|
||||||
|
id: entry.id || this.generateId(),
|
||||||
|
timestamp: entry.timestamp || new Date(),
|
||||||
|
}));
|
||||||
|
this.entries = [...newEntries.reverse(), ...this.entries];
|
||||||
|
this.entries$.next(this.entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear(): void {
|
||||||
|
this.entries = [];
|
||||||
|
this.entries$.next(this.entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getEntries(): IActivityEntry[] {
|
||||||
|
return [...this.entries];
|
||||||
|
}
|
||||||
|
|
||||||
|
public filter(criteria: { user?: string; type?: IActivityEntry['type'] }): IActivityEntry[] {
|
||||||
|
return this.entries.filter((entry) => {
|
||||||
|
if (criteria.user && entry.user !== criteria.user) return false;
|
||||||
|
if (criteria.type && entry.type !== criteria.type) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public search(query: string): IActivityEntry[] {
|
||||||
|
const lowerQuery = query.toLowerCase();
|
||||||
|
return this.entries.filter(
|
||||||
|
(entry) =>
|
||||||
|
entry.message.toLowerCase().includes(lowerQuery) ||
|
||||||
|
entry.user.toLowerCase().includes(lowerQuery)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRIVATE HELPERS
|
||||||
|
private generateId(): string {
|
||||||
|
return `activity-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFilteredEntries(): IActivityEntry[] {
|
||||||
|
let result = this.entries;
|
||||||
|
|
||||||
|
if (this.searchQuery) {
|
||||||
|
const lowerQuery = this.searchQuery.toLowerCase();
|
||||||
|
result = result.filter(
|
||||||
|
(entry) =>
|
||||||
|
entry.message.toLowerCase().includes(lowerQuery) ||
|
||||||
|
entry.user.toLowerCase().includes(lowerQuery)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.filterCriteria.user || this.filterCriteria.type) {
|
||||||
|
result = result.filter((entry) => {
|
||||||
|
if (this.filterCriteria.user && entry.user !== this.filterCriteria.user) return false;
|
||||||
|
if (this.filterCriteria.type && entry.type !== this.filterCriteria.type) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private groupEntriesByDate(
|
||||||
|
entries: IActivityEntry[]
|
||||||
|
): Array<{ label: string; entries: IActivityEntry[] }> {
|
||||||
|
const groups: Map<string, IActivityEntry[]> = new Map();
|
||||||
|
const today = new Date();
|
||||||
|
const yesterday = new Date(today);
|
||||||
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const date = entry.timestamp || new Date();
|
||||||
|
let label: string;
|
||||||
|
|
||||||
|
if (this.isSameDay(date, today)) {
|
||||||
|
label = 'Today';
|
||||||
|
} else if (this.isSameDay(date, yesterday)) {
|
||||||
|
label = 'Yesterday';
|
||||||
|
} else {
|
||||||
|
label = date.toLocaleDateString('en-US', {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: date.getFullYear() !== today.getFullYear() ? 'numeric' : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!groups.has(label)) {
|
||||||
|
groups.set(label, []);
|
||||||
|
}
|
||||||
|
groups.get(label)!.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(groups.entries()).map(([label, entries]) => ({
|
||||||
|
label,
|
||||||
|
entries,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private isSameDay(date1: Date, date2: Date): boolean {
|
||||||
|
return (
|
||||||
|
date1.getFullYear() === date2.getFullYear() &&
|
||||||
|
date1.getMonth() === date2.getMonth() &&
|
||||||
|
date1.getDate() === date2.getDate()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatTime(date: Date): string {
|
||||||
|
return date.toLocaleTimeString('en-US', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getIconForType(type: IActivityEntry['type']): string {
|
||||||
|
const icons: Record<IActivityEntry['type'], string> = {
|
||||||
|
login: 'lucide:logIn',
|
||||||
|
logout: 'lucide:logOut',
|
||||||
|
view: 'lucide:eye',
|
||||||
|
create: 'lucide:plus',
|
||||||
|
update: 'lucide:edit',
|
||||||
|
delete: 'lucide:trash2',
|
||||||
|
custom: 'lucide:activity',
|
||||||
|
};
|
||||||
|
return icons[type] || icons.custom;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleSearchInput(e: InputEvent): void {
|
||||||
|
const target = e.target as HTMLInputElement;
|
||||||
|
this.searchQuery = target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleContextMenu(e: MouseEvent, entry: IActivityEntry): void {
|
||||||
|
e.preventDefault();
|
||||||
|
DeesContextmenu.openContextMenuWithOptions(e, [
|
||||||
|
{
|
||||||
|
name: 'Copy activity',
|
||||||
|
iconName: 'lucide:copy',
|
||||||
|
action: async () => {
|
||||||
|
await navigator.clipboard.writeText(`${entry.user} ${entry.message}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Filter by user',
|
||||||
|
iconName: 'lucide:user',
|
||||||
|
action: async () => {
|
||||||
|
this.filterCriteria = { user: entry.user };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Filter by type',
|
||||||
|
iconName: 'lucide:filter',
|
||||||
|
action: async () => {
|
||||||
|
this.filterCriteria = { type: entry.type };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Clear filters',
|
||||||
|
iconName: 'lucide:x',
|
||||||
|
action: async () => {
|
||||||
|
this.filterCriteria = {};
|
||||||
|
this.searchQuery = '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-appui-activitylog.js';
|
||||||
@@ -8,15 +8,15 @@ import {
|
|||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
import * as interfaces from '../interfaces/index.js';
|
import * as interfaces from '../../interfaces/index.js';
|
||||||
import * as plugins from '../00plugins.js';
|
import * as plugins from '../../00plugins.js';
|
||||||
import { demoFunc } from './demo.js';
|
import { demoFunc } from './demo.js';
|
||||||
import { appuiAppbarStyles } from './styles.js';
|
import { appuiAppbarStyles } from './styles.js';
|
||||||
import { renderAppuiAppbar } from './template.js';
|
import { renderAppuiAppbar } from './template.js';
|
||||||
|
|
||||||
// Import required components
|
// Import required components
|
||||||
import '../dees-icon/dees-icon.js';
|
import '../../dees-icon/dees-icon.js';
|
||||||
import '../dees-windowcontrols/dees-windowcontrols.js';
|
import '../../dees-windowcontrols/dees-windowcontrols.js';
|
||||||
import '../dees-appui-profiledropdown/dees-appui-profiledropdown.js';
|
import '../dees-appui-profiledropdown/dees-appui-profiledropdown.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { html, css } from '@design.estate/dees-element';
|
import { html, css } from '@design.estate/dees-element';
|
||||||
import type { DeesAppuiBar } from './component.js';
|
import type { DeesAppuiBar } from './component.js';
|
||||||
import type { IAppBarMenuItem } from '../interfaces/appbarmenuitem.js';
|
import type { IAppBarMenuItem } from '../../interfaces/appbarmenuitem.js';
|
||||||
import '@design.estate/dees-wcctools/demotools';
|
import '@design.estate/dees-wcctools/demotools';
|
||||||
import './component.js';
|
import './component.js';
|
||||||
|
|
||||||
1
ts_web/elements/00group-appui/dees-appui-appbar/index.ts
Normal file
1
ts_web/elements/00group-appui/dees-appui-appbar/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './component.js';
|
||||||
@@ -0,0 +1,608 @@
|
|||||||
|
import { html, css, DeesElement, customElement, state } from '@design.estate/dees-element';
|
||||||
|
import type { DeesAppuiBase } from './dees-appui-base.js';
|
||||||
|
import type { IAppConfig, IViewActivationContext } from '../../interfaces/appconfig.js';
|
||||||
|
import '@design.estate/dees-wcctools/demotools';
|
||||||
|
|
||||||
|
// Demo view component with lifecycle hooks
|
||||||
|
@customElement('demo-dashboard-view')
|
||||||
|
class DemoDashboardView extends DeesElement {
|
||||||
|
@state()
|
||||||
|
accessor activated: boolean = false;
|
||||||
|
|
||||||
|
onActivate(context: IViewActivationContext) {
|
||||||
|
this.activated = true;
|
||||||
|
console.log('Dashboard activated with context:', context);
|
||||||
|
|
||||||
|
// Set view-specific secondary menu
|
||||||
|
context.appui.setSecondaryMenu({
|
||||||
|
heading: 'Dashboard',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: 'Quick Access',
|
||||||
|
iconName: 'lucide:zap',
|
||||||
|
items: [
|
||||||
|
{ key: 'overview', iconName: 'layoutDashboard', action: () => console.log('Overview') },
|
||||||
|
{ key: 'recent', iconName: 'clock', badge: 5, action: () => console.log('Recent') },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Analytics',
|
||||||
|
iconName: 'lucide:barChart3',
|
||||||
|
items: [
|
||||||
|
{ key: 'metrics', iconName: 'activity', action: () => console.log('Metrics') },
|
||||||
|
{ key: 'reports', iconName: 'fileText', badge: 'new', badgeVariant: 'success', action: () => console.log('Reports') },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set content tabs for dashboard
|
||||||
|
context.appui.setContentTabs([
|
||||||
|
{ key: 'Overview', iconName: 'lucide:layoutDashboard', action: () => console.log('Overview tab') },
|
||||||
|
{ key: 'Analytics', iconName: 'lucide:barChart', action: () => console.log('Analytics tab') },
|
||||||
|
{ key: 'Reports', iconName: 'lucide:fileText', action: () => console.log('Reports tab') },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeactivate() {
|
||||||
|
this.activated = false;
|
||||||
|
console.log('Dashboard deactivated');
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 40px;
|
||||||
|
color: #a3a3a3;
|
||||||
|
font-family: 'Geist Sans', 'Inter', -apple-system, sans-serif;
|
||||||
|
}
|
||||||
|
h1 { color: #fafafa; font-weight: 600; font-size: 24px; margin-bottom: 8px; }
|
||||||
|
p { color: #737373; margin-bottom: 32px; }
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: rgba(255,255,255,0.03);
|
||||||
|
border: 1px solid rgba(255,255,255,0.08);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.card h3 { color: #fafafa; font-size: 14px; font-weight: 600; margin-bottom: 8px; }
|
||||||
|
.metric { font-size: 32px; font-weight: 700; color: #fafafa; }
|
||||||
|
.status { display: inline-block; padding: 2px 8px; border-radius: 9px; font-size: 12px; }
|
||||||
|
.status.active { background: #14532d; color: #4ade80; }
|
||||||
|
</style>
|
||||||
|
<h1>Dashboard</h1>
|
||||||
|
<p>Welcome back! Here's an overview of your system.</p>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="card">
|
||||||
|
<h3>Active Users</h3>
|
||||||
|
<div class="metric">1,234</div>
|
||||||
|
<span class="status active">Online</span>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>API Calls</h3>
|
||||||
|
<div class="metric">45.2K</div>
|
||||||
|
<p style="color: #4ade80; font-size: 12px; margin: 0;">+12% from last hour</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>System Health</h3>
|
||||||
|
<div class="metric">99.9%</div>
|
||||||
|
<p style="color: #737373; font-size: 12px; margin: 0;">All systems operational</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings view with route params and canDeactivate guard
|
||||||
|
@customElement('demo-settings-view')
|
||||||
|
class DemoSettingsView extends DeesElement {
|
||||||
|
@state()
|
||||||
|
accessor section: string = 'general';
|
||||||
|
|
||||||
|
@state()
|
||||||
|
accessor hasChanges: boolean = false;
|
||||||
|
|
||||||
|
private appui: DeesAppuiBase;
|
||||||
|
|
||||||
|
onActivate(context: IViewActivationContext) {
|
||||||
|
this.appui = context.appui as any;
|
||||||
|
console.log('Settings activated with params:', context.params);
|
||||||
|
|
||||||
|
if (context.params?.section) {
|
||||||
|
this.section = context.params.section;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set settings-specific secondary menu
|
||||||
|
context.appui.setSecondaryMenu({
|
||||||
|
heading: 'Settings',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: 'Account',
|
||||||
|
iconName: 'lucide:user',
|
||||||
|
items: [
|
||||||
|
{ key: 'general', iconName: 'settings', action: () => this.showSection('general') },
|
||||||
|
{ key: 'profile', iconName: 'user', action: () => this.showSection('profile') },
|
||||||
|
{ key: 'security', iconName: 'shield', action: () => this.showSection('security') },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Preferences',
|
||||||
|
iconName: 'lucide:sliders',
|
||||||
|
items: [
|
||||||
|
{ key: 'notifications', iconName: 'bell', badge: 3, action: () => this.showSection('notifications') },
|
||||||
|
{ key: 'appearance', iconName: 'palette', action: () => this.showSection('appearance') },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
context.appui.setSecondaryMenuSelection(this.section);
|
||||||
|
|
||||||
|
// Clear content tabs for settings
|
||||||
|
context.appui.setContentTabs([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeactivate() {
|
||||||
|
console.log('Settings deactivated');
|
||||||
|
this.hasChanges = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
canDeactivate(): boolean | string {
|
||||||
|
if (this.hasChanges) {
|
||||||
|
return 'You have unsaved changes. Leave anyway?';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
showSection(section: string) {
|
||||||
|
this.section = section;
|
||||||
|
this.appui?.setSecondaryMenuSelection(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
simulateChange() {
|
||||||
|
this.hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 40px;
|
||||||
|
color: #a3a3a3;
|
||||||
|
font-family: 'Geist Sans', 'Inter', -apple-system, sans-serif;
|
||||||
|
}
|
||||||
|
h1 { color: #fafafa; font-weight: 600; font-size: 24px; margin-bottom: 8px; }
|
||||||
|
p { color: #737373; margin-bottom: 24px; }
|
||||||
|
.section-name {
|
||||||
|
background: rgba(255,255,255,0.05);
|
||||||
|
border: 1px solid rgba(255,255,255,0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #fafafa;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
color: #fbbf24;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<h1>Settings</h1>
|
||||||
|
<p>Manage your account and application preferences.</p>
|
||||||
|
<div class="section-name">
|
||||||
|
Current section: <strong>${this.section}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button @click=${() => this.simulateChange()}>Make Changes</button>
|
||||||
|
</div>
|
||||||
|
${this.hasChanges ? html`<p class="warning">You have unsaved changes. Navigation will prompt for confirmation.</p>` : ''}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Projects view
|
||||||
|
@customElement('demo-projects-view')
|
||||||
|
class DemoProjectsView extends DeesElement {
|
||||||
|
onActivate(context: IViewActivationContext) {
|
||||||
|
context.appui.setSecondaryMenu({
|
||||||
|
heading: 'Projects',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: 'My Projects',
|
||||||
|
items: [
|
||||||
|
{ key: 'active', iconName: 'folder', badge: 3, action: () => console.log('Active') },
|
||||||
|
{ key: 'archived', iconName: 'archive', action: () => console.log('Archived') },
|
||||||
|
{ key: 'shared', iconName: 'users', badge: 2, badgeVariant: 'warning', action: () => console.log('Shared') },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
context.appui.setContentTabs([
|
||||||
|
{ key: 'Grid', iconName: 'lucide:grid', action: () => console.log('Grid view') },
|
||||||
|
{ key: 'List', iconName: 'lucide:list', action: () => console.log('List view') },
|
||||||
|
{ key: 'Board', iconName: 'lucide:kanban', action: () => console.log('Board view') },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 40px;
|
||||||
|
color: #a3a3a3;
|
||||||
|
font-family: 'Geist Sans', 'Inter', -apple-system, sans-serif;
|
||||||
|
}
|
||||||
|
h1 { color: #fafafa; font-weight: 600; font-size: 24px; margin-bottom: 24px; }
|
||||||
|
.projects {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.project {
|
||||||
|
background: rgba(255,255,255,0.03);
|
||||||
|
border: 1px solid rgba(255,255,255,0.08);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
.project:hover {
|
||||||
|
border-color: rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
.project h3 { color: #fafafa; margin: 0 0 8px 0; font-size: 16px; }
|
||||||
|
.project p { color: #737373; margin: 0; font-size: 13px; }
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: #14532d;
|
||||||
|
color: #4ade80;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 9px;
|
||||||
|
font-size: 11px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<h1>Projects</h1>
|
||||||
|
<div class="projects">
|
||||||
|
<div class="project">
|
||||||
|
<h3>Frontend App <span class="badge">Active</span></h3>
|
||||||
|
<p>React-based dashboard application</p>
|
||||||
|
</div>
|
||||||
|
<div class="project">
|
||||||
|
<h3>API Server <span class="badge">Active</span></h3>
|
||||||
|
<p>Node.js REST API backend</p>
|
||||||
|
</div>
|
||||||
|
<div class="project">
|
||||||
|
<h3>Mobile App <span class="badge">Active</span></h3>
|
||||||
|
<p>React Native iOS/Android app</p>
|
||||||
|
</div>
|
||||||
|
<div class="project">
|
||||||
|
<h3>Documentation</h3>
|
||||||
|
<p>Technical documentation site</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tasks view showing inline template content
|
||||||
|
@customElement('demo-tasks-view')
|
||||||
|
class DemoTasksView extends DeesElement {
|
||||||
|
onActivate(context: IViewActivationContext) {
|
||||||
|
context.appui.setSecondaryMenu({
|
||||||
|
heading: 'Tasks',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: 'Filters',
|
||||||
|
items: [
|
||||||
|
{ key: 'all', iconName: 'list', badge: 12, action: () => console.log('All') },
|
||||||
|
{ key: 'today', iconName: 'calendar', badge: 3, action: () => console.log('Today') },
|
||||||
|
{ key: 'upcoming', iconName: 'clock', action: () => console.log('Upcoming') },
|
||||||
|
{ key: 'completed', iconName: 'checkCircle', action: () => console.log('Completed') },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
context.appui.setContentTabs([
|
||||||
|
{ key: 'List', iconName: 'lucide:list', action: () => console.log('List') },
|
||||||
|
{ key: 'Calendar', iconName: 'lucide:calendar', action: () => console.log('Calendar') },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 40px;
|
||||||
|
color: #a3a3a3;
|
||||||
|
font-family: 'Geist Sans', 'Inter', -apple-system, sans-serif;
|
||||||
|
}
|
||||||
|
h1 { color: #fafafa; font-weight: 600; font-size: 24px; margin-bottom: 24px; }
|
||||||
|
.task-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.task {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
background: rgba(255,255,255,0.03);
|
||||||
|
border: 1px solid rgba(255,255,255,0.08);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
.checkbox {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border: 2px solid #525252;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.task-text { color: #fafafa; flex: 1; }
|
||||||
|
.due-date { color: #737373; font-size: 12px; }
|
||||||
|
.priority {
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.priority.high { background: #450a0a; color: #f87171; }
|
||||||
|
.priority.medium { background: #451a03; color: #fbbf24; }
|
||||||
|
</style>
|
||||||
|
<h1>Tasks</h1>
|
||||||
|
<div class="task-list">
|
||||||
|
<div class="task">
|
||||||
|
<div class="checkbox"></div>
|
||||||
|
<span class="task-text">Review pull request #42</span>
|
||||||
|
<span class="due-date">Today</span>
|
||||||
|
<span class="priority high">High</span>
|
||||||
|
</div>
|
||||||
|
<div class="task">
|
||||||
|
<div class="checkbox"></div>
|
||||||
|
<span class="task-text">Update documentation</span>
|
||||||
|
<span class="due-date">Tomorrow</span>
|
||||||
|
<span class="priority medium">Medium</span>
|
||||||
|
</div>
|
||||||
|
<div class="task">
|
||||||
|
<div class="checkbox"></div>
|
||||||
|
<span class="task-text">Write unit tests</span>
|
||||||
|
<span class="due-date">Dec 20</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const demoFunc = () => {
|
||||||
|
// App configuration using the new unified API
|
||||||
|
const appConfig: IAppConfig = {
|
||||||
|
branding: {
|
||||||
|
logoIcon: 'lucide:box',
|
||||||
|
logoText: 'Acme App'
|
||||||
|
},
|
||||||
|
|
||||||
|
appBar: {
|
||||||
|
menuItems: [
|
||||||
|
{
|
||||||
|
name: 'File',
|
||||||
|
action: async () => {},
|
||||||
|
submenu: [
|
||||||
|
{ name: 'New Project', shortcut: 'Cmd+N', iconName: 'filePlus', action: async () => console.log('New') },
|
||||||
|
{ name: 'Open...', shortcut: 'Cmd+O', iconName: 'folderOpen', action: async () => console.log('Open') },
|
||||||
|
{ name: 'Recent Projects', action: async () => {}, submenu: [
|
||||||
|
{ name: 'my-app', action: async () => console.log('Open my-app') },
|
||||||
|
{ name: 'component-lib', action: async () => console.log('Open component-lib') },
|
||||||
|
]},
|
||||||
|
{ divider: true },
|
||||||
|
{ name: 'Save All', shortcut: 'Cmd+S', iconName: 'save', action: async () => console.log('Save') },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Edit',
|
||||||
|
action: async () => {},
|
||||||
|
submenu: [
|
||||||
|
{ name: 'Undo', shortcut: 'Cmd+Z', iconName: 'undo', action: async () => console.log('Undo') },
|
||||||
|
{ name: 'Redo', shortcut: 'Cmd+Shift+Z', iconName: 'redo', action: async () => console.log('Redo') },
|
||||||
|
{ divider: true },
|
||||||
|
{ name: 'Cut', shortcut: 'Cmd+X', iconName: 'scissors', action: async () => console.log('Cut') },
|
||||||
|
{ name: 'Copy', shortcut: 'Cmd+C', iconName: 'copy', action: async () => console.log('Copy') },
|
||||||
|
{ name: 'Paste', shortcut: 'Cmd+V', iconName: 'clipboard', action: async () => console.log('Paste') },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'View',
|
||||||
|
action: async () => {},
|
||||||
|
submenu: [
|
||||||
|
{ name: 'Toggle Sidebar', shortcut: 'Cmd+B', action: async () => console.log('Toggle sidebar') },
|
||||||
|
{ name: 'Toggle Activity Log', shortcut: 'Cmd+Shift+A', action: async () => console.log('Toggle activity') },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Help',
|
||||||
|
action: async () => {},
|
||||||
|
submenu: [
|
||||||
|
{ name: 'Documentation', iconName: 'book', action: async () => console.log('Docs') },
|
||||||
|
{ name: 'Keyboard Shortcuts', iconName: 'keyboard', shortcut: 'Cmd+/', action: async () => console.log('Shortcuts') },
|
||||||
|
{ divider: true },
|
||||||
|
{ name: 'About', iconName: 'info', action: async () => console.log('About') },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
breadcrumbs: 'Dashboard',
|
||||||
|
showWindowControls: true,
|
||||||
|
showSearch: true,
|
||||||
|
user: {
|
||||||
|
name: 'Jane Smith',
|
||||||
|
email: 'jane.smith@example.com',
|
||||||
|
status: 'online'
|
||||||
|
},
|
||||||
|
profileMenuItems: [
|
||||||
|
{ name: 'Profile', iconName: 'user', action: async () => console.log('Profile') },
|
||||||
|
{ name: 'Account Settings', iconName: 'settings', action: async () => console.log('Settings') },
|
||||||
|
{ divider: true },
|
||||||
|
{ name: 'Help & Support', iconName: 'helpCircle', action: async () => console.log('Help') },
|
||||||
|
{ divider: true },
|
||||||
|
{ name: 'Sign Out', iconName: 'logOut', action: async () => console.log('Sign out') }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
views: [
|
||||||
|
{
|
||||||
|
id: 'dashboard',
|
||||||
|
name: 'Dashboard',
|
||||||
|
iconName: 'lucide:home',
|
||||||
|
content: 'demo-dashboard-view',
|
||||||
|
route: 'dashboard'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'projects',
|
||||||
|
name: 'Projects',
|
||||||
|
iconName: 'lucide:folder',
|
||||||
|
content: 'demo-projects-view',
|
||||||
|
route: 'projects',
|
||||||
|
badge: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tasks',
|
||||||
|
name: 'Tasks',
|
||||||
|
iconName: 'lucide:checkSquare',
|
||||||
|
content: 'demo-tasks-view',
|
||||||
|
route: 'tasks',
|
||||||
|
badge: 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'settings',
|
||||||
|
name: 'Settings',
|
||||||
|
iconName: 'lucide:settings',
|
||||||
|
content: 'demo-settings-view',
|
||||||
|
route: 'settings/:section?'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
mainMenu: {
|
||||||
|
sections: [
|
||||||
|
{ name: 'Main', views: ['dashboard'] },
|
||||||
|
{ name: 'Workspace', views: ['projects', 'tasks'] },
|
||||||
|
],
|
||||||
|
bottomItems: ['settings']
|
||||||
|
},
|
||||||
|
|
||||||
|
defaultView: 'dashboard',
|
||||||
|
|
||||||
|
onViewChange: (viewId, view) => {
|
||||||
|
console.log(`View changed to: ${viewId} (${view.name})`);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSearch: (query) => {
|
||||||
|
console.log('Search query:', query);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use a container element to properly initialize the demo
|
||||||
|
const containerElement = document.createElement('div');
|
||||||
|
containerElement.className = 'demo-container';
|
||||||
|
containerElement.style.cssText = 'position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden;';
|
||||||
|
|
||||||
|
const appuiElement = document.createElement('dees-appui-base') as DeesAppuiBase;
|
||||||
|
containerElement.appendChild(appuiElement);
|
||||||
|
|
||||||
|
// Initialize after element is connected
|
||||||
|
setTimeout(async () => {
|
||||||
|
await appuiElement.updateComplete;
|
||||||
|
|
||||||
|
// Configure using the unified API
|
||||||
|
appuiElement.configure(appConfig);
|
||||||
|
|
||||||
|
// Add demo activity entries
|
||||||
|
setTimeout(() => {
|
||||||
|
appuiElement.activityLog.addMany([
|
||||||
|
{
|
||||||
|
type: 'login',
|
||||||
|
user: 'Jane Smith',
|
||||||
|
message: 'logged in from Chrome on macOS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'create',
|
||||||
|
user: 'Jane Smith',
|
||||||
|
message: 'created project "Frontend App"'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'update',
|
||||||
|
user: 'John Doe',
|
||||||
|
message: 'updated API documentation'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'view',
|
||||||
|
user: 'Jane Smith',
|
||||||
|
message: 'viewed dashboard analytics'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'delete',
|
||||||
|
user: 'Admin',
|
||||||
|
message: 'removed deprecated endpoint'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'custom',
|
||||||
|
user: 'System',
|
||||||
|
message: 'scheduled backup completed',
|
||||||
|
iconName: 'lucide:database'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
// Subscribe to view changes
|
||||||
|
appuiElement.viewChanged$.subscribe((event) => {
|
||||||
|
console.log('View changed event:', event);
|
||||||
|
// Update breadcrumbs based on view
|
||||||
|
appuiElement.setBreadcrumbs(event.view.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Subscribe to lifecycle events
|
||||||
|
appuiElement.viewLifecycle$.subscribe((event) => {
|
||||||
|
console.log('Lifecycle event:', event.type, event.viewId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Demo: Dynamically update a badge after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
appuiElement.setMainMenuBadge('tasks', 15);
|
||||||
|
appuiElement.activityLog.add({
|
||||||
|
type: 'update',
|
||||||
|
user: 'System',
|
||||||
|
message: 'new tasks added'
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<dees-demowrapper>
|
||||||
|
${containerElement}
|
||||||
|
</dees-demowrapper>
|
||||||
|
`;
|
||||||
|
};
|
||||||
1023
ts_web/elements/00group-appui/dees-appui-base/dees-appui-base.ts
Normal file
1023
ts_web/elements/00group-appui/dees-appui-base/dees-appui-base.ts
Normal file
File diff suppressed because it is too large
Load Diff
2
ts_web/elements/00group-appui/dees-appui-base/index.ts
Normal file
2
ts_web/elements/00group-appui/dees-appui-base/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './dees-appui-base.js';
|
||||||
|
export * from './view.registry.js';
|
||||||
560
ts_web/elements/00group-appui/dees-appui-base/readme.md
Normal file
560
ts_web/elements/00group-appui/dees-appui-base/readme.md
Normal file
@@ -0,0 +1,560 @@
|
|||||||
|
# DeesAppuiBase
|
||||||
|
|
||||||
|
A comprehensive application shell component providing a complete UI framework with navigation, menus, activity logging, and view management.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { html, DeesElement, customElement } from '@design.estate/dees-element';
|
||||||
|
import { DeesAppuiBase } from '@design.estate/dees-catalog';
|
||||||
|
|
||||||
|
@customElement('my-app')
|
||||||
|
class MyApp extends DeesElement {
|
||||||
|
private appui: DeesAppuiBase;
|
||||||
|
|
||||||
|
async firstUpdated() {
|
||||||
|
this.appui = this.shadowRoot.querySelector('dees-appui-base');
|
||||||
|
|
||||||
|
// Configure with views and menu
|
||||||
|
this.appui.configure({
|
||||||
|
branding: { logoIcon: 'lucide:box', logoText: 'My App' },
|
||||||
|
views: [
|
||||||
|
{ id: 'dashboard', name: 'Dashboard', iconName: 'lucide:home', content: 'my-dashboard' },
|
||||||
|
{ id: 'settings', name: 'Settings', iconName: 'lucide:settings', content: 'my-settings' },
|
||||||
|
],
|
||||||
|
mainMenu: {
|
||||||
|
sections: [{ name: 'Main', views: ['dashboard', 'settings'] }]
|
||||||
|
},
|
||||||
|
defaultView: 'dashboard'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`<dees-appui-base></dees-appui-base>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration API
|
||||||
|
|
||||||
|
### `configure(config: IAppConfig)`
|
||||||
|
|
||||||
|
Configure the entire application shell with a single configuration object.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IAppConfig {
|
||||||
|
branding?: IBrandingConfig;
|
||||||
|
appBar?: IAppBarConfig;
|
||||||
|
views: IViewDefinition[];
|
||||||
|
mainMenu?: IMainMenuConfig;
|
||||||
|
defaultView?: string;
|
||||||
|
activityLog?: IActivityLogConfig;
|
||||||
|
onViewChange?: (viewId: string, view: IViewDefinition) => void;
|
||||||
|
onSearch?: (query: string) => void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### View Definition
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IViewDefinition {
|
||||||
|
id: string; // Unique identifier
|
||||||
|
name: string; // Display name
|
||||||
|
iconName?: string; // Icon (e.g., 'lucide:home')
|
||||||
|
content: // View content
|
||||||
|
| string // Tag name ('my-component')
|
||||||
|
| (new () => HTMLElement) // Class constructor
|
||||||
|
| (() => TemplateResult) // Template function
|
||||||
|
| (() => Promise<...>); // Async for lazy loading
|
||||||
|
secondaryMenu?: ISecondaryMenuGroup[];
|
||||||
|
contentTabs?: ITab[];
|
||||||
|
route?: string; // URL route (default: id)
|
||||||
|
badge?: string | number;
|
||||||
|
cache?: boolean; // Cache view instance (default: true)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Programmatic APIs
|
||||||
|
|
||||||
|
### App Bar API
|
||||||
|
|
||||||
|
Control the top application bar.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Set menu items (File, Edit, View, etc.)
|
||||||
|
appui.setAppBarMenus([
|
||||||
|
{
|
||||||
|
name: 'File',
|
||||||
|
submenu: [
|
||||||
|
{ name: 'New', shortcut: 'Cmd+N', action: () => {} },
|
||||||
|
{ name: 'Save', shortcut: 'Cmd+S', action: () => {} },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update single menu
|
||||||
|
appui.updateAppBarMenu('File', { submenu: [...newItems] });
|
||||||
|
|
||||||
|
// Breadcrumbs
|
||||||
|
appui.setBreadcrumbs('Dashboard > Settings > Profile');
|
||||||
|
appui.setBreadcrumbs(['Dashboard', 'Settings', 'Profile']);
|
||||||
|
|
||||||
|
// User profile
|
||||||
|
appui.setUser({
|
||||||
|
name: 'John Doe',
|
||||||
|
email: 'john@example.com',
|
||||||
|
avatar: '/avatars/john.png',
|
||||||
|
status: 'online' // 'online' | 'offline' | 'busy' | 'away'
|
||||||
|
});
|
||||||
|
|
||||||
|
appui.setProfileMenuItems([
|
||||||
|
{ name: 'Profile', iconName: 'lucide:user', action: () => {} },
|
||||||
|
{ divider: true },
|
||||||
|
{ name: 'Sign Out', iconName: 'lucide:log-out', action: () => {} }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Search
|
||||||
|
appui.setSearchVisible(true);
|
||||||
|
appui.onSearch((query) => console.log('Search:', query));
|
||||||
|
|
||||||
|
// Window controls (for Electron/Tauri apps)
|
||||||
|
appui.setWindowControlsVisible(false);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Main Menu API (Left Sidebar)
|
||||||
|
|
||||||
|
Control the main navigation menu.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Set entire menu
|
||||||
|
appui.setMainMenu({
|
||||||
|
logoIcon: 'lucide:box',
|
||||||
|
logoText: 'My App',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: 'Main',
|
||||||
|
tabs: [
|
||||||
|
{ key: 'dashboard', iconName: 'lucide:home', action: () => {} },
|
||||||
|
{ key: 'inbox', iconName: 'lucide:inbox', badge: 5, action: () => {} },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
bottomTabs: [
|
||||||
|
{ key: 'settings', iconName: 'lucide:settings', action: () => {} }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update specific group
|
||||||
|
appui.updateMainMenuGroup('Main', { tabs: [...newTabs] });
|
||||||
|
|
||||||
|
// Add/remove items
|
||||||
|
appui.addMainMenuItem('Main', { key: 'tasks', iconName: 'lucide:check', action: () => {} });
|
||||||
|
appui.removeMainMenuItem('Main', 'tasks');
|
||||||
|
|
||||||
|
// Selection
|
||||||
|
appui.setMainMenuSelection('dashboard');
|
||||||
|
appui.setMainMenuCollapsed(true);
|
||||||
|
|
||||||
|
// Badges
|
||||||
|
appui.setMainMenuBadge('inbox', 12);
|
||||||
|
appui.clearMainMenuBadge('inbox');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Secondary Menu API
|
||||||
|
|
||||||
|
Views can control the secondary (contextual) menu.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Set menu
|
||||||
|
appui.setSecondaryMenu({
|
||||||
|
heading: 'Settings',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: 'Account',
|
||||||
|
items: [
|
||||||
|
{ key: 'profile', iconName: 'lucide:user', action: () => {} },
|
||||||
|
{ key: 'security', iconName: 'lucide:shield', action: () => {} },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update group
|
||||||
|
appui.updateSecondaryMenuGroup('Account', { items: newItems });
|
||||||
|
|
||||||
|
// Add item
|
||||||
|
appui.addSecondaryMenuItem('Account', {
|
||||||
|
key: 'notifications',
|
||||||
|
iconName: 'lucide:bell',
|
||||||
|
action: () => {}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Selection
|
||||||
|
appui.setSecondaryMenuSelection('profile');
|
||||||
|
|
||||||
|
// Clear
|
||||||
|
appui.clearSecondaryMenu();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Content Tabs API
|
||||||
|
|
||||||
|
Control tabs in the main content area.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Set tabs
|
||||||
|
appui.setContentTabs([
|
||||||
|
{ key: 'code', iconName: 'lucide:code', action: () => {} },
|
||||||
|
{ key: 'preview', iconName: 'lucide:eye', action: () => {} }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Add/remove
|
||||||
|
appui.addContentTab({ key: 'debug', iconName: 'lucide:bug', action: () => {} });
|
||||||
|
appui.removeContentTab('debug');
|
||||||
|
|
||||||
|
// Select
|
||||||
|
appui.selectContentTab('preview');
|
||||||
|
|
||||||
|
// Get current
|
||||||
|
const current = appui.getSelectedContentTab();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Activity Log API
|
||||||
|
|
||||||
|
Add activity entries to the right-side activity log.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Add single entry
|
||||||
|
appui.activityLog.add({
|
||||||
|
type: 'create', // 'login' | 'logout' | 'view' | 'create' | 'update' | 'delete' | 'custom'
|
||||||
|
user: 'John Doe',
|
||||||
|
message: 'created a new invoice',
|
||||||
|
iconName: 'lucide:file-plus', // Optional custom icon
|
||||||
|
data: { invoiceId: '123' } // Optional metadata
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add multiple
|
||||||
|
appui.activityLog.addMany([...entries]);
|
||||||
|
|
||||||
|
// Clear
|
||||||
|
appui.activityLog.clear();
|
||||||
|
|
||||||
|
// Query
|
||||||
|
const entries = appui.activityLog.getEntries();
|
||||||
|
const filtered = appui.activityLog.filter({ user: 'John', type: 'create' });
|
||||||
|
const searched = appui.activityLog.search('invoice');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Navigation API
|
||||||
|
|
||||||
|
Navigate between views programmatically.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Navigate to view
|
||||||
|
await appui.navigateToView('settings');
|
||||||
|
await appui.navigateToView('settings', { section: 'profile' });
|
||||||
|
|
||||||
|
// Get current view
|
||||||
|
const current = appui.getCurrentView();
|
||||||
|
|
||||||
|
// Subscribe to view changes
|
||||||
|
appui.viewChanged$.subscribe((event) => {
|
||||||
|
console.log(`Navigated to: ${event.viewId}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Subscribe to lifecycle events
|
||||||
|
appui.viewLifecycle$.subscribe((event) => {
|
||||||
|
if (event.type === 'activated') {
|
||||||
|
console.log(`View ${event.viewId} activated`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## View Lifecycle Hooks
|
||||||
|
|
||||||
|
Views can implement lifecycle hooks to respond to activation/deactivation.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { DeesElement, customElement } from '@design.estate/dees-element';
|
||||||
|
import type { IViewActivationContext, IViewLifecycle } from '@design.estate/dees-catalog';
|
||||||
|
|
||||||
|
@customElement('my-settings-view')
|
||||||
|
class MySettingsView extends DeesElement implements IViewLifecycle {
|
||||||
|
/**
|
||||||
|
* Called when view is activated (displayed)
|
||||||
|
* Receives typed context with appui reference
|
||||||
|
*/
|
||||||
|
async onActivate(context: IViewActivationContext) {
|
||||||
|
const { appui, viewId, params } = context;
|
||||||
|
|
||||||
|
// Set view-specific secondary menu
|
||||||
|
appui.setSecondaryMenu({
|
||||||
|
heading: 'Settings',
|
||||||
|
groups: [{ name: 'Options', items: [...] }]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set view-specific tabs
|
||||||
|
appui.setContentTabs([...]);
|
||||||
|
|
||||||
|
// Load data based on route params
|
||||||
|
if (params?.section) {
|
||||||
|
await this.loadSection(params.section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when view is deactivated (hidden)
|
||||||
|
*/
|
||||||
|
onDeactivate() {
|
||||||
|
this.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before navigation away
|
||||||
|
* Return false or a message string to block navigation
|
||||||
|
*/
|
||||||
|
canDeactivate(): boolean | string {
|
||||||
|
if (this.hasUnsavedChanges) {
|
||||||
|
return 'You have unsaved changes. Leave anyway?';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### IViewActivationContext
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IViewActivationContext {
|
||||||
|
appui: DeesAppuiBase; // Reference to the app shell
|
||||||
|
viewId: string; // The view ID being activated
|
||||||
|
params?: Record<string, string>; // Route parameters
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Routing
|
||||||
|
|
||||||
|
Routes are automatically registered from view definitions using `domtools.router`.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const views = [
|
||||||
|
{ id: 'dashboard', route: 'dashboard', ... },
|
||||||
|
{ id: 'settings', route: 'settings/:section?', ... }, // Parameterized
|
||||||
|
{ id: 'user', route: 'users/:id', ... },
|
||||||
|
];
|
||||||
|
|
||||||
|
// URL: #dashboard → navigates to dashboard view
|
||||||
|
// URL: #settings/profile → navigates to settings with params.section = 'profile'
|
||||||
|
// URL: #users/123 → navigates to user with params.id = '123'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hash-based Routing
|
||||||
|
|
||||||
|
The router uses hash-based routing by default (`#viewId`). URLs are automatically synchronized when navigating via `navigateToView()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## View Caching
|
||||||
|
|
||||||
|
Views are cached by default. When navigating away and back, the same DOM element is reused (hidden/shown) rather than destroyed and recreated.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Disable caching for a specific view
|
||||||
|
{
|
||||||
|
id: 'reports',
|
||||||
|
name: 'Reports',
|
||||||
|
content: 'my-reports-view',
|
||||||
|
cache: false // Always recreate this view
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lazy Loading
|
||||||
|
|
||||||
|
Use async content functions for lazy loading views.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
id: 'analytics',
|
||||||
|
name: 'Analytics',
|
||||||
|
content: async () => {
|
||||||
|
const module = await import('./views/analytics.js');
|
||||||
|
return module.AnalyticsView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## RxJS Observables
|
||||||
|
|
||||||
|
The component exposes RxJS Subjects for reactive programming.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// View lifecycle events
|
||||||
|
appui.viewLifecycle$.subscribe((event) => {
|
||||||
|
// event.type: 'loading' | 'activated' | 'deactivated' | 'loaded' | 'loadError'
|
||||||
|
// event.viewId: string
|
||||||
|
// event.element?: HTMLElement
|
||||||
|
// event.params?: Record<string, string>
|
||||||
|
// event.error?: unknown
|
||||||
|
});
|
||||||
|
|
||||||
|
// View change events
|
||||||
|
appui.viewChanged$.subscribe((event) => {
|
||||||
|
// event.viewId: string
|
||||||
|
// event.view: IViewDefinition
|
||||||
|
// event.previousView?: IViewDefinition
|
||||||
|
// event.params?: Record<string, string>
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Complete Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { html, DeesElement, customElement } from '@design.estate/dees-element';
|
||||||
|
import { DeesAppuiBase, IViewActivationContext } from '@design.estate/dees-catalog';
|
||||||
|
|
||||||
|
@customElement('my-app')
|
||||||
|
class MyApp extends DeesElement {
|
||||||
|
private appui: DeesAppuiBase;
|
||||||
|
|
||||||
|
async firstUpdated() {
|
||||||
|
this.appui = this.shadowRoot.querySelector('dees-appui-base');
|
||||||
|
|
||||||
|
this.appui.configure({
|
||||||
|
branding: {
|
||||||
|
logoIcon: 'lucide:briefcase',
|
||||||
|
logoText: 'CRM Pro'
|
||||||
|
},
|
||||||
|
|
||||||
|
appBar: {
|
||||||
|
menuItems: [
|
||||||
|
{ name: 'File', submenu: [...] },
|
||||||
|
{ name: 'Edit', submenu: [...] }
|
||||||
|
],
|
||||||
|
showSearch: true,
|
||||||
|
user: { name: 'Jane Smith', status: 'online' }
|
||||||
|
},
|
||||||
|
|
||||||
|
views: [
|
||||||
|
{
|
||||||
|
id: 'dashboard',
|
||||||
|
name: 'Dashboard',
|
||||||
|
iconName: 'lucide:home',
|
||||||
|
content: 'crm-dashboard',
|
||||||
|
route: 'dashboard'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'contacts',
|
||||||
|
name: 'Contacts',
|
||||||
|
iconName: 'lucide:users',
|
||||||
|
content: 'crm-contacts',
|
||||||
|
route: 'contacts',
|
||||||
|
badge: 42
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'settings',
|
||||||
|
name: 'Settings',
|
||||||
|
iconName: 'lucide:settings',
|
||||||
|
content: 'crm-settings',
|
||||||
|
route: 'settings/:section?'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
mainMenu: {
|
||||||
|
sections: [
|
||||||
|
{ name: 'Main', views: ['dashboard', 'contacts'] }
|
||||||
|
],
|
||||||
|
bottomItems: ['settings']
|
||||||
|
},
|
||||||
|
|
||||||
|
defaultView: 'dashboard',
|
||||||
|
|
||||||
|
onViewChange: (viewId, view) => {
|
||||||
|
console.log(`Navigated to: ${view.name}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSearch: (query) => {
|
||||||
|
console.log(`Search: ${query}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load activity from backend
|
||||||
|
const activities = await fetch('/api/activities').then(r => r.json());
|
||||||
|
this.appui.activityLog.addMany(activities);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`<dees-appui-base></dees-appui-base>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// View with lifecycle hooks
|
||||||
|
@customElement('crm-settings')
|
||||||
|
class CrmSettings extends DeesElement {
|
||||||
|
private appui: DeesAppuiBase;
|
||||||
|
|
||||||
|
onActivate(context: IViewActivationContext) {
|
||||||
|
this.appui = context.appui;
|
||||||
|
|
||||||
|
// Set secondary menu for settings
|
||||||
|
this.appui.setSecondaryMenu({
|
||||||
|
heading: 'Settings',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: 'Account',
|
||||||
|
items: [
|
||||||
|
{ key: 'profile', iconName: 'lucide:user', action: () => this.showSection('profile') },
|
||||||
|
{ key: 'security', iconName: 'lucide:shield', action: () => this.showSection('security') }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Preferences',
|
||||||
|
items: [
|
||||||
|
{ key: 'notifications', iconName: 'lucide:bell', action: () => this.showSection('notifications') }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Navigate to section from URL params
|
||||||
|
if (context.params?.section) {
|
||||||
|
this.showSection(context.params.section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showSection(section: string) {
|
||||||
|
this.appui.setSecondaryMenuSelection(section);
|
||||||
|
// ... load section content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TypeScript Types
|
||||||
|
|
||||||
|
All interfaces are exported from `@design.estate/dees-catalog`:
|
||||||
|
|
||||||
|
- `IAppConfig` - Main configuration
|
||||||
|
- `IViewDefinition` - View definition
|
||||||
|
- `IViewActivationContext` - Context passed to `onActivate`
|
||||||
|
- `IViewLifecycle` - Lifecycle hooks interface
|
||||||
|
- `IViewLifecycleEvent` - Lifecycle event for rxjs Subject
|
||||||
|
- `IViewChangeEvent` - View change event
|
||||||
|
- `IAppUser` - User configuration
|
||||||
|
- `IActivityEntry` - Activity log entry
|
||||||
|
- `IActivityLogAPI` - Activity log methods
|
||||||
|
- `IAppBarMenuItem` - App bar menu item
|
||||||
|
- `IMainMenuConfig` - Main menu configuration
|
||||||
|
- `ISecondaryMenuGroup` - Secondary menu group
|
||||||
|
- `ITab` - Tab definition
|
||||||
402
ts_web/elements/00group-appui/dees-appui-base/view.registry.ts
Normal file
402
ts_web/elements/00group-appui/dees-appui-base/view.registry.ts
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
import { html, render, type TemplateResult } from '@design.estate/dees-element';
|
||||||
|
import type {
|
||||||
|
IViewDefinition,
|
||||||
|
IViewActivationContext,
|
||||||
|
IViewLifecycle,
|
||||||
|
TDeesAppuiBase
|
||||||
|
} from '../../interfaces/appconfig.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry for managing views and their lifecycle
|
||||||
|
*
|
||||||
|
* Key features:
|
||||||
|
* - View caching with hide/show pattern (not destroy/create)
|
||||||
|
* - Async content loading support (lazy loading)
|
||||||
|
* - View lifecycle hooks (onActivate, onDeactivate, canDeactivate)
|
||||||
|
*/
|
||||||
|
export class ViewRegistry {
|
||||||
|
private views: Map<string, IViewDefinition> = new Map();
|
||||||
|
private instances: Map<string, HTMLElement> = new Map();
|
||||||
|
private currentViewId: string | null = null;
|
||||||
|
private appui: TDeesAppuiBase | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the appui reference for view activation context
|
||||||
|
*/
|
||||||
|
public setAppuiRef(appui: TDeesAppuiBase): void {
|
||||||
|
this.appui = appui;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a single view
|
||||||
|
*/
|
||||||
|
public register(view: IViewDefinition): void {
|
||||||
|
if (this.views.has(view.id)) {
|
||||||
|
console.warn(`View with id "${view.id}" already registered. Overwriting.`);
|
||||||
|
}
|
||||||
|
this.views.set(view.id, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register multiple views
|
||||||
|
*/
|
||||||
|
public registerAll(views: IViewDefinition[]): void {
|
||||||
|
views.forEach((view) => this.register(view));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a view definition by ID
|
||||||
|
*/
|
||||||
|
public get(viewId: string): IViewDefinition | undefined {
|
||||||
|
return this.views.get(viewId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all registered view IDs
|
||||||
|
*/
|
||||||
|
public getViewIds(): string[] {
|
||||||
|
return Array.from(this.views.keys());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all views
|
||||||
|
*/
|
||||||
|
public getAll(): IViewDefinition[] {
|
||||||
|
return Array.from(this.views.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get route for a view
|
||||||
|
*/
|
||||||
|
public getRoute(viewId: string): string {
|
||||||
|
const view = this.views.get(viewId);
|
||||||
|
return view?.route || view?.id || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find view by route (supports parameterized routes like 'settings/:section')
|
||||||
|
*/
|
||||||
|
public findByRoute(route: string): { view: IViewDefinition; params: Record<string, string> } | undefined {
|
||||||
|
for (const view of this.views.values()) {
|
||||||
|
const viewRoute = view.route || view.id;
|
||||||
|
const params = this.matchRoute(viewRoute, route);
|
||||||
|
if (params !== null) {
|
||||||
|
return { view, params };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match a route pattern against an actual route
|
||||||
|
* Returns params if matched, null otherwise
|
||||||
|
*/
|
||||||
|
private matchRoute(pattern: string, route: string): Record<string, string> | null {
|
||||||
|
const patternParts = pattern.split('/');
|
||||||
|
const routeParts = route.split('/');
|
||||||
|
|
||||||
|
// Check for optional trailing param (ends with ?)
|
||||||
|
const hasOptionalParam = patternParts.length > 0 &&
|
||||||
|
patternParts[patternParts.length - 1].endsWith('?');
|
||||||
|
|
||||||
|
if (hasOptionalParam) {
|
||||||
|
// Allow route to be shorter by 1
|
||||||
|
if (routeParts.length < patternParts.length - 1 || routeParts.length > patternParts.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (patternParts.length !== routeParts.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params: Record<string, string> = {};
|
||||||
|
|
||||||
|
for (let i = 0; i < patternParts.length; i++) {
|
||||||
|
let part = patternParts[i];
|
||||||
|
const isOptional = part.endsWith('?');
|
||||||
|
if (isOptional) {
|
||||||
|
part = part.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part.startsWith(':')) {
|
||||||
|
// This is a parameter
|
||||||
|
const paramName = part.slice(1);
|
||||||
|
if (routeParts[i] !== undefined) {
|
||||||
|
params[paramName] = routeParts[i];
|
||||||
|
} else if (!isOptional) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (routeParts[i] !== part) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if navigation away from current view is allowed
|
||||||
|
*/
|
||||||
|
public async canLeaveCurrentView(): Promise<boolean | string> {
|
||||||
|
if (!this.currentViewId) return true;
|
||||||
|
|
||||||
|
const instance = this.instances.get(this.currentViewId);
|
||||||
|
if (!instance) return true;
|
||||||
|
|
||||||
|
const lifecycle = instance as unknown as IViewLifecycle;
|
||||||
|
if (typeof lifecycle.canDeactivate === 'function') {
|
||||||
|
return await lifecycle.canDeactivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate a view - handles caching, lifecycle, and rendering
|
||||||
|
*/
|
||||||
|
public async activateView(
|
||||||
|
viewId: string,
|
||||||
|
container: HTMLElement,
|
||||||
|
params?: Record<string, string>
|
||||||
|
): Promise<HTMLElement | null> {
|
||||||
|
const view = this.views.get(viewId);
|
||||||
|
if (!view) {
|
||||||
|
console.error(`View "${viewId}" not found in registry`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if caching is enabled for this view (default: true)
|
||||||
|
const shouldCache = view.cache !== false;
|
||||||
|
|
||||||
|
// Deactivate current view
|
||||||
|
if (this.currentViewId && this.currentViewId !== viewId) {
|
||||||
|
await this.deactivateView(this.currentViewId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for cached instance
|
||||||
|
let element = shouldCache ? this.instances.get(viewId) : undefined;
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
// Reuse cached instance - just show it
|
||||||
|
element.style.display = '';
|
||||||
|
} else {
|
||||||
|
// Create new instance
|
||||||
|
element = await this.createViewElement(view);
|
||||||
|
if (!element) {
|
||||||
|
console.error(`Failed to create element for view "${viewId}"`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to container
|
||||||
|
container.appendChild(element);
|
||||||
|
|
||||||
|
// Cache if enabled
|
||||||
|
if (shouldCache) {
|
||||||
|
this.instances.set(viewId, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentViewId = viewId;
|
||||||
|
|
||||||
|
// Call onActivate lifecycle hook
|
||||||
|
await this.callOnActivate(element, viewId, params);
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deactivate a view (hide and call lifecycle hook)
|
||||||
|
*/
|
||||||
|
private async deactivateView(viewId: string): Promise<void> {
|
||||||
|
const instance = this.instances.get(viewId);
|
||||||
|
if (!instance) return;
|
||||||
|
|
||||||
|
// Call onDeactivate lifecycle hook
|
||||||
|
const lifecycle = instance as unknown as IViewLifecycle;
|
||||||
|
if (typeof lifecycle.onDeactivate === 'function') {
|
||||||
|
await lifecycle.onDeactivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the element
|
||||||
|
instance.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a view element from its definition (supports async content)
|
||||||
|
*/
|
||||||
|
private async createViewElement(view: IViewDefinition): Promise<HTMLElement | null> {
|
||||||
|
let content = view.content;
|
||||||
|
|
||||||
|
// Handle async content (lazy loading)
|
||||||
|
if (typeof content === 'function' &&
|
||||||
|
!(content.prototype instanceof HTMLElement) &&
|
||||||
|
content.constructor.name === 'AsyncFunction') {
|
||||||
|
try {
|
||||||
|
content = await (content as () => Promise<string | (new () => HTMLElement) | (() => TemplateResult)>)();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to load async content for view "${view.id}":`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let element: HTMLElement;
|
||||||
|
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
// Tag name string
|
||||||
|
element = document.createElement(content);
|
||||||
|
} else if (typeof content === 'function') {
|
||||||
|
// Check if it's a class constructor or template function
|
||||||
|
if (content.prototype instanceof HTMLElement) {
|
||||||
|
// Element class constructor
|
||||||
|
element = new (content as new () => HTMLElement)();
|
||||||
|
} else {
|
||||||
|
// Template function - wrap in a container and use Lit's render
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'view-content-wrapper';
|
||||||
|
wrapper.style.cssText = 'display: contents;';
|
||||||
|
const template = (content as () => TemplateResult)();
|
||||||
|
render(template, wrapper);
|
||||||
|
element = wrapper;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(`Invalid content type for view "${view.id}"`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add view ID as data attribute for debugging
|
||||||
|
element.dataset.viewId = view.id;
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call onActivate lifecycle hook on a view element
|
||||||
|
*/
|
||||||
|
private async callOnActivate(
|
||||||
|
element: HTMLElement,
|
||||||
|
viewId: string,
|
||||||
|
params?: Record<string, string>
|
||||||
|
): Promise<void> {
|
||||||
|
const lifecycle = element as unknown as IViewLifecycle;
|
||||||
|
if (typeof lifecycle.onActivate === 'function') {
|
||||||
|
const context: IViewActivationContext = {
|
||||||
|
appui: this.appui!,
|
||||||
|
viewId,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
await lifecycle.onActivate(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy method - renders view without caching
|
||||||
|
* @deprecated Use activateView instead
|
||||||
|
*/
|
||||||
|
public renderView(viewId: string, container: HTMLElement): HTMLElement | null {
|
||||||
|
const view = this.views.get(viewId);
|
||||||
|
if (!view) {
|
||||||
|
console.error(`View "${viewId}" not found in registry`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For legacy compatibility, clear container
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
let element: HTMLElement;
|
||||||
|
const content = view.content;
|
||||||
|
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
element = document.createElement(content);
|
||||||
|
} else if (typeof content === 'function') {
|
||||||
|
if ((content as any).prototype instanceof HTMLElement) {
|
||||||
|
element = new (content as new () => HTMLElement)();
|
||||||
|
} else {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'view-content-wrapper';
|
||||||
|
wrapper.style.cssText = 'display: contents;';
|
||||||
|
const template = (content as () => TemplateResult)();
|
||||||
|
render(template, wrapper);
|
||||||
|
element = wrapper;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(`Invalid content type for view "${viewId}"`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(element);
|
||||||
|
this.instances.set(viewId, element);
|
||||||
|
this.currentViewId = viewId;
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get currently active view ID
|
||||||
|
*/
|
||||||
|
public getCurrentViewId(): string | null {
|
||||||
|
return this.currentViewId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cached instance of a view
|
||||||
|
*/
|
||||||
|
public getInstance(viewId: string): HTMLElement | undefined {
|
||||||
|
return this.instances.get(viewId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear a specific cached instance
|
||||||
|
*/
|
||||||
|
public clearInstance(viewId: string): void {
|
||||||
|
const instance = this.instances.get(viewId);
|
||||||
|
if (instance && instance.parentNode) {
|
||||||
|
instance.parentNode.removeChild(instance);
|
||||||
|
}
|
||||||
|
this.instances.delete(viewId);
|
||||||
|
if (this.currentViewId === viewId) {
|
||||||
|
this.currentViewId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all instances
|
||||||
|
*/
|
||||||
|
public clearInstances(): void {
|
||||||
|
for (const [viewId, instance] of this.instances) {
|
||||||
|
if (instance.parentNode) {
|
||||||
|
instance.parentNode.removeChild(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.instances.clear();
|
||||||
|
this.currentViewId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister a view
|
||||||
|
*/
|
||||||
|
public unregister(viewId: string): boolean {
|
||||||
|
this.clearInstance(viewId);
|
||||||
|
return this.views.delete(viewId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the registry
|
||||||
|
*/
|
||||||
|
public clear(): void {
|
||||||
|
this.views.clear();
|
||||||
|
this.clearInstances();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a view is registered
|
||||||
|
*/
|
||||||
|
public has(viewId: string): boolean {
|
||||||
|
return this.views.has(viewId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of registered views
|
||||||
|
*/
|
||||||
|
public get size(): number {
|
||||||
|
return this.views.size;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as interfaces from '../interfaces/index.js';
|
import * as interfaces from '../../interfaces/index.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DeesElement,
|
DeesElement,
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
import '../dees-appui-tabs/dees-appui-tabs.js';
|
import '../dees-appui-tabs/dees-appui-tabs.js';
|
||||||
import type { DeesAppuiTabs } from '../dees-appui-tabs/dees-appui-tabs.js';
|
import type { DeesAppuiTabs } from '../dees-appui-tabs/dees-appui-tabs.js';
|
||||||
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
|
|
||||||
@customElement('dees-appui-maincontent')
|
@customElement('dees-appui-maincontent')
|
||||||
export class DeesAppuiMaincontent extends DeesElement {
|
export class DeesAppuiMaincontent extends DeesElement {
|
||||||
@@ -35,16 +36,21 @@ export class DeesAppuiMaincontent extends DeesElement {
|
|||||||
@property({
|
@property({
|
||||||
type: Array,
|
type: Array,
|
||||||
})
|
})
|
||||||
accessor tabs: interfaces.ITab[] = [
|
accessor tabs: interfaces.IMenuItem[] = [
|
||||||
{ key: '⚠️ Please set tabs', action: () => console.warn('No tabs configured for maincontent') },
|
{ key: '⚠️ Please set tabs', action: () => console.warn('No tabs configured for maincontent') },
|
||||||
];
|
];
|
||||||
|
|
||||||
@property({ type: Object })
|
@property({ type: Object })
|
||||||
accessor selectedTab: interfaces.ITab | null = null;
|
accessor selectedTab: interfaces.IMenuItem | null = null;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
accessor showTabs: boolean = true;
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
|
themeDefaultStyles,
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
|
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||||
:host {
|
:host {
|
||||||
color: ${cssManager.bdTheme('#333', '#fff')};
|
color: ${cssManager.bdTheme('#333', '#fff')};
|
||||||
display: block;
|
display: block;
|
||||||
@@ -75,6 +81,14 @@ export class DeesAppuiMaincontent extends DeesElement {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host([notabs]) .topbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([notabs]) .content-area {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -109,8 +123,23 @@ export class DeesAppuiMaincontent extends DeesElement {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updated(changedProperties: Map<string | number | symbol, unknown>) {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
if (changedProperties.has('showTabs')) {
|
||||||
|
if (this.showTabs) {
|
||||||
|
this.removeAttribute('notabs');
|
||||||
|
} else {
|
||||||
|
this.setAttribute('notabs', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
|
async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
|
||||||
await super.firstUpdated(_changedProperties);
|
await super.firstUpdated(_changedProperties);
|
||||||
|
// Apply initial notabs state
|
||||||
|
if (!this.showTabs) {
|
||||||
|
this.setAttribute('notabs', '');
|
||||||
|
}
|
||||||
// Tab selection is now handled by the dees-appui-tabs component
|
// Tab selection is now handled by the dees-appui-tabs component
|
||||||
// But we need to ensure the tabs component is ready
|
// But we need to ensure the tabs component is ready
|
||||||
const tabsComponent = this.shadowRoot.querySelector('dees-appui-tabs') as DeesAppuiTabs;
|
const tabsComponent = this.shadowRoot.querySelector('dees-appui-tabs') as DeesAppuiTabs;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-appui-maincontent.js';
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { html } from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
export const demoFunc = () => html`
|
||||||
|
<style>
|
||||||
|
.demo-mainmenu-container {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
background: #1a1a1a;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.demo-mainmenu-container .spacer {
|
||||||
|
flex: 1;
|
||||||
|
background: #0f0f0f;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="demo-mainmenu-container">
|
||||||
|
<dees-appui-mainmenu
|
||||||
|
.logoIcon=${'lucide:box'}
|
||||||
|
.logoText=${'Acme App'}
|
||||||
|
.menuGroups=${[
|
||||||
|
{
|
||||||
|
tabs: [
|
||||||
|
{ key: 'Dashboard', iconName: 'lucide:home', action: () => console.log('Dashboard') },
|
||||||
|
{ key: 'Inbox', iconName: 'lucide:inbox', action: () => console.log('Inbox') },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Workspace',
|
||||||
|
tabs: [
|
||||||
|
{ key: 'Projects', iconName: 'lucide:folder', action: () => console.log('Projects') },
|
||||||
|
{ key: 'Tasks', iconName: 'lucide:checkSquare', action: () => console.log('Tasks') },
|
||||||
|
{ key: 'Documents', iconName: 'lucide:fileText', action: () => console.log('Documents') },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Analytics',
|
||||||
|
tabs: [
|
||||||
|
{ key: 'Reports', iconName: 'lucide:barChart3', action: () => console.log('Reports') },
|
||||||
|
{ key: 'Insights', iconName: 'lucide:lightbulb', action: () => console.log('Insights') },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
.bottomTabs=${[
|
||||||
|
{ key: 'Settings', iconName: 'lucide:settings', action: () => console.log('Settings') },
|
||||||
|
{ key: 'Help', iconName: 'lucide:helpCircle', action: () => console.log('Help') },
|
||||||
|
]}
|
||||||
|
></dees-appui-mainmenu>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,463 @@
|
|||||||
|
import * as plugins from '../../00plugins.js';
|
||||||
|
import * as interfaces from '../../interfaces/index.js';
|
||||||
|
import { zIndexLayers } from '../../00zindex.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DeesElement,
|
||||||
|
type TemplateResult,
|
||||||
|
property,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
css,
|
||||||
|
cssManager,
|
||||||
|
} from '@design.estate/dees-element';
|
||||||
|
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
|
||||||
|
import { demoFunc } from './dees-appui-mainmenu.demo.js';
|
||||||
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the most left menu
|
||||||
|
* usually used as organization selector
|
||||||
|
*/
|
||||||
|
@customElement('dees-appui-mainmenu')
|
||||||
|
export class DeesAppuiMainmenu extends DeesElement {
|
||||||
|
public static demo = demoFunc;
|
||||||
|
|
||||||
|
// INSTANCE
|
||||||
|
|
||||||
|
// Logo properties
|
||||||
|
@property({ type: String })
|
||||||
|
accessor logoIcon: string = '';
|
||||||
|
|
||||||
|
@property({ type: String })
|
||||||
|
accessor logoText: string = '';
|
||||||
|
|
||||||
|
// Menu groups (new way)
|
||||||
|
@property({ type: Array })
|
||||||
|
accessor menuGroups: interfaces.IMenuGroup[] = [];
|
||||||
|
|
||||||
|
// Bottom tabs (pinned to bottom)
|
||||||
|
@property({ type: Array })
|
||||||
|
accessor bottomTabs: interfaces.IMenuItem[] = [];
|
||||||
|
|
||||||
|
// Legacy tabs property (for backward compatibility)
|
||||||
|
@property({ type: Array })
|
||||||
|
accessor tabs: interfaces.IMenuItem[] = [];
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor selectedTab: interfaces.IMenuItem;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true })
|
||||||
|
accessor collapsed: boolean = false;
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
themeDefaultStyles,
|
||||||
|
cssManager.defaultStyles,
|
||||||
|
css`
|
||||||
|
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||||
|
:host {
|
||||||
|
--menu-width-expanded: 200px;
|
||||||
|
--menu-width-collapsed: 56px;
|
||||||
|
--tooltip-bg: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
||||||
|
--tooltip-fg: ${cssManager.bdTheme('#fafafa', '#18181b')};
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainContainer {
|
||||||
|
color: ${cssManager.bdTheme('#666', '#ccc')};
|
||||||
|
z-index: ${zIndexLayers.fixed.appBar};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
width: var(--menu-width-expanded);
|
||||||
|
height: 100%;
|
||||||
|
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
||||||
|
user-select: none;
|
||||||
|
border-right: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
|
||||||
|
font-family: 'Geist Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
transition: width 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .mainContainer {
|
||||||
|
width: var(--menu-width-collapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Floating collapse toggle button */
|
||||||
|
.collapse-toggle {
|
||||||
|
position: absolute;
|
||||||
|
right: -12px;
|
||||||
|
top: 24px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: ${cssManager.bdTheme('#ffffff', '#27272a')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#3f3f46')};
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: ${cssManager.bdTheme('#737373', '#a1a1aa')};
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease, background 0.15s ease;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-toggle:hover {
|
||||||
|
background: ${cssManager.bdTheme('#f4f4f5', '#3f3f46')};
|
||||||
|
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(:hover) .collapse-toggle {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-toggle dees-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logo Section */
|
||||||
|
.logoSection {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
height: 48px;
|
||||||
|
padding: 0 14px;
|
||||||
|
border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logoSection .logoIcon {
|
||||||
|
font-size: 22px;
|
||||||
|
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logoSection .logoText {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: opacity 0.2s ease, width 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .logoSection {
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .logoSection .logoText {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Middle Section (scrollable) */
|
||||||
|
.menuSection {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuSection::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuSection::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuSection::-webkit-scrollbar-thumb {
|
||||||
|
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.15)', 'rgba(255, 255, 255, 0.15)')};
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuSection::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.25)', 'rgba(255, 255, 255, 0.25)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu Group */
|
||||||
|
.menuGroup {
|
||||||
|
padding: 0 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuGroup:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groupHeader {
|
||||||
|
padding: 8px 12px 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: ${cssManager.bdTheme('#737373', '#737373')};
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: opacity 0.2s ease, max-height 0.25s ease;
|
||||||
|
max-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .groupHeader {
|
||||||
|
opacity: 0;
|
||||||
|
max-height: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groupTabs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .menuGroup {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab Item */
|
||||||
|
.tab {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
color: ${cssManager.bdTheme('#525252', '#a3a3a3')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab:hover {
|
||||||
|
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.06)')};
|
||||||
|
color: ${cssManager.bdTheme('#262626', '#e5e5e5')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab:active {
|
||||||
|
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.08)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.selectedTab {
|
||||||
|
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.08)')};
|
||||||
|
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.selectedTab::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 3px;
|
||||||
|
height: 16px;
|
||||||
|
background: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||||
|
border-radius: 0 2px 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab dees-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
opacity: 0.85;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.selectedTab dees-icon {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab .tabLabel {
|
||||||
|
flex: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: opacity 0.2s ease, width 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Collapsed tab styles */
|
||||||
|
:host([collapsed]) .tab {
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .tab .tabLabel {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .tab.selectedTab::before {
|
||||||
|
left: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tooltip for collapsed state */
|
||||||
|
.tab-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
left: 100%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
margin-left: 12px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: var(--tooltip-bg);
|
||||||
|
color: var(--tooltip-fg);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-tooltip::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -4px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
border: 4px solid transparent;
|
||||||
|
border-right-color: var(--tooltip-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .tab:hover .tab-tooltip {
|
||||||
|
opacity: 1;
|
||||||
|
transition-delay: 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bottom Section */
|
||||||
|
.bottomSection {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 8px;
|
||||||
|
border-top: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .bottomSection {
|
||||||
|
padding: 8px 4px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
public render(): TemplateResult {
|
||||||
|
// Get all tabs for selection (from groups or legacy tabs)
|
||||||
|
const allTabs = this.getAllTabs();
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="mainContainer" @contextmenu=${(eventArg: MouseEvent) => {
|
||||||
|
DeesContextmenu.openContextMenuWithOptions(eventArg, [{
|
||||||
|
name: 'app settings',
|
||||||
|
action: async () => {},
|
||||||
|
iconName: 'gear',
|
||||||
|
}])
|
||||||
|
}}>
|
||||||
|
${this.logoIcon || this.logoText ? html`
|
||||||
|
<div class="logoSection">
|
||||||
|
${this.logoIcon ? html`<dees-icon class="logoIcon" .icon="${this.logoIcon}"></dees-icon>` : ''}
|
||||||
|
${this.logoText ? html`<span class="logoText">${this.logoText}</span>` : ''}
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
<div class="menuSection">
|
||||||
|
${this.menuGroups.length > 0 ? this.renderMenuGroups() : this.renderLegacyTabs()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${this.bottomTabs.length > 0 ? html`
|
||||||
|
<div class="bottomSection">
|
||||||
|
${this.bottomTabs.map((tabArg) => this.renderTab(tabArg))}
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
<button class="collapse-toggle" @click="${() => this.toggleCollapse()}">
|
||||||
|
<dees-icon .icon="${this.collapsed ? 'lucide:chevronRight' : 'lucide:chevronLeft'}"></dees-icon>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderMenuGroups(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
${this.menuGroups.map((group) => html`
|
||||||
|
<div class="menuGroup">
|
||||||
|
${group.name ? html`<div class="groupHeader">${group.name}</div>` : ''}
|
||||||
|
<div class="groupTabs">
|
||||||
|
${group.items.map((tabArg) => this.renderTab(tabArg))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderLegacyTabs(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div class="menuGroup">
|
||||||
|
<div class="groupTabs">
|
||||||
|
${this.tabs.map((tabArg) => this.renderTab(tabArg))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderTab(tabArg: interfaces.IMenuItem): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="tab ${tabArg === this.selectedTab ? 'selectedTab' : ''}"
|
||||||
|
@click="${() => {
|
||||||
|
this.updateTab(tabArg);
|
||||||
|
}}"
|
||||||
|
>
|
||||||
|
<dees-icon .icon="${tabArg.iconName || ''}"></dees-icon>
|
||||||
|
<span class="tabLabel">${tabArg.key}</span>
|
||||||
|
<span class="tab-tooltip">${tabArg.key}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAllTabs(): interfaces.IMenuItem[] {
|
||||||
|
if (this.menuGroups.length > 0) {
|
||||||
|
const groupTabs = this.menuGroups.flatMap(group => group.items);
|
||||||
|
return [...groupTabs, ...this.bottomTabs];
|
||||||
|
}
|
||||||
|
return [...this.tabs, ...this.bottomTabs];
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTab(tabArg: interfaces.IMenuItem) {
|
||||||
|
this.selectedTab = tabArg;
|
||||||
|
this.selectedTab.action();
|
||||||
|
|
||||||
|
// Emit tab-select event
|
||||||
|
this.dispatchEvent(new CustomEvent('tab-select', {
|
||||||
|
detail: { tab: tabArg },
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated() {
|
||||||
|
const allTabs = this.getAllTabs();
|
||||||
|
if (allTabs.length > 0) {
|
||||||
|
this.updateTab(allTabs[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleCollapse(): void {
|
||||||
|
this.collapsed = !this.collapsed;
|
||||||
|
this.dispatchEvent(new CustomEvent('collapse-change', {
|
||||||
|
detail: { collapsed: this.collapsed },
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-appui-mainmenu.js';
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as plugins from '../00plugins.js';
|
import * as plugins from '../../00plugins.js';
|
||||||
import { zIndexLayers } from '../00zindex.js';
|
import { zIndexLayers } from '../../00zindex.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DeesElement,
|
DeesElement,
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
cssManager,
|
cssManager,
|
||||||
state,
|
state,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
|
|
||||||
@customElement('dees-appui-profiledropdown')
|
@customElement('dees-appui-profiledropdown')
|
||||||
export class DeesAppuiProfileDropdown extends DeesElement {
|
export class DeesAppuiProfileDropdown extends DeesElement {
|
||||||
@@ -53,8 +54,10 @@ export class DeesAppuiProfileDropdown extends DeesElement {
|
|||||||
accessor position: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' = 'top-right';
|
accessor position: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' = 'top-right';
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
|
themeDefaultStyles,
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
|
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-appui-profiledropdown.js';
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { html } from '@design.estate/dees-element';
|
||||||
|
import type * as interfaces from '../../interfaces/index.js';
|
||||||
|
|
||||||
|
export const demoFunc = () => html`
|
||||||
|
<style>
|
||||||
|
.demo-secondarymenu-container {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
background: #1a1a1a;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.demo-secondarymenu-container .spacer {
|
||||||
|
flex: 1;
|
||||||
|
background: #0f0f0f;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="demo-secondarymenu-container">
|
||||||
|
<dees-appui-secondarymenu
|
||||||
|
.heading=${'Projects'}
|
||||||
|
.groups=${[
|
||||||
|
{
|
||||||
|
name: 'Active',
|
||||||
|
iconName: 'lucide:folder',
|
||||||
|
items: [
|
||||||
|
{ key: 'Frontend App', iconName: 'code', action: () => console.log('Frontend'), badge: 3, badgeVariant: 'warning' },
|
||||||
|
{ key: 'API Server', iconName: 'server', action: () => console.log('API'), badge: 'new', badgeVariant: 'success' },
|
||||||
|
{ key: 'Database', iconName: 'database', action: () => console.log('Database') },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Archived',
|
||||||
|
iconName: 'lucide:archive',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ key: 'Legacy System', iconName: 'box', action: () => console.log('Legacy') },
|
||||||
|
{ key: 'Old API', iconName: 'server', action: () => console.log('Old API') },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Settings',
|
||||||
|
iconName: 'lucide:settings',
|
||||||
|
items: [
|
||||||
|
{ key: 'Configuration', iconName: 'sliders', action: () => console.log('Config') },
|
||||||
|
{ key: 'Integrations', iconName: 'plug', action: () => console.log('Integrations'), badge: 5, badgeVariant: 'error' },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
] as interfaces.IMenuGroup[]}
|
||||||
|
@item-select=${(e: CustomEvent) => console.log('Selected:', e.detail)}
|
||||||
|
></dees-appui-secondarymenu>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,604 @@
|
|||||||
|
import * as plugins from '../../00plugins.js';
|
||||||
|
import * as interfaces from '../../interfaces/index.js';
|
||||||
|
|
||||||
|
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
|
||||||
|
import '../../dees-icon/dees-icon.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DeesElement,
|
||||||
|
type TemplateResult,
|
||||||
|
property,
|
||||||
|
state,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
css,
|
||||||
|
cssManager,
|
||||||
|
} from '@design.estate/dees-element';
|
||||||
|
import { demoFunc } from './dees-appui-secondarymenu.demo.js';
|
||||||
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Secondary navigation menu for sub-navigation within MainMenu views
|
||||||
|
* Supports collapsible groups, badges, and dynamic headings
|
||||||
|
*/
|
||||||
|
@customElement('dees-appui-secondarymenu')
|
||||||
|
export class DeesAppuiSecondarymenu extends DeesElement {
|
||||||
|
public static demo = demoFunc;
|
||||||
|
|
||||||
|
// INSTANCE
|
||||||
|
|
||||||
|
/** Dynamic heading - typically shows the selected MainMenu item */
|
||||||
|
@property({ type: String })
|
||||||
|
accessor heading: string = 'Menu';
|
||||||
|
|
||||||
|
/** Grouped items with collapse support */
|
||||||
|
@property({ type: Array })
|
||||||
|
accessor groups: interfaces.IMenuGroup[] = [];
|
||||||
|
|
||||||
|
/** Legacy flat list support for backward compatibility */
|
||||||
|
@property({ type: Array })
|
||||||
|
accessor selectionOptions: (interfaces.IMenuItem | { divider: true })[] = [];
|
||||||
|
|
||||||
|
/** Currently selected item */
|
||||||
|
@property({ type: Object })
|
||||||
|
accessor selectedItem: interfaces.IMenuItem | null = null;
|
||||||
|
|
||||||
|
/** Internal state for collapsed groups */
|
||||||
|
@state()
|
||||||
|
accessor collapsedGroups: Set<string> = new Set();
|
||||||
|
|
||||||
|
/** Horizontal collapse state */
|
||||||
|
@property({ type: Boolean, reflect: true })
|
||||||
|
accessor collapsed: boolean = false;
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
themeDefaultStyles,
|
||||||
|
cssManager.defaultStyles,
|
||||||
|
css`
|
||||||
|
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||||
|
:host {
|
||||||
|
--sidebar-width-expanded: 240px;
|
||||||
|
--sidebar-width-collapsed: 56px;
|
||||||
|
--sidebar-bg: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
||||||
|
--sidebar-fg: ${cssManager.bdTheme('#525252', '#a3a3a3')};
|
||||||
|
--sidebar-fg-muted: ${cssManager.bdTheme('#737373', '#737373')};
|
||||||
|
--sidebar-fg-active: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||||
|
--sidebar-border: ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
|
||||||
|
--sidebar-hover: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.06)')};
|
||||||
|
--sidebar-active: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.08)')};
|
||||||
|
--sidebar-accent: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||||
|
--tooltip-bg: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
||||||
|
--tooltip-fg: ${cssManager.bdTheme('#fafafa', '#18181b')};
|
||||||
|
|
||||||
|
/* Badge colors */
|
||||||
|
--badge-default-bg: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
|
||||||
|
--badge-default-fg: ${cssManager.bdTheme('#3f3f46', '#a1a1aa')};
|
||||||
|
--badge-success-bg: ${cssManager.bdTheme('#dcfce7', '#14532d')};
|
||||||
|
--badge-success-fg: ${cssManager.bdTheme('#166534', '#4ade80')};
|
||||||
|
--badge-warning-bg: ${cssManager.bdTheme('#fef3c7', '#451a03')};
|
||||||
|
--badge-warning-fg: ${cssManager.bdTheme('#92400e', '#fbbf24')};
|
||||||
|
--badge-error-bg: ${cssManager.bdTheme('#fee2e2', '#450a0a')};
|
||||||
|
--badge-error-fg: ${cssManager.bdTheme('#991b1b', '#f87171')};
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
width: var(--sidebar-width-expanded);
|
||||||
|
background: var(--sidebar-bg);
|
||||||
|
border-right: 1px solid var(--sidebar-border);
|
||||||
|
font-family: 'Geist Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
user-select: none;
|
||||||
|
transition: width 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) {
|
||||||
|
width: var(--sidebar-width-collapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
.maincontainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Floating collapse toggle button */
|
||||||
|
.collapse-toggle {
|
||||||
|
position: absolute;
|
||||||
|
right: -12px;
|
||||||
|
top: 24px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: ${cssManager.bdTheme('#ffffff', '#27272a')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#3f3f46')};
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: ${cssManager.bdTheme('#737373', '#a1a1aa')};
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease, background 0.15s ease;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-toggle:hover {
|
||||||
|
background: ${cssManager.bdTheme('#f4f4f5', '#3f3f46')};
|
||||||
|
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(:hover) .collapse-toggle {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-toggle dees-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header Section */
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 48px;
|
||||||
|
padding: 0 16px;
|
||||||
|
border-bottom: 1px solid var(--sidebar-border);
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .heading {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--sidebar-fg-active);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: opacity 0.2s ease, width 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .header {
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .header .heading {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollable Menu Section */
|
||||||
|
.menuSection {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuSection::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuSection::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuSection::-webkit-scrollbar-thumb {
|
||||||
|
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.15)', 'rgba(255, 255, 255, 0.15)')};
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuSection::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.25)', 'rgba(255, 255, 255, 0.25)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu Group */
|
||||||
|
.menuGroup {
|
||||||
|
padding: 0 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .menuGroup {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groupHeader {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: background 0.15s ease, opacity 0.2s ease, max-height 0.25s ease;
|
||||||
|
max-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groupHeader:hover {
|
||||||
|
background: var(--sidebar-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.groupHeader .groupTitle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--sidebar-fg-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groupHeader .groupTitle dees-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groupHeader .chevron {
|
||||||
|
font-size: 12px;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
color: var(--sidebar-fg-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.groupHeader.collapsed .chevron {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide group headers when horizontally collapsed */
|
||||||
|
:host([collapsed]) .groupHeader {
|
||||||
|
opacity: 0;
|
||||||
|
max-height: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Group Items Container */
|
||||||
|
.groupItems {
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.25s ease, opacity 0.2s ease;
|
||||||
|
max-height: 500px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groupItems.collapsed {
|
||||||
|
max-height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Always show items when horizontally collapsed (regardless of group collapse state) */
|
||||||
|
:host([collapsed]) .groupItems {
|
||||||
|
max-height: none;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu Item */
|
||||||
|
.menuItem {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin: 2px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 450;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
color: var(--sidebar-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItem:hover {
|
||||||
|
background: var(--sidebar-hover);
|
||||||
|
color: var(--sidebar-fg-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItem:active {
|
||||||
|
background: var(--sidebar-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItem.selected {
|
||||||
|
background: var(--sidebar-active);
|
||||||
|
color: var(--sidebar-fg-active);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItem.selected::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 3px;
|
||||||
|
height: 16px;
|
||||||
|
background: var(--sidebar-accent);
|
||||||
|
border-radius: 0 2px 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItem dees-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
opacity: 0.7;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItem.selected dees-icon {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItem .itemLabel {
|
||||||
|
flex: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: opacity 0.2s ease, width 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Collapsed menu item styles */
|
||||||
|
:host([collapsed]) .menuItem {
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8px;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .menuItem .itemLabel {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .menuItem.selected::before {
|
||||||
|
left: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tooltip for collapsed state */
|
||||||
|
.item-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
left: 100%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
margin-left: 12px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: var(--tooltip-bg);
|
||||||
|
color: var(--tooltip-fg);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-tooltip::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -4px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
border: 4px solid transparent;
|
||||||
|
border-right-color: var(--tooltip-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .menuItem:hover .item-tooltip {
|
||||||
|
opacity: 1;
|
||||||
|
transition-delay: 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badge Styles */
|
||||||
|
.badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
padding: 0 6px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 9px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.default {
|
||||||
|
background: var(--badge-default-bg);
|
||||||
|
color: var(--badge-default-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.success {
|
||||||
|
background: var(--badge-success-bg);
|
||||||
|
color: var(--badge-success-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.warning {
|
||||||
|
background: var(--badge-warning-bg);
|
||||||
|
color: var(--badge-warning-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.error {
|
||||||
|
background: var(--badge-error-bg);
|
||||||
|
color: var(--badge-error-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([collapsed]) .badge {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Divider */
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
background: var(--sidebar-border);
|
||||||
|
margin: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Legacy options container */
|
||||||
|
.legacyOptions {
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
public render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div class="maincontainer">
|
||||||
|
<div class="header">
|
||||||
|
<span class="heading">${this.heading}</span>
|
||||||
|
</div>
|
||||||
|
<div class="menuSection">
|
||||||
|
${this.groups.length > 0
|
||||||
|
? this.renderGroups()
|
||||||
|
: this.renderLegacyOptions()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="collapse-toggle" @click="${() => this.toggleCollapse()}">
|
||||||
|
<dees-icon .icon="${this.collapsed ? 'lucide:chevronRight' : 'lucide:chevronLeft'}"></dees-icon>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderGroups(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
${this.groups.map((group) => html`
|
||||||
|
<div class="menuGroup">
|
||||||
|
<div
|
||||||
|
class="groupHeader ${this.collapsedGroups.has(group.name) ? 'collapsed' : ''}"
|
||||||
|
@click="${() => this.toggleGroup(group.name)}"
|
||||||
|
>
|
||||||
|
<span class="groupTitle">
|
||||||
|
${group.iconName ? html`<dees-icon .icon="${group.iconName.startsWith('lucide:') ? group.iconName : `lucide:${group.iconName}`}"></dees-icon>` : ''}
|
||||||
|
${group.name}
|
||||||
|
</span>
|
||||||
|
<dees-icon class="chevron" .icon="${'lucide:chevronDown'}"></dees-icon>
|
||||||
|
</div>
|
||||||
|
<div class="groupItems ${this.collapsedGroups.has(group.name) ? 'collapsed' : ''}">
|
||||||
|
${group.items.map((item) => this.renderMenuItem(item, group))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderMenuItem(item: interfaces.IMenuItem, group?: interfaces.IMenuGroup): TemplateResult {
|
||||||
|
const isSelected = this.selectedItem?.key === item.key;
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="menuItem ${isSelected ? 'selected' : ''}"
|
||||||
|
@click="${() => this.selectItem(item, group)}"
|
||||||
|
@contextmenu="${(e: MouseEvent) => this.handleContextMenu(e, item)}"
|
||||||
|
>
|
||||||
|
${item.iconName ? html`<dees-icon .icon="${item.iconName.startsWith('lucide:') ? item.iconName : `lucide:${item.iconName}`}"></dees-icon>` : ''}
|
||||||
|
<span class="itemLabel">${item.key}</span>
|
||||||
|
${item.badge !== undefined ? html`
|
||||||
|
<span class="badge ${item.badgeVariant || 'default'}">${item.badge}</span>
|
||||||
|
` : ''}
|
||||||
|
<span class="item-tooltip">${item.key}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderLegacyOptions(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div class="legacyOptions">
|
||||||
|
${this.selectionOptions.map((option) => {
|
||||||
|
if ('divider' in option && option.divider) {
|
||||||
|
return html`<div class="divider"></div>`;
|
||||||
|
}
|
||||||
|
const item = option as interfaces.IMenuItem;
|
||||||
|
return this.renderMenuItem({
|
||||||
|
key: item.key,
|
||||||
|
iconName: item.iconName,
|
||||||
|
action: item.action,
|
||||||
|
});
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleGroup(groupName: string): void {
|
||||||
|
const newCollapsed = new Set(this.collapsedGroups);
|
||||||
|
if (newCollapsed.has(groupName)) {
|
||||||
|
newCollapsed.delete(groupName);
|
||||||
|
} else {
|
||||||
|
newCollapsed.add(groupName);
|
||||||
|
}
|
||||||
|
this.collapsedGroups = newCollapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleCollapse(): void {
|
||||||
|
this.collapsed = !this.collapsed;
|
||||||
|
this.dispatchEvent(new CustomEvent('collapse-change', {
|
||||||
|
detail: { collapsed: this.collapsed },
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private selectItem(item: interfaces.IMenuItem, group?: interfaces.IMenuGroup): void {
|
||||||
|
this.selectedItem = item;
|
||||||
|
item.action();
|
||||||
|
|
||||||
|
this.dispatchEvent(new CustomEvent('item-select', {
|
||||||
|
detail: { item, group },
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleContextMenu(event: MouseEvent, item: interfaces.IMenuItem): void {
|
||||||
|
DeesContextmenu.openContextMenuWithOptions(event, [
|
||||||
|
{
|
||||||
|
name: 'View details',
|
||||||
|
action: async () => {},
|
||||||
|
iconName: 'lucide:eye',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Edit',
|
||||||
|
action: async () => {},
|
||||||
|
iconName: 'lucide:pencil',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
|
||||||
|
await super.firstUpdated(_changedProperties);
|
||||||
|
|
||||||
|
// Initialize collapsed state from group defaults
|
||||||
|
if (this.groups.length > 0) {
|
||||||
|
const initialCollapsed = new Set<string>();
|
||||||
|
this.groups.forEach(group => {
|
||||||
|
if (group.collapsed) {
|
||||||
|
initialCollapsed.add(group.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.collapsedGroups = initialCollapsed;
|
||||||
|
|
||||||
|
// Auto-select first item if none selected
|
||||||
|
if (!this.selectedItem && this.groups[0]?.items.length > 0) {
|
||||||
|
this.selectItem(this.groups[0].items[0], this.groups[0]);
|
||||||
|
}
|
||||||
|
} else if (this.selectionOptions.length > 0) {
|
||||||
|
// Legacy mode: select first non-divider option
|
||||||
|
const firstOption = this.selectionOptions.find(opt => !('divider' in opt)) as interfaces.IMenuItem;
|
||||||
|
if (firstOption && !this.selectedItem) {
|
||||||
|
this.selectItem({
|
||||||
|
key: firstOption.key,
|
||||||
|
iconName: firstOption.iconName,
|
||||||
|
action: firstOption.action,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'dees-appui-secondarymenu': DeesAppuiSecondarymenu;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-appui-secondarymenu.js';
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { html, cssManager } from '@design.estate/dees-element';
|
||||||
|
import * as interfaces from '../../interfaces/index.js';
|
||||||
|
|
||||||
|
export const demoFunc = () => {
|
||||||
|
const horizontalTabs: interfaces.IMenuItem[] = [
|
||||||
|
{ key: 'Home', iconName: 'lucide:home', action: () => console.log('Home clicked') },
|
||||||
|
{ key: 'Analytics Dashboard', iconName: 'lucide:lineChart', action: () => console.log('Analytics clicked') },
|
||||||
|
{ key: 'Reports', iconName: 'lucide:fileText', action: () => console.log('Reports clicked') },
|
||||||
|
{ key: 'User Settings', iconName: 'lucide:settings', action: () => console.log('Settings clicked') },
|
||||||
|
{ key: 'Help', iconName: 'lucide:helpCircle', action: () => console.log('Help clicked') },
|
||||||
|
];
|
||||||
|
|
||||||
|
const verticalTabs: interfaces.IMenuItem[] = [
|
||||||
|
{ key: 'Profile', iconName: 'lucide:user', action: () => console.log('Profile clicked') },
|
||||||
|
{ key: 'Security', iconName: 'lucide:shield', action: () => console.log('Security clicked') },
|
||||||
|
{ key: 'Notifications', iconName: 'lucide:bell', action: () => console.log('Notifications clicked') },
|
||||||
|
{ key: 'Integrations', iconName: 'lucide:link', action: () => console.log('Integrations clicked') },
|
||||||
|
{ key: 'Advanced', iconName: 'lucide:code', action: () => console.log('Advanced clicked') },
|
||||||
|
];
|
||||||
|
|
||||||
|
const noIndicatorTabs: interfaces.IMenuItem[] = [
|
||||||
|
{ key: 'All', action: () => console.log('All clicked') },
|
||||||
|
{ key: 'Active', action: () => console.log('Active clicked') },
|
||||||
|
{ key: 'Completed', action: () => console.log('Completed clicked') },
|
||||||
|
{ key: 'Archived', action: () => console.log('Archived clicked') },
|
||||||
|
];
|
||||||
|
|
||||||
|
const demoContent = (text: string) => html`
|
||||||
|
<div style="padding: 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};">
|
||||||
|
${text}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
.demo-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 32px;
|
||||||
|
padding: 48px;
|
||||||
|
background: ${cssManager.bdTheme('#f8f9fa', '#0a0a0a')};
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
background: ${cssManager.bdTheme('#ffffff', '#18181b')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.two-column {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 200px 1fr;
|
||||||
|
gap: 24px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="demo-container">
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">Horizontal Tabs with Animated Indicator</div>
|
||||||
|
<dees-appui-tabs .tabs=${horizontalTabs}>
|
||||||
|
${demoContent('Select a tab to see the smooth sliding animation of the indicator. The indicator automatically adjusts its width to match the tab content with minimal padding.')}
|
||||||
|
</dees-appui-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">Vertical Tabs Layout</div>
|
||||||
|
<div class="two-column">
|
||||||
|
<dees-appui-tabs .tabStyle=${'vertical'} .tabs=${verticalTabs}></dees-appui-tabs>
|
||||||
|
${demoContent('Vertical tabs work great for settings pages and navigation menus. The animated indicator smoothly transitions between selections.')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">Without Indicator</div>
|
||||||
|
<dees-appui-tabs .showTabIndicator=${false} .tabs=${noIndicatorTabs}>
|
||||||
|
${demoContent('Tabs can also be used without the animated indicator by setting showTabIndicator to false.')}
|
||||||
|
</dees-appui-tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as interfaces from '../interfaces/index.js';
|
import * as interfaces from '../../interfaces/index.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DeesElement,
|
DeesElement,
|
||||||
@@ -11,106 +11,21 @@ import {
|
|||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
|
import { demoFunc } from './dees-appui-tabs.demo.js';
|
||||||
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
|
|
||||||
@customElement('dees-appui-tabs')
|
@customElement('dees-appui-tabs')
|
||||||
export class DeesAppuiTabs extends DeesElement {
|
export class DeesAppuiTabs extends DeesElement {
|
||||||
public static demo = () => {
|
public static demo = demoFunc;
|
||||||
const horizontalTabs: interfaces.ITab[] = [
|
|
||||||
{ key: 'Home', iconName: 'lucide:home', action: () => console.log('Home clicked') },
|
|
||||||
{ key: 'Analytics Dashboard', iconName: 'lucide:lineChart', action: () => console.log('Analytics clicked') },
|
|
||||||
{ key: 'Reports', iconName: 'lucide:fileText', action: () => console.log('Reports clicked') },
|
|
||||||
{ key: 'User Settings', iconName: 'lucide:settings', action: () => console.log('Settings clicked') },
|
|
||||||
{ key: 'Help', iconName: 'lucide:helpCircle', action: () => console.log('Help clicked') },
|
|
||||||
];
|
|
||||||
|
|
||||||
const verticalTabs: interfaces.ITab[] = [
|
|
||||||
{ key: 'Profile', iconName: 'lucide:user', action: () => console.log('Profile clicked') },
|
|
||||||
{ key: 'Security', iconName: 'lucide:shield', action: () => console.log('Security clicked') },
|
|
||||||
{ key: 'Notifications', iconName: 'lucide:bell', action: () => console.log('Notifications clicked') },
|
|
||||||
{ key: 'Integrations', iconName: 'lucide:link', action: () => console.log('Integrations clicked') },
|
|
||||||
{ key: 'Advanced', iconName: 'lucide:code', action: () => console.log('Advanced clicked') },
|
|
||||||
];
|
|
||||||
|
|
||||||
const noIndicatorTabs: interfaces.ITab[] = [
|
|
||||||
{ key: 'All', action: () => console.log('All clicked') },
|
|
||||||
{ key: 'Active', action: () => console.log('Active clicked') },
|
|
||||||
{ key: 'Completed', action: () => console.log('Completed clicked') },
|
|
||||||
{ key: 'Archived', action: () => console.log('Archived clicked') },
|
|
||||||
];
|
|
||||||
|
|
||||||
const demoContent = (text: string) => html`
|
|
||||||
<div style="padding: 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};">
|
|
||||||
${text}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
.demo-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 32px;
|
|
||||||
padding: 48px;
|
|
||||||
background: ${cssManager.bdTheme('#f8f9fa', '#0a0a0a')};
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
background: ${cssManager.bdTheme('#ffffff', '#18181b')};
|
|
||||||
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 24px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
|
||||||
}
|
|
||||||
|
|
||||||
.two-column {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 200px 1fr;
|
|
||||||
gap: 24px;
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="demo-container">
|
|
||||||
<div class="section">
|
|
||||||
<div class="section-title">Horizontal Tabs with Animated Indicator</div>
|
|
||||||
<dees-appui-tabs .tabs=${horizontalTabs}>
|
|
||||||
${demoContent('Select a tab to see the smooth sliding animation of the indicator. The indicator automatically adjusts its width to match the tab content with minimal padding.')}
|
|
||||||
</dees-appui-tabs>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<div class="section-title">Vertical Tabs Layout</div>
|
|
||||||
<div class="two-column">
|
|
||||||
<dees-appui-tabs .tabStyle=${'vertical'} .tabs=${verticalTabs}></dees-appui-tabs>
|
|
||||||
${demoContent('Vertical tabs work great for settings pages and navigation menus. The animated indicator smoothly transitions between selections.')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<div class="section-title">Without Indicator</div>
|
|
||||||
<dees-appui-tabs .showTabIndicator=${false} .tabs=${noIndicatorTabs}>
|
|
||||||
${demoContent('Tabs can also be used without the animated indicator by setting showTabIndicator to false.')}
|
|
||||||
</dees-appui-tabs>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
// INSTANCE
|
// INSTANCE
|
||||||
@property({
|
@property({
|
||||||
type: Array,
|
type: Array,
|
||||||
})
|
})
|
||||||
accessor tabs: interfaces.ITab[] = [];
|
accessor tabs: interfaces.IMenuItem[] = [];
|
||||||
|
|
||||||
@property({ type: Object })
|
@property({ type: Object })
|
||||||
accessor selectedTab: interfaces.ITab | null = null;
|
accessor selectedTab: interfaces.IMenuItem | null = null;
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
accessor showTabIndicator: boolean = true;
|
accessor showTabIndicator: boolean = true;
|
||||||
@@ -119,8 +34,10 @@ export class DeesAppuiTabs extends DeesElement {
|
|||||||
accessor tabStyle: 'horizontal' | 'vertical' = 'horizontal';
|
accessor tabStyle: 'horizontal' | 'vertical' = 'horizontal';
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
|
themeDefaultStyles,
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
|
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -132,7 +49,9 @@ export class DeesAppuiTabs extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tabs-wrapper.horizontal-wrapper {
|
.tabs-wrapper.horizontal-wrapper {
|
||||||
|
height: 48px;
|
||||||
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabsContainer {
|
.tabsContainer {
|
||||||
@@ -146,7 +65,7 @@ export class DeesAppuiTabs extends DeesElement {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
height: 48px;
|
height: 100%;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
@@ -310,7 +229,7 @@ export class DeesAppuiTabs extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderTab(tab: interfaces.ITab, isHorizontal: boolean): TemplateResult {
|
private renderTab(tab: interfaces.IMenuItem, isHorizontal: boolean): TemplateResult {
|
||||||
const isSelected = tab === this.selectedTab;
|
const isSelected = tab === this.selectedTab;
|
||||||
const classes = `tab ${isSelected ? 'selectedTab' : ''}`;
|
const classes = `tab ${isSelected ? 'selectedTab' : ''}`;
|
||||||
|
|
||||||
@@ -334,11 +253,11 @@ export class DeesAppuiTabs extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderTabIcon(tab: interfaces.ITab): TemplateResult | '' {
|
private renderTabIcon(tab: interfaces.IMenuItem): TemplateResult | '' {
|
||||||
return tab.iconName ? html`<dees-icon .icon=${tab.iconName}></dees-icon>` : '';
|
return tab.iconName ? html`<dees-icon .icon=${tab.iconName}></dees-icon>` : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
private selectTab(tabArg: interfaces.ITab) {
|
private selectTab(tabArg: interfaces.IMenuItem) {
|
||||||
this.selectedTab = tabArg;
|
this.selectedTab = tabArg;
|
||||||
tabArg.action();
|
tabArg.action();
|
||||||
|
|
||||||
1
ts_web/elements/00group-appui/dees-appui-tabs/index.ts
Normal file
1
ts_web/elements/00group-appui/dees-appui-tabs/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-appui-tabs.js';
|
||||||
9
ts_web/elements/00group-appui/index.ts
Normal file
9
ts_web/elements/00group-appui/index.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// App UI Components
|
||||||
|
export * from './dees-appui-activitylog/index.js';
|
||||||
|
export * from './dees-appui-appbar/index.js';
|
||||||
|
export * from './dees-appui-base/index.js';
|
||||||
|
export * from './dees-appui-maincontent/index.js';
|
||||||
|
export * from './dees-appui-mainmenu/index.js';
|
||||||
|
export * from './dees-appui-secondarymenu/index.js';
|
||||||
|
export * from './dees-appui-profiledropdown/index.js';
|
||||||
|
export * from './dees-appui-tabs/index.js';
|
||||||
1
ts_web/elements/00group-button/dees-button-exit/index.ts
Normal file
1
ts_web/elements/00group-button/dees-button-exit/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-button-exit.js';
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
|
|
||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
import { demoFunc } from './dees-button-group.demo.js';
|
import { demoFunc } from './dees-button-group.demo.js';
|
||||||
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@@ -33,8 +34,10 @@ export class DeesButtonGroup extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
|
themeDefaultStyles,
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
|
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||||
:host {
|
:host {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-button-group.js';
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { html, css, cssManager, domtools } from '@design.estate/dees-element';
|
import { html, css, cssManager, domtools } from '@design.estate/dees-element';
|
||||||
import '@design.estate/dees-wcctools/demotools';
|
import '@design.estate/dees-wcctools/demotools';
|
||||||
import '../dees-panel/dees-panel.js';
|
import '../../dees-panel/dees-panel.js';
|
||||||
import '../dees-form/dees-form.js';
|
import '../../00group-form/dees-form/dees-form.js';
|
||||||
import '../dees-form-submit/dees-form-submit.js';
|
import '../../00group-form/dees-form-submit/dees-form-submit.js';
|
||||||
import '../dees-input-text/dees-input-text.js';
|
import '../../00group-input/dees-input-text/dees-input-text.js';
|
||||||
import '../dees-icon/dees-icon.js';
|
import '../../dees-icon/dees-icon.js';
|
||||||
import type { DeesButton } from '../dees-button/dees-button.js';
|
import type { DeesButton } from '../dees-button/dees-button.js';
|
||||||
|
|
||||||
export const demoFunc = () => html`
|
export const demoFunc = () => html`
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
|
|
||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
import { demoFunc } from './dees-button.demo.js';
|
import { demoFunc } from './dees-button.demo.js';
|
||||||
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@@ -79,8 +80,10 @@ export class DeesButton extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
|
themeDefaultStyles,
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
|
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||||
:host {
|
:host {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
1
ts_web/elements/00group-button/dees-button/index.ts
Normal file
1
ts_web/elements/00group-button/dees-button/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-button.js';
|
||||||
4
ts_web/elements/00group-button/index.ts
Normal file
4
ts_web/elements/00group-button/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Button Components
|
||||||
|
export * from './dees-button/index.js';
|
||||||
|
export * from './dees-button-exit/index.js';
|
||||||
|
export * from './dees-button-group/index.js';
|
||||||
1
ts_web/elements/00group-chart/dees-chart-area/index.ts
Normal file
1
ts_web/elements/00group-chart/dees-chart-area/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './component.js';
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
|
|
||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
import { demoFunc } from './dees-chart-log.demo.js';
|
import { demoFunc } from './dees-chart-log.demo.js';
|
||||||
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@@ -50,8 +51,10 @@ export class DeesChartLog extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
|
themeDefaultStyles,
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
|
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||||
:host {
|
:host {
|
||||||
font-family: 'SF Mono', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
|
font-family: 'SF Mono', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
|
||||||
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
|
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
|
||||||
1
ts_web/elements/00group-chart/dees-chart-log/index.ts
Normal file
1
ts_web/elements/00group-chart/dees-chart-log/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-chart-log.js';
|
||||||
3
ts_web/elements/00group-chart/index.ts
Normal file
3
ts_web/elements/00group-chart/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// Chart Components
|
||||||
|
export * from './dees-chart-area/index.js';
|
||||||
|
export * from './dees-chart-log/index.js';
|
||||||
@@ -8,14 +8,14 @@ import {
|
|||||||
state,
|
state,
|
||||||
cssManager,
|
cssManager,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
import { cssGeistFontFamily, cssMonoFontFamily } from '../00fonts.js';
|
import { cssGeistFontFamily, cssMonoFontFamily } from '../../00fonts.js';
|
||||||
|
|
||||||
import hlight from 'highlight.js';
|
import hlight from 'highlight.js';
|
||||||
|
|
||||||
import * as smartstring from '@push.rocks/smartstring';
|
import * as smartstring from '@push.rocks/smartstring';
|
||||||
|
|
||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
import { DeesContextmenu } from '../dees-contextmenu/dees-contextmenu.js';
|
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-dataview-codebox.js';
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as colors from '../00colors.js';
|
import * as colors from '../../00colors.js';
|
||||||
import * as plugins from '../00plugins.js';
|
import * as plugins from '../../00plugins.js';
|
||||||
|
|
||||||
import { demoFunc } from './dees-dataview-statusobject.demo.js';
|
import { demoFunc } from './dees-dataview-statusobject.demo.js';
|
||||||
import {
|
import {
|
||||||
@@ -15,7 +15,8 @@ import {
|
|||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
import * as tsclass from '@tsclass/tsclass';
|
import * as tsclass from '@tsclass/tsclass';
|
||||||
import { DeesContextmenu } from '../dees-contextmenu/dees-contextmenu.js';
|
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
|
||||||
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@@ -30,8 +31,10 @@ export class DeesDataviewStatusobject extends DeesElement {
|
|||||||
@property({ type: Object }) accessor statusObject: tsclass.code.IStatusObject;
|
@property({ type: Object }) accessor statusObject: tsclass.code.IStatusObject;
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
|
themeDefaultStyles,
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
|
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||||
:host {
|
:host {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-dataview-statusobject.js';
|
||||||
3
ts_web/elements/00group-dataview/index.ts
Normal file
3
ts_web/elements/00group-dataview/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// Data View Components
|
||||||
|
export * from './dees-dataview-codebox/index.js';
|
||||||
|
export * from './dees-dataview-statusobject/index.js';
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
cssManager,
|
cssManager,
|
||||||
domtools
|
domtools
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
|
|
||||||
const deferred = domtools.plugins.smartpromise.defer();
|
const deferred = domtools.plugins.smartpromise.defer();
|
||||||
|
|
||||||
@@ -22,8 +23,10 @@ export class DeesEditorMarkdown extends DeesElement {
|
|||||||
public static demo = () => html`<dees-editormarkdown></dees-editormarkdown>`;
|
public static demo = () => html`<dees-editormarkdown></dees-editormarkdown>`;
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
|
themeDefaultStyles,
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
|
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||||
.gridcontainer {
|
.gridcontainer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-editor-markdown.js';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-editor-markdownoutlet.js';
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
import { MONACO_VERSION } from './version.js';
|
import { MONACO_VERSION } from './version.js';
|
||||||
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
|
|
||||||
import type * as monaco from 'monaco-editor';
|
import type * as monaco from 'monaco-editor';
|
||||||
|
|
||||||
@@ -51,8 +52,10 @@ export class DeesEditor extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
|
themeDefaultStyles,
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
|
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||||
:host {
|
:host {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,2 +1 @@
|
|||||||
export * from './dees-editor.js';
|
export * from './dees-editor.js';
|
||||||
export * from './version.js';
|
|
||||||
4
ts_web/elements/00group-editor/index.ts
Normal file
4
ts_web/elements/00group-editor/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Editor Components
|
||||||
|
export * from './dees-editor/index.js';
|
||||||
|
export * from './dees-editor-markdown/index.js';
|
||||||
|
export * from './dees-editor-markdownoutlet/index.js';
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
property,
|
property,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
import type { DeesForm } from '../dees-form/dees-form.js';
|
import type { DeesForm } from '../dees-form/dees-form.js';
|
||||||
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@@ -39,7 +40,9 @@ export class DeesFormSubmit extends DeesElement {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static styles = [cssManager.defaultStyles, css``];
|
public static styles = [themeDefaultStyles, cssManager.defaultStyles, css`
|
||||||
|
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||||
|
`];
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return html`
|
return html`
|
||||||
1
ts_web/elements/00group-form/dees-form-submit/index.ts
Normal file
1
ts_web/elements/00group-form/dees-form-submit/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-form-submit.js';
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { html, css, domtools, cssManager } from '@design.estate/dees-element';
|
import { html, css, domtools, cssManager } from '@design.estate/dees-element';
|
||||||
import type { DeesForm } from '../dees-form/dees-form.js';
|
import type { DeesForm } from './dees-form.js';
|
||||||
import '@design.estate/dees-wcctools/demotools';
|
import '@design.estate/dees-wcctools/demotools';
|
||||||
|
|
||||||
export const demoFunc = () => html`
|
export const demoFunc = () => html`
|
||||||
@@ -8,19 +8,19 @@ import {
|
|||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
|
|
||||||
import { DeesInputCheckbox } from '../dees-input-checkbox/dees-input-checkbox.js';
|
import { DeesInputCheckbox } from '../../00group-input/dees-input-checkbox/dees-input-checkbox.js';
|
||||||
import { DeesInputDatepicker } from '../dees-input-datepicker/index.js';
|
import { DeesInputDatepicker } from '../../00group-input/dees-input-datepicker/index.js';
|
||||||
import { DeesInputText } from '../dees-input-text/dees-input-text.js';
|
import { DeesInputText } from '../../00group-input/dees-input-text/dees-input-text.js';
|
||||||
import { DeesInputQuantitySelector } from '../dees-input-quantityselector/dees-input-quantityselector.js';
|
import { DeesInputQuantitySelector } from '../../00group-input/dees-input-quantityselector/dees-input-quantityselector.js';
|
||||||
import { DeesInputRadiogroup } from '../dees-input-radiogroup/dees-input-radiogroup.js';
|
import { DeesInputRadiogroup } from '../../00group-input/dees-input-radiogroup/dees-input-radiogroup.js';
|
||||||
import { DeesInputDropdown } from '../dees-input-dropdown/dees-input-dropdown.js';
|
import { DeesInputDropdown } from '../../00group-input/dees-input-dropdown/dees-input-dropdown.js';
|
||||||
import { DeesInputFileupload } from '../dees-input-fileupload/index.js';
|
import { DeesInputFileupload } from '../../00group-input/dees-input-fileupload/index.js';
|
||||||
import { DeesInputIban } from '../dees-input-iban/dees-input-iban.js';
|
import { DeesInputIban } from '../../00group-input/dees-input-iban/dees-input-iban.js';
|
||||||
import { DeesInputMultitoggle } from '../dees-input-multitoggle/dees-input-multitoggle.js';
|
import { DeesInputMultitoggle } from '../../00group-input/dees-input-multitoggle/dees-input-multitoggle.js';
|
||||||
import { DeesInputPhone } from '../dees-input-phone/dees-input-phone.js';
|
import { DeesInputPhone } from '../../00group-input/dees-input-phone/dees-input-phone.js';
|
||||||
import { DeesInputTypelist } from '../dees-input-typelist/dees-input-typelist.js';
|
import { DeesInputTypelist } from '../../00group-input/dees-input-typelist/dees-input-typelist.js';
|
||||||
import { DeesFormSubmit } from '../dees-form-submit/dees-form-submit.js';
|
import { DeesFormSubmit } from '../dees-form-submit/dees-form-submit.js';
|
||||||
import { DeesTable } from '../dees-table/index.js';
|
import { DeesTable } from '../../dees-table/index.js';
|
||||||
import { demoFunc } from './dees-form.demo.js';
|
import { demoFunc } from './dees-form.demo.js';
|
||||||
|
|
||||||
// Unified set for form input types
|
// Unified set for form input types
|
||||||
1
ts_web/elements/00group-form/dees-form/index.ts
Normal file
1
ts_web/elements/00group-form/dees-form/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-form.js';
|
||||||
3
ts_web/elements/00group-form/index.ts
Normal file
3
ts_web/elements/00group-form/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// Form Components
|
||||||
|
export * from './dees-form/index.js';
|
||||||
|
export * from './dees-form-submit/index.js';
|
||||||
1
ts_web/elements/00group-input/dees-input-base/index.ts
Normal file
1
ts_web/elements/00group-input/dees-input-base/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './dees-input-base.js';
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { html, css, cssManager } from '@design.estate/dees-element';
|
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||||
import '@design.estate/dees-wcctools/demotools';
|
import '@design.estate/dees-wcctools/demotools';
|
||||||
import '../dees-panel/dees-panel.js';
|
import '../../dees-panel/dees-panel.js';
|
||||||
import type { DeesInputCheckbox } from '../dees-input-checkbox/dees-input-checkbox.js';
|
import type { DeesInputCheckbox } from '../dees-input-checkbox/dees-input-checkbox.js';
|
||||||
import '../dees-button/dees-button.js';
|
import '../../00group-button/dees-button/dees-button.js';
|
||||||
|
|
||||||
export const demoFunc = () => html`
|
export const demoFunc = () => html`
|
||||||
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
|
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user