Add comprehensive component library expansion with new UI components
This commit includes multiple new components and improvements to the UI essentials library: ## New Components Added: - **Accordion**: Expandable/collapsible content panels with full accessibility - **Alert**: Notification and messaging component with variants and dismiss functionality - **Popover**: Floating content containers with positioning and trigger options - **Timeline**: Chronological event display with customizable styling - **Tree View**: Hierarchical data display with expand/collapse functionality - **Toast**: Notification component (previously committed, includes style refinements) ## Component Features: - Full TypeScript implementation with Angular 19+ patterns - Comprehensive SCSS styling using semantic design tokens exclusively - Multiple size variants (sm, md, lg) and color variants (primary, success, warning, danger, info) - Accessibility support with ARIA attributes and keyboard navigation - Responsive design with mobile-first approach - Interactive demos showcasing all component features - Integration with existing design system and routing ## Demo Applications: - Created comprehensive demo components for each new component - Interactive examples with live code demonstrations - Integrated into main demo application routing and navigation ## Documentation: - Added COMPONENT_CREATION_TEMPLATE.md with detailed guidelines - Comprehensive component creation patterns and best practices - Design token usage guidelines and validation rules ## Code Quality: - Follows established Angular and SCSS conventions - Uses only verified semantic design tokens - Maintains consistency with existing component architecture - Comprehensive error handling and edge case management 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TreeViewComponent } from "../../../../../ui-essentials/src/public-api";
|
||||
import { TreeNode } from '../../../../../../dist/ui-essentials/lib/components/data-display/tree-view/tree-view.component';
|
||||
|
||||
@Component({
|
||||
selector: 'ui-tree-view-demo',
|
||||
standalone: true,
|
||||
imports: [CommonModule, TreeViewComponent],
|
||||
template: `
|
||||
<div class="demo-container">
|
||||
<h2>Tree View Demo</h2>
|
||||
|
||||
<!-- Size Variants -->
|
||||
<section class="demo-section">
|
||||
<h3>Sizes</h3>
|
||||
<div class="demo-row">
|
||||
@for (size of sizes; track size) {
|
||||
<div class="demo-column">
|
||||
<h4>{{ size | titlecase }} Size</h4>
|
||||
<ui-tree-view
|
||||
[size]="size"
|
||||
[nodes]="basicNodes"
|
||||
(nodeSelected)="handleNodeSelected($event)"
|
||||
(nodeToggled)="handleNodeToggled($event)">
|
||||
</ui-tree-view>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Color Variants -->
|
||||
<section class="demo-section">
|
||||
<h3>Variants</h3>
|
||||
<div class="demo-row">
|
||||
@for (variant of variants; track variant) {
|
||||
<div class="demo-column">
|
||||
<h4>{{ variant | titlecase }} Variant</h4>
|
||||
<ui-tree-view
|
||||
[variant]="variant"
|
||||
[nodes]="basicNodes"
|
||||
(nodeSelected)="handleNodeSelected($event)"
|
||||
(nodeToggled)="handleNodeToggled($event)">
|
||||
</ui-tree-view>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="demo-section">
|
||||
<h3>Features</h3>
|
||||
<div class="demo-row">
|
||||
<!-- Multi-select -->
|
||||
<div class="demo-column">
|
||||
<h4>Multi-Select</h4>
|
||||
<ui-tree-view
|
||||
[nodes]="featureNodes"
|
||||
[multiSelect]="true"
|
||||
(nodeSelected)="handleNodeSelected($event)"
|
||||
(nodeToggled)="handleNodeToggled($event)">
|
||||
</ui-tree-view>
|
||||
</div>
|
||||
|
||||
<!-- With Icons -->
|
||||
<div class="demo-column">
|
||||
<h4>With Icons</h4>
|
||||
<ui-tree-view
|
||||
[nodes]="iconNodes"
|
||||
[showIcons]="true"
|
||||
(nodeSelected)="handleNodeSelected($event)"
|
||||
(nodeToggled)="handleNodeToggled($event)">
|
||||
</ui-tree-view>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- States -->
|
||||
<section class="demo-section">
|
||||
<h3>States</h3>
|
||||
<div class="demo-row">
|
||||
<!-- Loading -->
|
||||
<div class="demo-column">
|
||||
<h4>Loading</h4>
|
||||
<ui-tree-view [loading]="true"></ui-tree-view>
|
||||
</div>
|
||||
|
||||
<!-- Empty -->
|
||||
<div class="demo-column">
|
||||
<h4>Empty</h4>
|
||||
<ui-tree-view
|
||||
[nodes]="[]"
|
||||
emptyMessage="No files found">
|
||||
</ui-tree-view>
|
||||
</div>
|
||||
|
||||
<!-- Disabled Nodes -->
|
||||
<div class="demo-column">
|
||||
<h4>Disabled Nodes</h4>
|
||||
<ui-tree-view
|
||||
[nodes]="disabledNodes"
|
||||
(nodeSelected)="handleNodeSelected($event)"
|
||||
(nodeToggled)="handleNodeToggled($event)">
|
||||
</ui-tree-view>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Interactive Controls -->
|
||||
<section class="demo-section">
|
||||
<h3>Interactive Controls</h3>
|
||||
<div class="demo-controls">
|
||||
<button class="demo-button" (click)="expandAll()">Expand All</button>
|
||||
<button class="demo-button" (click)="collapseAll()">Collapse All</button>
|
||||
<button class="demo-button" (click)="getSelected()">Get Selected</button>
|
||||
<button class="demo-button" (click)="clearSelection()">Clear Selection</button>
|
||||
</div>
|
||||
|
||||
<ui-tree-view
|
||||
#interactiveTree
|
||||
[nodes]="interactiveNodes"
|
||||
[multiSelect]="true"
|
||||
(nodeSelected)="handleNodeSelected($event)"
|
||||
(nodeToggled)="handleNodeToggled($event)"
|
||||
(nodesChanged)="handleNodesChanged($event)">
|
||||
</ui-tree-view>
|
||||
|
||||
@if (selectedInfo) {
|
||||
<div class="demo-info">
|
||||
<strong>Selected:</strong> {{ selectedInfo }}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (lastAction) {
|
||||
<div class="demo-info">
|
||||
<strong>Last Action:</strong> {{ lastAction }}
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
|
||||
<!-- File System Example -->
|
||||
<section class="demo-section">
|
||||
<h3>File System Example</h3>
|
||||
<ui-tree-view
|
||||
[nodes]="fileSystemNodes"
|
||||
[showIcons]="true"
|
||||
size="sm"
|
||||
emptyMessage="No files or folders"
|
||||
(nodeSelected)="handleFileSystemSelect($event)"
|
||||
(nodeToggled)="handleNodeToggled($event)">
|
||||
</ui-tree-view>
|
||||
|
||||
@if (selectedFile) {
|
||||
<div class="demo-info">
|
||||
<strong>Selected File:</strong> {{ selectedFile }}
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
`,
|
||||
styleUrl: './tree-view-demo.component.scss'
|
||||
})
|
||||
export class TreeViewDemoComponent {
|
||||
sizes = ['sm', 'md', 'lg'] as const;
|
||||
variants = ['primary', 'secondary'] as const;
|
||||
|
||||
selectedInfo = '';
|
||||
lastAction = '';
|
||||
selectedFile = '';
|
||||
|
||||
basicNodes: TreeNode[] = [
|
||||
{
|
||||
id: 'item1',
|
||||
label: 'Item 1',
|
||||
children: [
|
||||
{ id: 'item1-1', label: 'Sub Item 1.1' },
|
||||
{ id: 'item1-2', label: 'Sub Item 1.2' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'item2',
|
||||
label: 'Item 2',
|
||||
children: [
|
||||
{
|
||||
id: 'item2-1',
|
||||
label: 'Sub Item 2.1',
|
||||
children: [
|
||||
{ id: 'item2-1-1', label: 'Sub Sub Item 2.1.1' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{ id: 'item3', label: 'Item 3' }
|
||||
];
|
||||
|
||||
featureNodes: TreeNode[] = [
|
||||
{
|
||||
id: 'feature1',
|
||||
label: 'Authentication',
|
||||
expanded: true,
|
||||
children: [
|
||||
{ id: 'feature1-1', label: 'Login', selected: true },
|
||||
{ id: 'feature1-2', label: 'Registration' },
|
||||
{ id: 'feature1-3', label: 'Password Reset' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'feature2',
|
||||
label: 'User Management',
|
||||
children: [
|
||||
{ id: 'feature2-1', label: 'User Profile' },
|
||||
{ id: 'feature2-2', label: 'User Settings', selected: true }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
iconNodes: TreeNode[] = [
|
||||
{
|
||||
id: 'folder1',
|
||||
label: 'Documents',
|
||||
icon: '📁',
|
||||
expanded: true,
|
||||
children: [
|
||||
{ id: 'file1', label: 'Report.pdf', icon: '📄' },
|
||||
{ id: 'file2', label: 'Presentation.pptx', icon: '📊' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'folder2',
|
||||
label: 'Images',
|
||||
icon: '📁',
|
||||
children: [
|
||||
{ id: 'file3', label: 'Photo1.jpg', icon: '🖼️' },
|
||||
{ id: 'file4', label: 'Logo.png', icon: '🖼️' }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
disabledNodes: TreeNode[] = [
|
||||
{
|
||||
id: 'enabled1',
|
||||
label: 'Enabled Item',
|
||||
children: [
|
||||
{ id: 'disabled1', label: 'Disabled Sub Item', disabled: true },
|
||||
{ id: 'enabled2', label: 'Enabled Sub Item' }
|
||||
]
|
||||
},
|
||||
{ id: 'disabled2', label: 'Disabled Item', disabled: true }
|
||||
];
|
||||
|
||||
interactiveNodes: TreeNode[] = JSON.parse(JSON.stringify(this.basicNodes));
|
||||
|
||||
fileSystemNodes: TreeNode[] = [
|
||||
{
|
||||
id: 'src',
|
||||
label: 'src',
|
||||
icon: '📁',
|
||||
expanded: true,
|
||||
children: [
|
||||
{
|
||||
id: 'components',
|
||||
label: 'components',
|
||||
icon: '📁',
|
||||
children: [
|
||||
{ id: 'button.ts', label: 'button.component.ts', icon: '📄' },
|
||||
{ id: 'input.ts', label: 'input.component.ts', icon: '📄' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'services',
|
||||
label: 'services',
|
||||
icon: '📁',
|
||||
children: [
|
||||
{ id: 'api.ts', label: 'api.service.ts', icon: '📄' },
|
||||
{ id: 'auth.ts', label: 'auth.service.ts', icon: '📄' }
|
||||
]
|
||||
},
|
||||
{ id: 'main.ts', label: 'main.ts', icon: '📄' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'assets',
|
||||
label: 'assets',
|
||||
icon: '📁',
|
||||
children: [
|
||||
{ id: 'logo.png', label: 'logo.png', icon: '🖼️' },
|
||||
{ id: 'styles.css', label: 'styles.css', icon: '📄' }
|
||||
]
|
||||
},
|
||||
{ id: 'package.json', label: 'package.json', icon: '📄' },
|
||||
{ id: 'readme.md', label: 'README.md', icon: '📄' }
|
||||
];
|
||||
|
||||
handleNodeSelected(event: { node: TreeNode; selected: boolean }): void {
|
||||
this.lastAction = `${event.selected ? 'Selected' : 'Deselected'}: ${event.node.label}`;
|
||||
console.log('Node selected:', event);
|
||||
}
|
||||
|
||||
handleNodeToggled(event: { node: TreeNode; expanded: boolean }): void {
|
||||
this.lastAction = `${event.expanded ? 'Expanded' : 'Collapsed'}: ${event.node.label}`;
|
||||
console.log('Node toggled:', event);
|
||||
}
|
||||
|
||||
handleNodesChanged(nodes: TreeNode[]): void {
|
||||
this.interactiveNodes = nodes;
|
||||
const selected = this.getSelectedNodes(nodes);
|
||||
this.selectedInfo = selected.map(n => n.label).join(', ') || 'None';
|
||||
}
|
||||
|
||||
handleFileSystemSelect(event: { node: TreeNode; selected: boolean }): void {
|
||||
this.selectedFile = event.selected ? event.node.label : '';
|
||||
}
|
||||
|
||||
expandAll(): void {
|
||||
this.setAllExpanded(this.interactiveNodes, true);
|
||||
this.lastAction = 'Expanded all nodes';
|
||||
}
|
||||
|
||||
collapseAll(): void {
|
||||
this.setAllExpanded(this.interactiveNodes, false);
|
||||
this.lastAction = 'Collapsed all nodes';
|
||||
}
|
||||
|
||||
getSelected(): void {
|
||||
const selected = this.getSelectedNodes(this.interactiveNodes);
|
||||
this.selectedInfo = selected.map(n => n.label).join(', ') || 'None';
|
||||
this.lastAction = `Found ${selected.length} selected nodes`;
|
||||
}
|
||||
|
||||
clearSelection(): void {
|
||||
this.clearAllSelections(this.interactiveNodes);
|
||||
this.selectedInfo = 'None';
|
||||
this.lastAction = 'Cleared all selections';
|
||||
}
|
||||
|
||||
private setAllExpanded(nodes: TreeNode[], expanded: boolean): void {
|
||||
for (const node of nodes) {
|
||||
if (node.children && node.children.length > 0) {
|
||||
node.expanded = expanded;
|
||||
this.setAllExpanded(node.children, expanded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getSelectedNodes(nodes: TreeNode[]): TreeNode[] {
|
||||
const selected: TreeNode[] = [];
|
||||
for (const node of nodes) {
|
||||
if (node.selected) {
|
||||
selected.push(node);
|
||||
}
|
||||
if (node.children) {
|
||||
selected.push(...this.getSelectedNodes(node.children));
|
||||
}
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
private clearAllSelections(nodes: TreeNode[]): void {
|
||||
for (const node of nodes) {
|
||||
node.selected = false;
|
||||
if (node.children) {
|
||||
this.clearAllSelections(node.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user