Compare commits

..

19 Commits

Author SHA1 Message Date
12861b2230 1.7.0
Some checks failed
Default (tags) / security (push) Failing after 17s
Default (tags) / test (push) Failing after 8s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-04-22 12:49:57 +00:00
b7f672e0f2 feat(dees-searchbar): Add dees-searchbar component with live search and filter demo 2025-04-22 12:49:57 +00:00
fcb44dfd24 1.6.0
Some checks failed
Default (tags) / security (push) Failing after 20s
Default (tags) / test (push) Failing after 9s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-04-22 12:30:22 +00:00
f17b880b59 feat(documentation/dees-heading): Add codex documentation overview and dees-heading component demo 2025-04-22 12:30:22 +00:00
68785d9a72 1.5.6
Some checks failed
Default (tags) / security (push) Failing after 20s
Default (tags) / test (push) Failing after 8s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-04-18 17:07:43 +00:00
ab4396297a fix(dependencies): Bump dependency versions and update demo code references 2025-04-18 17:07:43 +00:00
ef369f2955 update 2025-04-13 17:32:44 +00:00
1e73a9527b 1.5.5
Some checks failed
Default (tags) / security (push) Failing after 19s
Default (tags) / test (push) Failing after 9s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-04-12 14:35:56 +00:00
23a4faa5d1 fix(catalog): No code or documentation changes were detected. This commit records an empty update in commit information and confirms that the current state remains stable. 2025-04-12 14:35:55 +00:00
b0020ace16 1.5.4
Some checks failed
Default (tags) / security (push) Failing after 12s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-04-11 11:19:58 +00:00
bb78d32dbf fix(readme): Update readme with company and trademark guidelines, clarifying legal usage without exposing licensing details. 2025-04-11 11:19:58 +00:00
e83ad8d504 1.5.3
Some checks failed
Default (tags) / security (push) Failing after 17s
Default (tags) / test (push) Failing after 8s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-04-11 11:19:24 +00:00
765b01afe0 fix(readme): Update readme.md: remove redundant usage section and refine component documentation with improved examples. 2025-04-11 11:19:23 +00:00
00e34e7e6c 1.5.2
Some checks failed
Default (tags) / security (push) Failing after 9s
Default (tags) / test (push) Failing after 10s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-04-11 10:24:28 +00:00
bf2ee25390 fix(ci): Remove obsolete GitLab CI configuration file 2025-04-11 10:24:28 +00:00
bf6d8d0bc6 1.5.1
Some checks failed
Default (tags) / security (push) Failing after 18s
Default (tags) / test (push) Failing after 11s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-04-11 10:20:35 +00:00
3399004e75 fix(readme): Update readme with comprehensive reference documentation: add a usage snippet for components like DeesButton, introduce a detailed overview table of all component categories, and enhance documentation sections for each component group. 2025-04-11 10:20:35 +00:00
6c2f36f020 1.5.0
Some checks failed
Default (tags) / security (push) Failing after 14s
Default (tags) / test (push) Failing after 14s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-04-11 10:15:00 +00:00
71f4d44782 feat(badge): Add dees-badge component with demo file and update packageManager field in package.json 2025-04-11 10:15:00 +00:00
18 changed files with 4276 additions and 2551 deletions

View File

@ -1,128 +0,0 @@
# gitzone ci_default
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
cache:
paths:
- .npmci_cache/
key: '$CI_BUILD_STAGE'
stages:
- security
- test
- release
- metadata
before_script:
- pnpm install -g pnpm
- pnpm install -g @shipzone/npmci
- npmci npm prepare
# ====================
# security stage
# ====================
# ====================
# security stage
# ====================
auditProductionDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci command npm config set registry https://registry.npmjs.org
- npmci command pnpm audit --audit-level=high --prod
tags:
- lossless
- docker
allow_failure: true
auditDevDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci command npm config set registry https://registry.npmjs.org
- npmci command pnpm audit --audit-level=high --dev
tags:
- lossless
- docker
allow_failure: true
# ====================
# test stage
# ====================
testStable:
stage: test
script:
- npmci node install stable
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
testBuild:
stage: test
script:
- npmci node install stable
- npmci npm install
- npmci npm build
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
release:
stage: release
script:
- npmci node install stable
- npmci npm publish
only:
- tags
tags:
- lossless
- docker
- notpriv
# ====================
# metadata stage
# ====================
codequality:
stage: metadata
allow_failure: true
only:
- tags
script:
- npmci command npm install -g typescript
- npmci npm prepare
- npmci npm install
tags:
- lossless
- docker
- priv
trigger:
stage: metadata
script:
- npmci trigger
only:
- tags
tags:
- lossless
- docker
- notpriv
pages:
stage: metadata
script:
- npmci node install stable
- npmci npm install
- npmci command npm run buildDocs
tags:
- lossless
- docker
- notpriv
only:
- tags
artifacts:
expire_in: 1 week
paths:
- public
allow_failure: true

View File

@ -1,5 +1,77 @@
# Changelog
## 2025-04-22 - 1.7.0 - feat(dees-searchbar)
Add dees-searchbar component with live search and filter demo
- Introduces a new dees-searchbar element with an input field, a search button, and filters
- Wires up events for 'search-changed' and 'search-submit' to provide realtime feedback
- Adds a demo file to showcase usage and logging of search events
## 2025-04-22 - 1.6.0 - feat(documentation/dees-heading)
Add codex documentation overview and dees-heading component demo
- Introduce 'codex.md' to provide a high-level overview of project layout, component patterns, and build workflow
- Add and update dees-heading component with demo to support multiple heading levels and horizontal rule styles
- Update component export index to include dees-heading
## 2025-04-18 - 1.5.6 - fix(dependencies)
Bump dependency versions and update demo code references
- Upgrade @design.estate/dees-element from ^2.0.39 to ^2.0.41
- Upgrade @tsclass/tsclass from ^4.4.0 to ^9.0.0
- Upgrade lucide from ^0.488.0 to ^0.501.0
- Update @types/node from ^22.10.7 to ^22.14.1
- Update dees-icon demo: scope search to demo container and adjust hover scaling
- Replace resolveExec with directives.resolveExec in dees-table for proper rendering
## 2025-04-12 - 1.5.5 - fix(catalog)
No code or documentation changes were detected. This commit records an empty update in commit information and confirms that the current state remains stable.
- Verified that there are no modifications in source, documentation, or demos
- Commit metadata and build configuration remain unchanged
## 2025-04-11 - 1.5.4 - fix(readme)
Update readme with company and trademark guidelines, clarifying legal usage without exposing licensing details.
- Added sections detailing company information and trademark guidelines.
- Outlined legal disclaimers for trademark usage.
## 2025-04-11 - 1.5.3 - fix(readme)
Update readme.md: remove redundant usage section and refine component documentation with improved examples.
- Removed the standalone manual import and usage example for components.
- Added refined examples demonstrating both basic and option-based usage (e.g. for DeesButton).
- Improved markdown formatting and consistency across component documentation.
## 2025-04-11 - 1.5.3 - fix(readme)
Update readme.md for clearer documentation: removed redundant 'Usage' section and refined component examples (e.g., DeesButton's basic and options usage) for improved clarity and consistency.
- Removed standalone usage example showing manual import and creation of components
- Added refined examples demonstrating both basic and option-based usage of components
- Improved overall readme formatting and consistency across component documentation
## 2025-04-11 - 1.5.2 - fix(ci)
Remove obsolete GitLab CI configuration file
- Deleted .gitlab-ci.yml as the CI pipeline configuration is now managed elsewhere.
- Cleaned up CI stages for security, testing, release, and metadata.
## 2025-04-11 - 1.5.1 - fix(readme)
Update readme with comprehensive reference documentation: add a usage snippet for components like DeesButton, introduce a detailed overview table of all component categories, and enhance documentation sections for each component group.
- Added a code example showing how to import and use DeesButton.
- Introduced a components overview table that categorizes Core UI, Forms, Layout, Data Display, Visualization, Dialogs & Overlays, Navigation, and Development components.
- Expanded detailed documentation with usage examples for each component type.
- Reorganized content to improve clarity and ease of navigation for developers.
## 2025-04-11 - 1.5.0 - feat(badge)
Add dees-badge component with demo file and update packageManager field in package.json
- Introduce a new badge component allowing different types (default, primary, success, warning, error) with an optional rounded style
- Provide a demo for the badge component
- Export the badge component in the main elements index
- Update package.json to include an explicit packageManager field
## 2025-01-20 - 1.4.1 - fix(dependencies)
Update dependency versions for smartpromise, webcontainer/api, tapbundle, and @types/node

43
codex.md Normal file
View File

@ -0,0 +1,43 @@
# Codex: Project Overview and Codebase Structure
## Project Overview
- Package: `@design.estate/dees-catalog`
- Focus: Web Components library providing UI elements and layouts for modern web apps.
## Directory Layout
- ts_web/: TypeScript source files
- elements/: Individual Web Component definitions
- pages/: Page-level templates for composite layouts
- html/: Demo/app entry point loading the bundled scripts
- dist_bundle/: Bundled browser JS and source maps
- dist_ts_web/: ES module outputs for TypeScript/web consumers
- dist_watch/: Watch-mode development bundle with live reload
- test/: Browser-based tests using `@push.rocks/tapbundle`
## Component Patterns
- Each component in ts_web/elements/:
- Decorated with `@customElement('tag-name')`
- Extends `DeesElement` from `@design.estate/dees-element`
- Uses `@property` for reactive, reflected attributes
- Defines `static styles = [cssManager.defaultStyles, css`...`]`
- Implements `render()` returning a Lit `html` template with slots or markup
- Exposes a demo via `public static demo` linking to `.demo.ts` files
## Build & Development Workflow
- Install dependencies: `npm install` or `pnpm install`
- Build production bundle: `npm run build`
- Start dev watch mode: `npm run watch`
- Run tests: `npm test` (launches browser fixtures)
## Theming & Utilities
- Default global styles via `cssManager.defaultStyles`
- Theme-aware values with `cssManager.bdTheme(light, dark)`
- DOM utilities set up in `html/index.ts` using `@design.estate/dees-domtools`
## Documentation
- `readme.md` provides an overview of all components and basic usage
- Live examples in `.demo.ts` files
accessible via component `demo` static property
## Updates to this file
If you have pattern insisights or general changes to the codebase, please update this file.

View File

@ -1,6 +1,6 @@
{
"name": "@design.estate/dees-catalog",
"version": "1.4.1",
"version": "1.7.0",
"private": false,
"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",
@ -16,7 +16,7 @@
"license": "MIT",
"dependencies": {
"@design.estate/dees-domtools": "^2.1.1",
"@design.estate/dees-element": "^2.0.39",
"@design.estate/dees-element": "^2.0.41",
"@design.estate/dees-wcctools": "^1.0.90",
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "^6.7.2",
@ -25,11 +25,12 @@
"@push.rocks/smarti18n": "^1.0.4",
"@push.rocks/smartpromise": "^4.2.0",
"@push.rocks/smartstring": "^4.0.15",
"@tsclass/tsclass": "^4.4.0",
"@tsclass/tsclass": "^9.0.0",
"@webcontainer/api": "1.2.0",
"apexcharts": "^4.3.0",
"highlight.js": "11.11.1",
"ibantools": "^4.5.1",
"lucide": "^0.501.0",
"monaco-editor": "^0.52.2",
"pdfjs-dist": "^4.10.38",
"xterm": "^5.3.0",
@ -42,7 +43,7 @@
"@git.zone/tswatch": "^2.0.37",
"@push.rocks/projectinfo": "^5.0.2",
"@push.rocks/tapbundle": "^5.5.6",
"@types/node": "^22.10.7"
"@types/node": "^22.14.1"
},
"files": [
"ts/**/*",
@ -84,5 +85,6 @@
"Web Applications",
"Modern Web",
"Frontend Development"
]
],
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
}

4320
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

1223
readme.md

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@design.estate/dees-catalog',
version: '1.4.1',
version: '1.7.0',
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
}

View File

@ -0,0 +1,12 @@
import { html } from '@design.estate/dees-element';
export const demoFunc = () => html`
<div style="display: flex; gap: 8px; align-items: center;">
<dees-badge .text=${'Default'}></dees-badge>
<dees-badge .type=${'primary'} .text=${'Primary'}></dees-badge>
<dees-badge .type=${'success'} .text=${'Success'}></dees-badge>
<dees-badge .type=${'warning'} .text=${'Warning'}></dees-badge>
<dees-badge .type=${'error'} .text=${'Error'}></dees-badge>
<dees-badge .type=${'primary'} .rounded=${true} .text=${'Rounded'}></dees-badge>
</div>
`;

View File

@ -0,0 +1,96 @@
import {
DeesElement,
css,
cssManager,
customElement,
html,
property,
type CSSResult,
type TemplateResult,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import { demoFunc } from './dees-badge.demo.js';
declare global {
interface HTMLElementTagNameMap {
'dees-badge': DeesBadge;
}
}
@customElement('dees-badge')
export class DeesBadge extends DeesElement {
public static demo = demoFunc;
@property({ type: String })
public type: 'default' | 'primary' | 'success' | 'warning' | 'error' = 'default';
@property({ type: String })
public text: string = '';
@property({ type: Boolean })
public rounded: boolean = false;
constructor() {
super();
domtools.elementBasic.setup();
}
public static styles = [
cssManager.defaultStyles,
css`
:host {
display: inline-block;
}
.badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 2px 8px;
font-size: 12px;
font-weight: 500;
line-height: 1.5;
border-radius: 4px;
white-space: nowrap;
}
.badge.rounded {
border-radius: 12px;
}
.badge.default {
background: ${cssManager.bdTheme('#f5f5f5', '#333')};
color: ${cssManager.bdTheme('#666', '#ccc')};
}
.badge.primary {
background: #0050b9;
color: #ffffff;
}
.badge.success {
background: #2e7d32;
color: #ffffff;
}
.badge.warning {
background: #ed6c02;
color: #ffffff;
}
.badge.error {
background: #e4002b;
color: #ffffff;
}
`,
];
public render(): TemplateResult {
return html`
<div class="badge ${this.type} ${this.rounded ? 'rounded' : ''}">
${this.text}
</div>
`;
}
}

View File

@ -0,0 +1,14 @@
import { html } from '@design.estate/dees-element';
export function demoFunc() {
return html`
<dees-heading level="1">This is a H1 heading</dees-heading>
<dees-heading level="2">This is a H2 heading</dees-heading>
<dees-heading level="3">This is a H3 heading</dees-heading>
<dees-heading level="4">This is a H4 heading</dees-heading>
<dees-heading level="5">This is a H5 heading</dees-heading>
<dees-heading level="6">This is a H6 heading</dees-heading>
<dees-heading level="hr">This is an hr heading</dees-heading>
<dees-heading level="hr-small">This is an hr small heading</dees-heading>
`;
}

View File

@ -0,0 +1,115 @@
import {
customElement,
html,
css,
property,
cssManager,
type TemplateResult,
DeesElement,
type CSSResult,
} from '@design.estate/dees-element';
import { demoFunc } from './dees-heading.demo.js';
declare global {
interface HTMLElementTagNameMap {
'dees-heading': DeesHeading;
}
}
@customElement('dees-heading')
export class DeesHeading extends DeesElement {
// demo
public static demo = demoFunc;
// properties
/**
* Heading level: 1-6 for h1-h6, or 'hr' for horizontal rule style
*/
@property({ type: String, reflect: true })
public level: '1' | '2' | '3' | '4' | '5' | '6' | 'hr' | 'hr-small' = '1';
// STATIC STYLES
public static styles: CSSResult[] = [
cssManager.defaultStyles,
css`
/* Heading styles */
h1, h2, h3, h4, h5, h6 {
margin: 16px 0 8px;
font-weight: 600;
color: ${cssManager.bdTheme('#000', '#fff')};
}
h1 { font-size: 32px; font-family: 'Cal Sans'; letter-spacing: 0.025em;}
h2 { font-size: 28px; }
h3 { font-size: 24px; }
h4 { font-size: 20px; }
h5 { font-size: 16px; }
h6 { font-size: 14px; }
/* Horizontal rule style heading */
.heading-hr {
display: flex;
align-items: center;
text-align: center;
margin: 16px 0;
color: ${cssManager.bdTheme('#000', '#fff')};
}
/* Fade lines toward and away from text for hr style */
.heading-hr::before {
content: '';
flex: 1;
height: 1px;
/* fade in toward center */
background: ${cssManager.bdTheme(
'linear-gradient(to right, transparent, #ccc)',
'linear-gradient(to right, transparent, #333)'
)};
margin: 0 8px;
}
.heading-hr::after {
content: '';
flex: 1;
height: 1px;
/* fade out away from center */
background: ${cssManager.bdTheme(
'linear-gradient(to right, #ccc, transparent)',
'linear-gradient(to right, #333, transparent)'
)};
margin: 0 8px;
}
/* Small hr variant with reduced margins */
.heading-hr.heading-hr-small {
margin: 8px 0;
font-size: 12px;
}
.heading-hr.heading-hr-small::before,
.heading-hr.heading-hr-small::after {
margin: 0 8px;
}
`,
];
// INSTANCE
public render(): TemplateResult {
switch (this.level) {
case '1':
return html`<h1><slot></slot></h1>`;
case '2':
return html`<h2><slot></slot></h2>`;
case '3':
return html`<h3><slot></slot></h3>`;
case '4':
return html`<h4><slot></slot></h4>`;
case '5':
return html`<h5><slot></slot></h5>`;
case '6':
return html`<h6><slot></slot></h6>`;
case 'hr':
return html`<div class="heading-hr"><slot></slot></div>`;
case 'hr-small':
return html`<div class="heading-hr heading-hr-small"><slot></slot></div>`;
default:
return html`<h1><slot></slot></h1>`;
}
}
}

View File

@ -1,31 +1,155 @@
import { html } from '@design.estate/dees-element';
import { icons, type IconWithPrefix } from './dees-icon.js';
import * as lucideIcons from 'lucide';
import { faIcons } from './dees-icon.js';
export const demoFunc = () => {
// Group FontAwesome icons by type
const faIcons = Object.keys(icons.fa);
// Extract Lucide icons from the lucideIcons object directly
// Log the first few keys to understand the structure
console.log('First few Lucide keys:', Object.keys(lucideIcons).slice(0, 5));
// Get all icon functions from lucideIcons (they have PascalCase names)
const lucideIconsList = Object.keys(lucideIcons)
.filter(key => {
// Skip utility functions and focus on icon components (first letter is uppercase)
const isUppercaseFirst = key[0] === key[0].toUpperCase() && key[0] !== key[0].toLowerCase();
const isFunction = typeof lucideIcons[key] === 'function';
const notUtility = !['createElement', 'createIcons', 'default'].includes(key);
return isFunction && isUppercaseFirst && notUtility;
})
.map(pascalName => {
// Convert PascalCase to camelCase
return pascalName.charAt(0).toLowerCase() + pascalName.slice(1);
});
// Log how many icons we found
console.log(`Found ${lucideIconsList.length} Lucide icons`);
// If we didn't find any, try an alternative approach
if (lucideIconsList.length === 0) {
console.log('Trying alternative approach to find Lucide icons');
// Try to get icon names from a known property if available
if (lucideIcons.icons) {
const iconSource = lucideIcons.icons || {};
lucideIconsList.push(...Object.keys(iconSource));
console.log(`Found ${lucideIconsList.length} icons via alternative method`);
}
}
export const demoFunc = () => html`
// Define the functions in TS scope instead of script tags
const searchIcons = (event: InputEvent) => {
const searchTerm = (event.target as HTMLInputElement).value.toLowerCase().trim();
// Get the demo container first, then search within it
const demoContainer = (event.target as HTMLElement).closest('.demoContainer');
const containers = demoContainer.querySelectorAll('.iconContainer');
containers.forEach(container => {
const iconName = container.getAttribute('data-name');
if (searchTerm === '') {
container.classList.remove('hidden');
} else if (iconName && iconName.includes(searchTerm)) {
container.classList.remove('hidden');
} else {
container.classList.add('hidden');
}
});
// Update counts - search within demoContainer
demoContainer.querySelectorAll('.section-container').forEach(section => {
const visibleIcons = section.querySelectorAll('.iconContainer:not(.hidden)').length;
const countElement = section.querySelector('.icon-count');
if (countElement) {
const totalIconsCount = section.classList.contains('fa-section')
? faIcons.length
: lucideIconsList.length;
countElement.textContent = visibleIcons === totalIconsCount
? `${totalIconsCount} icons`
: `${visibleIcons} of ${totalIconsCount} icons`;
}
});
};
const copyIconName = (iconNameToCopy: string, type: 'fa' | 'lucide') => {
// Use the new prefix format
const textToCopy = `${type}:${iconNameToCopy}`;
navigator.clipboard.writeText(textToCopy).then(() => {
// Find the event target
const currentEvent = window.event as MouseEvent;
const currentTarget = currentEvent.currentTarget as HTMLElement;
// Show feedback
const tooltip = currentTarget.querySelector('.copy-tooltip');
if (tooltip) {
tooltip.textContent = 'Copied!';
setTimeout(() => {
tooltip.textContent = 'Click to copy';
}, 2000);
}
});
};
return html`
<style>
.demoContainer {
width: 100%;
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
background: #111111;
padding: 10px; font-size: 30px;
padding: 20px;
font-size: 30px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
.search-container {
width: 100%;
margin-bottom: 20px;
display: flex;
}
#iconSearch {
flex: 1;
padding: 12px 16px;
font-size: 16px;
border: none;
border-radius: 4px;
background: #222;
color: #fff;
border: 1px solid #333;
}
#iconSearch:focus {
outline: none;
border-color: #e4002b;
}
dees-icon {
transition: color 0.02s;
transition: all 0.2s ease;
color: #ffffff;
}
dees-icon:hover {
color: #e4002b;
}
.iconContainer {
display: block;
padding: 16px 16px 0px 16px;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 16px 0px 16px;
border: 1px solid #333333;
margin-right: 8px;
margin-bottom: 8px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 4px;
transition: background-color 0.2s;
cursor: pointer;
position: relative;
}
.iconContainer:hover {
background-color: #222;
}
.iconName {
@ -33,23 +157,136 @@ export const demoFunc = () => html`
text-align: center;
color: #ccc;
background: #333333;
padding: 4px 8px;
padding-bottom: 4px;
padding: 6px 10px;
margin-left: -16px;
margin-right: -16px;
margin-top: 16px;
margin-top: 20px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 120px;
border-radius: 0 0 4px 4px;
}
.section-title {
width: 100%;
color: #ffffff;
font-size: 24px;
margin: 20px 0;
padding-bottom: 10px;
border-bottom: 1px solid #333333;
display: flex;
justify-content: space-between;
align-items: center;
}
.api-note {
font-size: 14px;
color: #e4002b;
margin-bottom: 20px;
padding: 10px;
border: 1px solid #e4002b;
border-radius: 4px;
background: rgba(228, 0, 43, 0.1);
}
.icon-count {
font-size: 14px;
color: #888;
font-weight: normal;
background: #222;
padding: 5px 10px;
border-radius: 20px;
}
.icons-grid {
display: flex;
flex-wrap: wrap;
width: 100%;
}
.section-container {
width: 100%;
margin-bottom: 30px;
}
.copy-tooltip {
position: absolute;
background: #333;
color: white;
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
top: -30px;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
}
.iconContainer:hover .copy-tooltip {
opacity: 1;
}
.iconContainer:hover dees-icon {
transform: scale(1.1);
}
.hidden {
display: none !important;
}
</style>
<div class="demoContainer">
${Object.keys(faIcons).map(
(iconName) => html`
<div class="iconContainer">
<dees-icon .iconFA=${iconName as any}></dees-icon>
<div class="iconName">${iconName}</div>
</div>
`
)}
<div class="demoContainer">
<div class="search-container">
<input type="text" id="iconSearch" placeholder="Search icons..." @input=${searchIcons}>
</div>
`;
<div class="api-note">
New API: Use <code>icon="fa:iconName"</code> or <code>icon="lucide:iconName"</code> instead of <code>iconFA</code>.
Click any icon to copy its new format to clipboard.
</div>
<div class="section-container fa-section">
<div class="section-title">
FontAwesome Icons
<span class="icon-count">${faIcons.length} icons</span>
</div>
<div class="icons-grid">
${faIcons.map(
(iconName) => {
const prefixedName = `fa:${iconName}`;
return html`
<div class="iconContainer fa-icon" data-name=${iconName.toLowerCase()} @click=${() => copyIconName(iconName, 'fa')}>
<dees-icon .icon=${prefixedName as IconWithPrefix} iconSize="24"></dees-icon>
<div class="iconName">${iconName}</div>
<span class="copy-tooltip">Click to copy</span>
</div>
`;
}
)}
</div>
</div>
<div class="section-container lucide-section">
<div class="section-title">
Lucide Icons
<span class="icon-count">${lucideIconsList.length} icons</span>
</div>
<div class="icons-grid">
${lucideIconsList.map(
(iconName) => {
const prefixedName = `lucide:${iconName}`;
return html`
<div class="iconContainer lucide-icon" data-name=${iconName.toLowerCase()} @click=${() => copyIconName(iconName, 'lucide')}>
<dees-icon .icon=${prefixedName as IconWithPrefix} iconSize="24"></dees-icon>
<div class="iconName">${iconName}</div>
<span class="copy-tooltip">Click to copy</span>
</div>
`;
}
)}
</div>
</div>
</div>
`;
};

View File

@ -75,7 +75,12 @@ import {
} from '@fortawesome/free-solid-svg-icons';
import { demoFunc } from './dees-icon.demo.js';
export const faIcons = {
// Import Lucide icons and the createElement function
import * as lucideIcons from 'lucide';
import { createElement } from 'lucide';
// Collect FontAwesome icons
const faIcons = {
// normal
arrowRight: faArrowRightSolid,
arrowUpRightFromSquare: faArrowUpRightFromSquareSolid,
@ -136,7 +141,32 @@ export const faIcons = {
twitter: faTwitter,
};
export type TIconKey = keyof typeof faIcons;
// Create a string literal type for all FA icons
type FAIconKey = keyof typeof faIcons;
// Create union types for the icons with prefixes
export type IconWithPrefix = `fa:${FAIconKey}` | `lucide:${string}`;
// Export only FontAwesome icons directly
export const icons = {
fa: faIcons
};
// Legacy type for backward compatibility
export type TIconKey = FAIconKey | `lucide:${string}`;
// Use a global static cache for all icons to reduce rendering
const iconCache = new Map<string, string>();
// Clear cache items occasionally to prevent memory leaks
const MAX_CACHE_SIZE = 500;
function limitCacheSize() {
if (iconCache.size > MAX_CACHE_SIZE) {
// Remove oldest entries (first 20% of items)
const keysToDelete = Array.from(iconCache.keys()).slice(0, MAX_CACHE_SIZE / 5);
keysToDelete.forEach(key => iconCache.delete(key));
}
}
declare global {
interface HTMLElementTagNameMap {
@ -148,31 +178,170 @@ declare global {
export class DeesIcon extends DeesElement {
public static demo = demoFunc;
/**
* @deprecated Use the `icon` property instead with format "fa:iconName" or "lucide:iconName"
*/
@property({
type: String
type: String,
converter: {
// Convert attribute string to property (for reflected attributes)
fromAttribute: (value: string): TIconKey => value as TIconKey,
// Convert property to attribute (for reflection)
toAttribute: (value: TIconKey): string => value
}
})
public iconFA: keyof typeof faIcons;
public iconFA?: TIconKey;
@property()
/**
* The preferred icon property. Use format "fa:iconName" or "lucide:iconName"
* Examples: "fa:check", "lucide:menu"
*/
@property({
type: String,
converter: {
fromAttribute: (value: string): IconWithPrefix => value as IconWithPrefix,
toAttribute: (value: IconWithPrefix): string => value
}
})
public icon?: IconWithPrefix;
@property({ type: Number })
public iconSize: number;
@property({ type: String })
public color: string = 'currentColor';
@property({ type: Number })
public strokeWidth: number = 2;
// For tracking when we need to re-render
private lastIcon: IconWithPrefix | TIconKey | null = null;
private lastIconSize: number | null = null;
private lastColor: string | null = null;
private lastStrokeWidth: number | null = null;
constructor() {
super();
domtools.elementBasic.setup();
}
/**
* Gets the effective icon value, supporting both the new `icon` property
* and the legacy `iconFA` property for backward compatibility.
* Prefers `icon` if both are set.
*/
private getEffectiveIcon(): IconWithPrefix | TIconKey | null {
// Prefer the new API
if (this.icon) {
return this.icon;
}
// Fall back to the old API
if (this.iconFA) {
// If iconFA is already in the proper format (lucide:name), use it directly
if (this.iconFA.startsWith('lucide:')) {
return this.iconFA;
}
// For FontAwesome icons with no prefix, add the prefix
return `fa:${this.iconFA}` as IconWithPrefix;
}
return null;
}
/**
* Parses an icon string into its type and name parts
* @param iconStr The icon string in format "type:name"
* @returns Object with type and name properties
*/
private parseIconString(iconStr: string): { type: 'fa' | 'lucide', name: string } {
if (iconStr.startsWith('fa:')) {
return {
type: 'fa',
name: iconStr.substring(3) // Remove 'fa:' prefix
};
} else if (iconStr.startsWith('lucide:')) {
return {
type: 'lucide',
name: iconStr.substring(7) // Remove 'lucide:' prefix
};
} else {
// For backward compatibility, assume FontAwesome if no prefix
return {
type: 'fa',
name: iconStr
};
}
}
private renderLucideIcon(iconName: string): string {
// Create a cache key based on all visual properties
const cacheKey = `lucide:${iconName}:${this.iconSize}:${this.color}:${this.strokeWidth}`;
// Check if we already have this icon in the cache
if (iconCache.has(cacheKey)) {
return iconCache.get(cacheKey) || '';
}
try {
// Get the Pascal case icon name (Menu instead of menu)
const pascalCaseName = iconName.charAt(0).toUpperCase() + iconName.slice(1);
// Check if the icon exists in lucideIcons
if (!lucideIcons[pascalCaseName]) {
console.warn(`Lucide icon '${pascalCaseName}' not found in lucideIcons object`);
return '';
}
// Use the exact pattern from Lucide documentation
const svgElement = createElement(lucideIcons[pascalCaseName], {
color: this.color,
size: this.iconSize,
strokeWidth: this.strokeWidth
});
if (!svgElement) {
console.warn(`createElement returned empty result for ${pascalCaseName}`);
return '';
}
// Get the HTML
const result = svgElement.outerHTML;
// Cache the result for future use
iconCache.set(cacheKey, result);
limitCacheSize();
return result;
} catch (error) {
console.error(`Error rendering Lucide icon ${iconName}:`, error);
// Create a fallback SVG with the icon name
return `<svg xmlns="http://www.w3.org/2000/svg" width="${this.iconSize}" height="${this.iconSize}" viewBox="0 0 24 24" fill="none" stroke="${this.color}" stroke-width="${this.strokeWidth}" stroke-linecap="round" stroke-linejoin="round">
<text x="50%" y="50%" font-size="6" text-anchor="middle" dominant-baseline="middle" fill="${this.color}">${iconName}</text>
</svg>`;
}
}
public static styles = [
cssManager.defaultStyles,
css`
:host {
display: block;
white-space: nowrap;
display: flex;
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
vertical-align: middle;
}
* {
transition: inherit !important;
/* Improve rendering performance */
#iconContainer svg {
display: block;
height: 100%;
width: 100%;
will-change: transform; /* Helps with animations */
contain: strict; /* Performance optimization */
}
`,
];
@ -181,8 +350,8 @@ export class DeesIcon extends DeesElement {
return html`
${domtools.elementBasic.styles}
<style>
#iconContainer svg {
display: block;
#iconContainer {
width: ${this.iconSize}px;
height: ${this.iconSize}px;
}
</style>
@ -190,14 +359,95 @@ export class DeesIcon extends DeesElement {
`;
}
public async updated() {
public updated() {
// If size is not specified, use font size as a base
if (!this.iconSize) {
this.iconSize = parseInt(globalThis.getComputedStyle(this).fontSize.replace(/\D/g,''));
}
if (this.iconFA) {
this.shadowRoot.querySelector('#iconContainer').innerHTML = this.iconFA
? icon(faIcons[this.iconFA]).html[0]
: 'icon not found';
// Get the effective icon (either from icon or iconFA property)
const effectiveIcon = this.getEffectiveIcon();
// Check if we actually need to update the icon
// This prevents unnecessary DOM operations when properties haven't changed
if (this.lastIcon === effectiveIcon &&
this.lastIconSize === this.iconSize &&
this.lastColor === this.color &&
this.lastStrokeWidth === this.strokeWidth) {
return; // No visual changes - skip update
}
// Update our "last properties" for future change detection
this.lastIcon = effectiveIcon;
this.lastIconSize = this.iconSize;
this.lastColor = this.color;
this.lastStrokeWidth = this.strokeWidth;
const container = this.shadowRoot?.querySelector('#iconContainer');
if (!container || !effectiveIcon) return;
try {
// Parse the icon string to get type and name
const { type, name } = this.parseIconString(effectiveIcon);
if (type === 'lucide') {
// For Lucide, use direct DOM manipulation as shown in the docs
// This approach avoids HTML string issues
container.innerHTML = ''; // Clear container
try {
// Convert to PascalCase
const pascalCaseName = name.charAt(0).toUpperCase() + name.slice(1);
if (lucideIcons[pascalCaseName]) {
// Use the documented pattern from Lucide docs
const svgElement = createElement(lucideIcons[pascalCaseName], {
color: this.color,
size: this.iconSize,
strokeWidth: this.strokeWidth
});
if (svgElement) {
// Directly append the element
container.appendChild(svgElement);
return; // Exit early since we've added the element
}
}
// If we reach here, something went wrong
throw new Error(`Could not create element for ${pascalCaseName}`);
} catch (error) {
console.error(`Error rendering Lucide icon:`, error);
// Fall back to the string-based approach
const iconHtml = this.renderLucideIcon(name);
if (iconHtml) {
container.innerHTML = iconHtml;
}
}
} else {
// Use FontAwesome rendering via HTML string
const faIcon = icons.fa[name as FAIconKey];
if (faIcon) {
const iconHtml = icon(faIcon).html[0];
container.innerHTML = iconHtml;
} else {
console.warn(`FontAwesome icon not found: ${name}`);
}
}
} catch (error) {
console.error(`Error updating icon ${effectiveIcon}:`, error);
}
}
}
// Clean up resources when element is removed
async disconnectedCallback() {
super.disconnectedCallback();
// Clear our references
this.lastIcon = null;
this.lastIconSize = null;
this.lastColor = null;
this.lastStrokeWidth = null;
}
}

View File

@ -0,0 +1,46 @@
import { html } from '@design.estate/dees-element';
export const demoFunc = () => {
const onChanged = (e: CustomEvent) => {
// find the demo wrapper and update the 'changed' log inside it
const wrapper = (e.target as HTMLElement).closest('.demoWrapper');
const el = wrapper?.querySelector('#changed');
if (el) el.textContent = `search-changed: ${e.detail.value}`;
};
const onSubmit = (e: CustomEvent) => {
// find the demo wrapper and update the 'submitted' log inside it
const wrapper = (e.target as HTMLElement).closest('.demoWrapper');
const el = wrapper?.querySelector('#submitted');
if (el) el.textContent = `search-submit: ${e.detail.value}`;
};
return html`
<style>
.demoWrapper {
display: block;
flex-direction: column;
align-items: center;
background: #888888;
}
.logs {
padding: 16px;
width: 600px;
color: #fff;
font-family: monospace;
}
.logs div {
margin: 4px 0;
}
</style>
<div class="demoWrapper">
<dees-searchbar
@search-changed=${onChanged}
@search-submit=${onSubmit}
></dees-searchbar>
<div class="logs">
<div id="changed">search-changed:</div>
<div id="submitted">search-submit:</div>
</div>
</div>
`;
};

View File

@ -0,0 +1,160 @@
import {
customElement,
DeesElement,
property,
html,
cssManager,
unsafeCSS,
css,
type TemplateResult,
domtools,
query,
} from '@design.estate/dees-element';
import * as colors from './00colors.js';
import { demoFunc } from './dees-searchbar.demo.js';
declare global {
interface HTMLElementTagNameMap {
'dees-searchbar': DeesSearchbar;
}
}
@customElement('dees-searchbar')
export class DeesSearchbar extends DeesElement {
// DEMO
public static demo = demoFunc;
// STATIC
public static styles = [
cssManager.defaultStyles,
css`
:host {
padding: 40px;
font-family: Dees Sans;
display: block;
background: ${cssManager.bdTheme('#eeeeeb', '#000000')};
}
.searchboxContainer {
position: relative;
margin: auto;
max-width: 800px;
background: ${cssManager.bdTheme('#00000015', '#ffffff15')};
--boxHeight: 60px;
height: var(--boxHeight);
border-radius: var(--boxHeight);
display: grid;
grid-template-columns: 1fr 140px;
justify-content: center;
align-items: center;
border-top: 1px solid ${cssManager.bdTheme('#00000015', '#ffffff20')};
}
input {
height: 100%;
width: 100%;
border: none;
background: none;
color: ${cssManager.bdTheme('#000000', '#eeeeeb')};
padding-left: 25px;
margin-right: -8px;
outline: none;
font-size: 16px;
}
.searchButton {
--buttonPadding: 8px;
background: ${cssManager.bdTheme('#eeeeeb', '#000000')};
color: ${cssManager.bdTheme('#000000', '#eeeeeb')};
line-height: calc(var(--boxHeight) - (var(--buttonPadding) * 2));
border-radius: var(--boxHeight);
transform: scale(1) ;
transform-origin: 50% 50%;
text-align: center;
transition: transform 0.1s, background 0.1s;
margin-right: var(--buttonPadding);
user-select: none;
}
.searchButton:hover {
color: #fff;
background: ${cssManager.bdTheme(colors.bright.blue, colors.dark.blue)};
}
.searchButton:active {
color: #fff;
background: ${cssManager.bdTheme(colors.bright.blueActive, colors.dark.blueActive)};
transform: scale(0.98);
}
.filters {
margin: auto;
max-width: 800px;
}
`,
];
// INSTANCE
@property()
public filters = [];
@query('input')
public searchInput!: HTMLInputElement;
@query('.searchButton')
public searchButton!: HTMLElement;
constructor() {
super();
}
public render(): TemplateResult {
return html`
<div class="searchboxContainer">
<input type="text" placeholder="Your Skills (e.g. TypeScript, Rust, Projectmanagement)" />
<div class="searchButton">Search -></div>
</div>
${this.filters.length > 0 ? html`
<div class="filters">
<dees-heading level="hr-small">Filters</dees-heading>
<dees-input-dropdown .label=${'location'}></dees-input-dropdown>
</div>
` : html``}
`;
}
/**
* Lifecycle: after first render, wire up events for input and submit actions
*/
public firstUpdated(): void {
// dispatch change on each input
this.searchInput.addEventListener('input', () => {
this.dispatchEvent(new CustomEvent('search-changed', {
bubbles: true,
composed: true,
detail: { value: this.searchInput.value }
}));
});
// submit on Enter key
this.searchInput.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Enter') {
this._dispatchSubmit();
}
});
// submit on button click
this.searchButton.addEventListener('click', () => this._dispatchSubmit());
}
/**
* Dispatch a submit event with the current search value
*/
private _dispatchSubmit(): void {
this.dispatchEvent(new CustomEvent('search-submit', {
bubbles: true,
composed: true,
detail: { value: this.searchInput.value }
}));
}
}

View File

@ -12,7 +12,7 @@ import {
unsafeCSS,
type CSSResult,
state,
resolveExec,
directives,
} from '@design.estate/dees-element';
import { DeesContextmenu } from './dees-contextmenu.js';
@ -415,7 +415,7 @@ export class DeesTable<T> extends DeesElement {
<div class="heading heading2">${this.heading2}</div>
</div>
<div class="headerActions">
${resolveExec(async () => {
${directives.resolveExec(async () => {
const resultArray: TemplateResult[] = [];
for (const action of this.dataActions) {
if (!action.type.includes('header')) continue;
@ -634,7 +634,7 @@ export class DeesTable<T> extends DeesElement {
selected
</div>
<div class="footerActions">
${resolveExec(async () => {
${directives.resolveExec(async () => {
const resultArray: TemplateResult[] = [];
for (const action of this.dataActions) {
if (!action.type.includes('footer')) continue;

View File

@ -4,6 +4,7 @@ export * from './dees-appui-base.js';
export * from './dees-appui-maincontent.js';
export * from './dees-appui-mainmenu.js';
export * from './dees-appui-mainselector.js';
export * from './dees-badge.js';
export * from './dees-button-exit.js';
export * from './dees-button.js';
export * from './dees-chart-area.js';
@ -17,6 +18,7 @@ export * from './dees-editor-markdown.js';
export * from './dees-editor-markdownoutlet.js';
export * from './dees-form-submit.js';
export * from './dees-form.js';
export * from './dees-heading.js';
export * from './dees-hint.js';
export * from './dees-icon.js';
export * from './dees-input-checkbox.js';
@ -34,6 +36,7 @@ export * from './dees-mobilenavigation.js';
export * from './dees-modal.js';
export * from './dees-input-multitoggle.js';
export * from './dees-pdf.js';
export * from './dees-searchbar.js';
export * from './dees-simple-appdash.js';
export * from './dees-simple-login.js';
export * from './dees-speechbubble.js';