/** * ds-token-list.js * List view of all design tokens in the project * UX Team Tool #2 */ import { createListView, setupListHandlers } from '../../utils/tool-templates.js'; import { ComponentHelpers } from '../../utils/component-helpers.js'; import contextStore from '../../stores/context-store.js'; import toolBridge from '../../services/tool-bridge.js'; class DSTokenList extends HTMLElement { constructor() { super(); this.tokens = []; this.filteredTokens = []; this.isLoading = false; } async connectedCallback() { this.render(); await this.loadTokens(); } async loadTokens() { this.isLoading = true; const container = this.querySelector('#token-list-container'); if (container) { container.innerHTML = ComponentHelpers.renderLoading('Loading design tokens...'); } try { const context = contextStore.getMCPContext(); if (!context.project_id) { throw new Error('No project selected'); } // Try to get resolved context which includes all tokens const result = await toolBridge.executeTool('dss_get_resolved_context', { manifest_path: `/projects/${context.project_id}/ds.config.json` }); // Extract tokens from result this.tokens = this.extractTokensFromContext(result); this.filteredTokens = [...this.tokens]; this.renderTokenList(); } catch (error) { console.error('[DSTokenList] Failed to load tokens:', error); if (container) { container.innerHTML = ComponentHelpers.renderError('Failed to load tokens', error); } } finally { this.isLoading = false; } } extractTokensFromContext(context) { const tokens = []; // Extract from colors, typography, spacing, etc. const categories = ['colors', 'typography', 'spacing', 'shadows', 'borders', 'radii']; for (const category of categories) { if (context[category]) { for (const [key, value] of Object.entries(context[category])) { tokens.push({ category, name: key, value: typeof value === 'object' ? JSON.stringify(value) : String(value), type: this.inferTokenType(category, key, value) }); } } } return tokens; } inferTokenType(category, key, value) { if (category === 'colors') return 'color'; if (category === 'typography') return 'font'; if (category === 'spacing') return 'size'; if (category === 'shadows') return 'shadow'; if (category === 'borders') return 'border'; if (category === 'radii') return 'radius'; return 'other'; } renderTokenList() { const container = this.querySelector('#token-list-container'); if (!container) return; const config = { title: 'Design Tokens', items: this.filteredTokens, columns: [ { key: 'name', label: 'Token Name', render: (token) => `${ComponentHelpers.escapeHtml(token.name)}` }, { key: 'category', label: 'Category', render: (token) => ComponentHelpers.createBadge(token.category, 'info') }, { key: 'value', label: 'Value', render: (token) => { if (token.type === 'color') { return `
${ComponentHelpers.escapeHtml(token.value)}
`; } return `${ComponentHelpers.escapeHtml(token.value)}`; } }, { key: 'type', label: 'Type', render: (token) => `${ComponentHelpers.escapeHtml(token.type)}` } ], actions: [ { label: 'Export All', icon: '📥', onClick: () => this.exportTokens() }, { label: 'Refresh', icon: '🔄', onClick: () => this.loadTokens() } ], onSearch: (query) => this.handleSearch(query), onFilter: (filterValue) => this.handleFilter(filterValue) }; container.innerHTML = createListView(config); setupListHandlers(container, config); // Update filter dropdown with categories const filterSelect = container.querySelector('#filter-select'); if (filterSelect) { const categories = [...new Set(this.tokens.map(t => t.category))]; filterSelect.innerHTML = ` ${categories.map(cat => ``).join('')} `; } } handleSearch(query) { const lowerQuery = query.toLowerCase(); this.filteredTokens = this.tokens.filter(token => token.name.toLowerCase().includes(lowerQuery) || token.value.toLowerCase().includes(lowerQuery) || token.category.toLowerCase().includes(lowerQuery) ); this.renderTokenList(); } handleFilter(filterValue) { if (!filterValue) { this.filteredTokens = [...this.tokens]; } else { this.filteredTokens = this.tokens.filter(token => token.category === filterValue); } this.renderTokenList(); } exportTokens() { const data = JSON.stringify(this.tokens, null, 2); const blob = new Blob([data], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'design-tokens.json'; a.click(); URL.revokeObjectURL(url); ComponentHelpers.showToast?.('Tokens exported', 'success'); } render() { this.innerHTML = `
${ComponentHelpers.renderLoading('Loading tokens...')}
`; } } customElements.define('ds-token-list', DSTokenList); export default DSTokenList;