diff --git a/changelog.md b/changelog.md
index 7627efe..dd829e9 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,15 @@
# Changelog
+## 2025-12-18 - 1.2.0 - feat(icons)
+migrate icon usage to the new dees-icon API and integrate collaboration sidebar into the editor
+
+- Replaced deprecated .iconFA with .icon across multiple components
+- Updated lucide icon identifiers to PascalCase/camelCase to match new dees-icon format
+- Added sdig-collaboration-sidebar component and exported it from elements index
+- Integrated a toggleable editor sidebar (PanelRight) and wired comment/suggestion navigation & add-comment events in sdig-contracteditor
+- Added development hints (readme.hints.md) documenting dees-icon usage and icon name formats
+- Minor UI/styling tweak: .btn-ghost.active appearance
+
## 2025-12-18 - 1.1.0 - feat(catalog)
add ContractEditor and many editor subcomponents; implement SignPad and SignBox; update README and bump dependencies
diff --git a/readme.hints.md b/readme.hints.md
index e69de29..9dd081f 100644
--- a/readme.hints.md
+++ b/readme.hints.md
@@ -0,0 +1,31 @@
+# Development Hints for @signature.digital/catalog
+
+## dees-icon Usage
+
+**Important**: Use the `.icon` property, NOT `.iconFA` (deprecated).
+
+### Format
+```html
+
+```
+
+### Lucide Icons (PascalCase)
+Lucide icons use **PascalCase** names:
+- `lucide:CheckCircle` ✓
+- `lucide:UserPlus` ✓
+- `lucide:PenTool` ✓
+- `lucide:Mail` ✓
+- `lucide:Users` ✓
+
+**Wrong formats**:
+- `lucide:check-circle` ✗ (kebab-case doesn't work)
+- `lucide:userPlus` ✗ (camelCase doesn't work)
+
+### FontAwesome Icons (camelCase)
+FontAwesome icons use **camelCase** names:
+- `fa:arrowRight`
+- `fa:magnifyingGlass`
+- `fa:penToSquare`
+
+### Documentation
+See: https://code.foss.global/design.estate/dees-catalog/src/branch/main/readme.icons.md
diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts
index 66d8e8f..c43e74f 100644
--- a/ts_web/00_commitinfo_data.ts
+++ b/ts_web/00_commitinfo_data.ts
@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@signature.digital/catalog',
- version: '1.1.0',
+ version: '1.2.0',
description: 'A comprehensive catalog of customizable web components designed for building and managing e-signature applications.'
}
diff --git a/ts_web/elements/index.ts b/ts_web/elements/index.ts
index 873bd7a..3283b75 100644
--- a/ts_web/elements/index.ts
+++ b/ts_web/elements/index.ts
@@ -11,6 +11,7 @@ export * from './sdig-contract-signatures/index.js';
export * from './sdig-contract-attachments/index.js';
export * from './sdig-contract-collaboration/index.js';
export * from './sdig-contract-audit/index.js';
+export * from './sdig-collaboration-sidebar/index.js';
// Signature components
export * from './sdig-signbox/index.js';
diff --git a/ts_web/elements/sdig-collaboration-sidebar/index.ts b/ts_web/elements/sdig-collaboration-sidebar/index.ts
new file mode 100644
index 0000000..f8500cd
--- /dev/null
+++ b/ts_web/elements/sdig-collaboration-sidebar/index.ts
@@ -0,0 +1 @@
+export * from './sdig-collaboration-sidebar.js';
diff --git a/ts_web/elements/sdig-collaboration-sidebar/sdig-collaboration-sidebar.ts b/ts_web/elements/sdig-collaboration-sidebar/sdig-collaboration-sidebar.ts
new file mode 100644
index 0000000..51e4ac2
--- /dev/null
+++ b/ts_web/elements/sdig-collaboration-sidebar/sdig-collaboration-sidebar.ts
@@ -0,0 +1,846 @@
+/**
+ * @file sdig-collaboration-sidebar.ts
+ * @description Compact collaboration sidebar for the contract editor
+ */
+
+import {
+ DeesElement,
+ property,
+ html,
+ customElement,
+ type TemplateResult,
+ css,
+ cssManager,
+ state,
+} from '@design.estate/dees-element';
+
+import * as plugins from '../../plugins.js';
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'sdig-collaboration-sidebar': SdigCollaborationSidebar;
+ }
+}
+
+// Comment interface
+interface IComment {
+ id: string;
+ userId: string;
+ userName: string;
+ userColor: string;
+ content: string;
+ createdAt: number;
+ updatedAt?: number;
+ anchorPath?: string;
+ anchorText?: string;
+ resolved: boolean;
+ replies: IComment[];
+}
+
+// Suggestion interface
+interface ISuggestion {
+ id: string;
+ userId: string;
+ userName: string;
+ userColor: string;
+ originalText: string;
+ suggestedText: string;
+ path: string;
+ status: 'pending' | 'accepted' | 'rejected';
+ createdAt: number;
+}
+
+// Presence interface
+interface IPresence {
+ userId: string;
+ userName: string;
+ userColor: string;
+ currentSection: string;
+ cursorPosition?: { path: string; offset: number };
+ lastActive: number;
+}
+
+@customElement('sdig-collaboration-sidebar')
+export class SdigCollaborationSidebar extends DeesElement {
+ // ============================================================================
+ // STATIC
+ // ============================================================================
+
+ public static demo = () => html`
+
+
+
+ `;
+
+ public static styles = [
+ cssManager.defaultStyles,
+ css`
+ :host {
+ display: block;
+ height: 100%;
+ overflow: hidden;
+ }
+
+ .sidebar-container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ }
+
+ /* Presence bar */
+ .presence-bar {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 16px;
+ background: ${cssManager.bdTheme('#f9fafb', '#111111')};
+ border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
+ }
+
+ .presence-avatars {
+ display: flex;
+ align-items: center;
+ }
+
+ .presence-avatar {
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 11px;
+ font-weight: 600;
+ color: white;
+ margin-left: -6px;
+ border: 2px solid ${cssManager.bdTheme('#f9fafb', '#111111')};
+ cursor: pointer;
+ position: relative;
+ }
+
+ .presence-avatar:first-child {
+ margin-left: 0;
+ }
+
+ .presence-avatar .status-dot {
+ position: absolute;
+ bottom: -1px;
+ right: -1px;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: #10b981;
+ border: 2px solid ${cssManager.bdTheme('#f9fafb', '#111111')};
+ }
+
+ .presence-avatar .status-dot.away {
+ background: #f59e0b;
+ }
+
+ .presence-count {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ background: ${cssManager.bdTheme('#e5e7eb', '#3f3f46')};
+ color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
+ font-size: 11px;
+ font-weight: 600;
+ margin-left: -6px;
+ border: 2px solid ${cssManager.bdTheme('#f9fafb', '#111111')};
+ }
+
+ .presence-label {
+ font-size: 12px;
+ color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
+ }
+
+ /* Scrollable content */
+ .sidebar-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: 12px;
+ }
+
+ /* Collapsible sections */
+ .collapsible-section {
+ margin-bottom: 12px;
+ border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
+ border-radius: 8px;
+ overflow: hidden;
+ background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
+ }
+
+ .section-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10px 12px;
+ background: ${cssManager.bdTheme('#f9fafb', '#111111')};
+ cursor: pointer;
+ user-select: none;
+ transition: background 0.15s ease;
+ }
+
+ .section-header:hover {
+ background: ${cssManager.bdTheme('#f3f4f6', '#18181b')};
+ }
+
+ .section-title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 13px;
+ font-weight: 600;
+ color: ${cssManager.bdTheme('#374151', '#d1d5db')};
+ }
+
+ .section-title dees-icon {
+ font-size: 14px;
+ color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
+ }
+
+ .section-badge {
+ padding: 2px 6px;
+ border-radius: 9999px;
+ font-size: 10px;
+ font-weight: 600;
+ background: ${cssManager.bdTheme('#dbeafe', '#1e3a5f')};
+ color: ${cssManager.bdTheme('#1e40af', '#93c5fd')};
+ }
+
+ .section-chevron {
+ font-size: 14px;
+ color: ${cssManager.bdTheme('#9ca3af', '#6b7280')};
+ transition: transform 0.2s ease;
+ }
+
+ .section-chevron.expanded {
+ transform: rotate(180deg);
+ }
+
+ .section-body {
+ padding: 0;
+ max-height: 0;
+ overflow: hidden;
+ transition: max-height 0.2s ease, padding 0.2s ease;
+ }
+
+ .section-body.expanded {
+ padding: 12px;
+ max-height: 1000px;
+ }
+
+ /* Compact comment cards */
+ .comment-card {
+ display: flex;
+ gap: 10px;
+ padding: 10px;
+ background: ${cssManager.bdTheme('#f9fafb', '#111111')};
+ border-radius: 6px;
+ margin-bottom: 8px;
+ cursor: pointer;
+ transition: background 0.15s ease;
+ }
+
+ .comment-card:last-child {
+ margin-bottom: 0;
+ }
+
+ .comment-card:hover {
+ background: ${cssManager.bdTheme('#f3f4f6', '#18181b')};
+ }
+
+ .comment-card.resolved {
+ opacity: 0.6;
+ }
+
+ .comment-avatar {
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 10px;
+ font-weight: 600;
+ color: white;
+ flex-shrink: 0;
+ }
+
+ .comment-body {
+ flex: 1;
+ min-width: 0;
+ }
+
+ .comment-meta {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin-bottom: 4px;
+ }
+
+ .comment-author {
+ font-size: 12px;
+ font-weight: 600;
+ color: ${cssManager.bdTheme('#374151', '#d1d5db')};
+ }
+
+ .comment-time {
+ font-size: 10px;
+ color: ${cssManager.bdTheme('#9ca3af', '#6b7280')};
+ }
+
+ .comment-preview {
+ font-size: 12px;
+ line-height: 1.4;
+ color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .comment-replies {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 10px;
+ color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
+ margin-top: 4px;
+ }
+
+ .comment-replies dees-icon {
+ font-size: 12px;
+ }
+
+ /* Suggestion cards */
+ .suggestion-card {
+ padding: 10px;
+ background: ${cssManager.bdTheme('#f9fafb', '#111111')};
+ border-radius: 6px;
+ margin-bottom: 8px;
+ cursor: pointer;
+ transition: background 0.15s ease;
+ }
+
+ .suggestion-card:last-child {
+ margin-bottom: 0;
+ }
+
+ .suggestion-card:hover {
+ background: ${cssManager.bdTheme('#f3f4f6', '#18181b')};
+ }
+
+ .suggestion-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 8px;
+ }
+
+ .suggestion-user {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ }
+
+ .suggestion-status {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 2px 6px;
+ border-radius: 9999px;
+ font-size: 10px;
+ font-weight: 500;
+ }
+
+ .suggestion-status dees-icon {
+ font-size: 10px;
+ }
+
+ .suggestion-status.pending {
+ background: ${cssManager.bdTheme('#fef3c7', '#422006')};
+ color: ${cssManager.bdTheme('#92400e', '#fcd34d')};
+ }
+
+ .suggestion-status.accepted {
+ background: ${cssManager.bdTheme('#d1fae5', '#064e3b')};
+ color: ${cssManager.bdTheme('#065f46', '#6ee7b7')};
+ }
+
+ .suggestion-status.rejected {
+ background: ${cssManager.bdTheme('#fee2e2', '#450a0a')};
+ color: ${cssManager.bdTheme('#991b1b', '#fca5a5')};
+ }
+
+ .suggestion-diff {
+ font-family: 'Roboto Mono', monospace;
+ font-size: 11px;
+ line-height: 1.4;
+ }
+
+ .diff-removed {
+ background: ${cssManager.bdTheme('#fee2e2', '#450a0a')};
+ color: ${cssManager.bdTheme('#991b1b', '#fca5a5')};
+ text-decoration: line-through;
+ padding: 1px 3px;
+ border-radius: 2px;
+ }
+
+ .diff-added {
+ background: ${cssManager.bdTheme('#d1fae5', '#064e3b')};
+ color: ${cssManager.bdTheme('#065f46', '#6ee7b7')};
+ padding: 1px 3px;
+ border-radius: 2px;
+ }
+
+ /* Quick add comment */
+ .quick-add {
+ padding: 12px;
+ border-top: 1px solid ${cssManager.bdTheme('#e5e5e5', '#27272a')};
+ background: ${cssManager.bdTheme('#f9fafb', '#111111')};
+ }
+
+ .quick-add-input {
+ width: 100%;
+ padding: 10px 12px;
+ font-size: 13px;
+ color: ${cssManager.bdTheme('#111111', '#fafafa')};
+ background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
+ border: 1px solid ${cssManager.bdTheme('#d1d5db', '#3f3f46')};
+ border-radius: 6px;
+ outline: none;
+ resize: none;
+ min-height: 60px;
+ font-family: inherit;
+ box-sizing: border-box;
+ }
+
+ .quick-add-input:focus {
+ border-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
+ box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(96, 165, 250, 0.1)')};
+ }
+
+ .quick-add-input::placeholder {
+ color: ${cssManager.bdTheme('#9ca3af', '#6b7280')};
+ }
+
+ .quick-add-actions {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 8px;
+ }
+
+ .btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 12px;
+ font-size: 12px;
+ font-weight: 500;
+ border-radius: 6px;
+ border: none;
+ cursor: pointer;
+ transition: all 0.15s ease;
+ }
+
+ .btn-primary {
+ background: ${cssManager.bdTheme('#111111', '#fafafa')};
+ color: ${cssManager.bdTheme('#ffffff', '#09090b')};
+ }
+
+ .btn-primary:hover {
+ background: ${cssManager.bdTheme('#333333', '#e5e5e5')};
+ }
+
+ .btn-primary:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+
+ /* Empty state */
+ .empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 24px 16px;
+ text-align: center;
+ color: ${cssManager.bdTheme('#9ca3af', '#6b7280')};
+ }
+
+ .empty-state dees-icon {
+ font-size: 32px;
+ margin-bottom: 8px;
+ opacity: 0.5;
+ }
+
+ .empty-state p {
+ margin: 0;
+ font-size: 12px;
+ }
+ `,
+ ];
+
+ // ============================================================================
+ // PROPERTIES
+ // ============================================================================
+
+ @property({ type: Object })
+ public accessor contract: plugins.sdInterfaces.IPortableContract | null = null;
+
+ @property({ type: Boolean })
+ public accessor readonly: boolean = false;
+
+ // ============================================================================
+ // STATE
+ // ============================================================================
+
+ @state()
+ private accessor commentsExpanded: boolean = true;
+
+ @state()
+ private accessor suggestionsExpanded: boolean = true;
+
+ @state()
+ private accessor newCommentText: string = '';
+
+ // Demo presence data
+ @state()
+ private accessor presenceList: IPresence[] = [
+ { userId: '1', userName: 'Alice Smith', userColor: '#3b82f6', currentSection: 'content', lastActive: Date.now() },
+ { userId: '2', userName: 'Bob Johnson', userColor: '#10b981', currentSection: 'parties', lastActive: Date.now() - 60000 },
+ { userId: '3', userName: 'Carol Davis', userColor: '#f59e0b', currentSection: 'terms', lastActive: Date.now() - 300000 },
+ ];
+
+ // Demo comments data
+ @state()
+ private accessor comments: IComment[] = [
+ {
+ id: '1',
+ userId: '1',
+ userName: 'Alice Smith',
+ userColor: '#3b82f6',
+ content: 'Can we clarify the payment terms in paragraph 3? The current wording seems ambiguous.',
+ createdAt: Date.now() - 3600000,
+ anchorPath: 'paragraphs.2',
+ anchorText: 'Compensation',
+ resolved: false,
+ replies: [
+ {
+ id: '1-1',
+ userId: '2',
+ userName: 'Bob Johnson',
+ userColor: '#10b981',
+ content: 'Good point. I\'ll update the wording to be more specific.',
+ createdAt: Date.now() - 1800000,
+ resolved: false,
+ replies: [],
+ },
+ ],
+ },
+ {
+ id: '2',
+ userId: '3',
+ userName: 'Carol Davis',
+ userColor: '#f59e0b',
+ content: 'The termination clause needs to comply with the latest regulations.',
+ createdAt: Date.now() - 86400000,
+ resolved: true,
+ replies: [],
+ },
+ {
+ id: '3',
+ userId: '2',
+ userName: 'Bob Johnson',
+ userColor: '#10b981',
+ content: 'Should we add an automatic renewal clause?',
+ createdAt: Date.now() - 7200000,
+ resolved: false,
+ replies: [],
+ },
+ ];
+
+ // Demo suggestions data
+ @state()
+ private accessor suggestions: ISuggestion[] = [
+ {
+ id: '1',
+ userId: '1',
+ userName: 'Alice Smith',
+ userColor: '#3b82f6',
+ originalText: 'monthly salary',
+ suggestedText: 'monthly gross salary',
+ path: 'paragraphs.2.content',
+ status: 'pending',
+ createdAt: Date.now() - 7200000,
+ },
+ {
+ id: '2',
+ userId: '3',
+ userName: 'Carol Davis',
+ userColor: '#f59e0b',
+ originalText: '30 days',
+ suggestedText: '60 days',
+ path: 'paragraphs.5.content',
+ status: 'pending',
+ createdAt: Date.now() - 3600000,
+ },
+ ];
+
+ // ============================================================================
+ // EVENT HANDLERS
+ // ============================================================================
+
+ private handleCommentClick(comment: IComment) {
+ this.dispatchEvent(
+ new CustomEvent('comment-click', {
+ detail: { comment },
+ bubbles: true,
+ composed: true,
+ })
+ );
+ }
+
+ private handleSuggestionClick(suggestion: ISuggestion) {
+ this.dispatchEvent(
+ new CustomEvent('suggestion-click', {
+ detail: { suggestion },
+ bubbles: true,
+ composed: true,
+ })
+ );
+ }
+
+ private handleAddComment() {
+ if (!this.newCommentText.trim()) return;
+
+ const newComment: IComment = {
+ id: `comment-${Date.now()}`,
+ userId: 'current-user',
+ userName: 'You',
+ userColor: '#6366f1',
+ content: this.newCommentText,
+ createdAt: Date.now(),
+ resolved: false,
+ replies: [],
+ };
+
+ this.comments = [newComment, ...this.comments];
+ this.newCommentText = '';
+
+ this.dispatchEvent(
+ new CustomEvent('add-comment', {
+ detail: { comment: newComment },
+ bubbles: true,
+ composed: true,
+ })
+ );
+ }
+
+ // ============================================================================
+ // HELPERS
+ // ============================================================================
+
+ private formatTimeAgo(timestamp: number): string {
+ const seconds = Math.floor((Date.now() - timestamp) / 1000);
+ if (seconds < 60) return 'now';
+ const minutes = Math.floor(seconds / 60);
+ if (minutes < 60) return `${minutes}m`;
+ const hours = Math.floor(minutes / 60);
+ if (hours < 24) return `${hours}h`;
+ const days = Math.floor(hours / 24);
+ return `${days}d`;
+ }
+
+ private getActivePresence(): IPresence[] {
+ const fiveMinutesAgo = Date.now() - 300000;
+ return this.presenceList.filter((p) => p.lastActive > fiveMinutesAgo);
+ }
+
+ private getOpenComments(): IComment[] {
+ return this.comments.filter((c) => !c.resolved);
+ }
+
+ private getPendingSuggestions(): ISuggestion[] {
+ return this.suggestions.filter((s) => s.status === 'pending');
+ }
+
+ // ============================================================================
+ // RENDER
+ // ============================================================================
+
+ public render(): TemplateResult {
+ const activePresence = this.getActivePresence();
+ const openComments = this.getOpenComments();
+ const pendingSuggestions = this.getPendingSuggestions();
+
+ return html`
+
+ `;
+ }
+
+ private renderCommentCard(comment: IComment): TemplateResult {
+ return html`
+
+ `;
+ }
+
+ private renderSuggestionCard(suggestion: ISuggestion): TemplateResult {
+ return html`
+ this.handleSuggestionClick(suggestion)}>
+
+
+ ${suggestion.originalText}
+ →
+ ${suggestion.suggestedText}
+
+
+ `;
+ }
+}
diff --git a/ts_web/elements/sdig-contract-attachments/sdig-contract-attachments.ts b/ts_web/elements/sdig-contract-attachments/sdig-contract-attachments.ts
index 5f902ed..976d60c 100644
--- a/ts_web/elements/sdig-contract-attachments/sdig-contract-attachments.ts
+++ b/ts_web/elements/sdig-contract-attachments/sdig-contract-attachments.ts
@@ -37,11 +37,11 @@ interface IAttachment {
// File type configuration
const FILE_TYPES = {
- document: { icon: 'lucide:file-text', color: '#3b82f6', label: 'Document' },
- image: { icon: 'lucide:image', color: '#10b981', label: 'Image' },
- spreadsheet: { icon: 'lucide:sheet', color: '#22c55e', label: 'Spreadsheet' },
- pdf: { icon: 'lucide:file-type', color: '#ef4444', label: 'PDF' },
- other: { icon: 'lucide:file', color: '#6b7280', label: 'File' },
+ document: { icon: 'lucide:FileText', color: '#3b82f6', label: 'Document' },
+ image: { icon: 'lucide:Image', color: '#10b981', label: 'Image' },
+ spreadsheet: { icon: 'lucide:Sheet', color: '#22c55e', label: 'Spreadsheet' },
+ pdf: { icon: 'lucide:FileType', color: '#ef4444', label: 'PDF' },
+ other: { icon: 'lucide:File', color: '#6b7280', label: 'File' },
};
@customElement('sdig-contract-attachments')
@@ -631,7 +631,7 @@ export class SdigContractAttachments extends DeesElement {
No comments yet
+