Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
190
admin-ui/js/components/listings/ds-component-list.js
Normal file
190
admin-ui/js/components/listings/ds-component-list.js
Normal file
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* ds-component-list.js
|
||||
* Component listing and management interface
|
||||
* Shows all components with links to Storybook and adoption stats
|
||||
*/
|
||||
|
||||
import URLBuilder from '../../utils/url-builder.js';
|
||||
|
||||
export default class ComponentList extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.components = [
|
||||
{ id: 'button', name: 'Button', category: 'Inputs', adoption: 95, variants: 12 },
|
||||
{ id: 'input', name: 'Input Field', category: 'Inputs', adoption: 88, variants: 8 },
|
||||
{ id: 'card', name: 'Card', category: 'Containers', adoption: 92, variants: 5 },
|
||||
{ id: 'modal', name: 'Modal', category: 'Containers', adoption: 78, variants: 3 },
|
||||
{ id: 'badge', name: 'Badge', category: 'Status', adoption: 85, variants: 6 },
|
||||
{ id: 'tooltip', name: 'Tooltip', category: 'Helpers', adoption: 72, variants: 4 },
|
||||
{ id: 'dropdown', name: 'Dropdown', category: 'Inputs', adoption: 81, variants: 4 },
|
||||
{ id: 'pagination', name: 'Pagination', category: 'Navigation', adoption: 65, variants: 2 },
|
||||
];
|
||||
this.selectedCategory = 'All';
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.innerHTML = `
|
||||
<div style="padding: 24px; height: 100%; overflow-y: auto;">
|
||||
<div style="margin-bottom: 24px;">
|
||||
<h1 style="margin: 0 0 8px 0; font-size: 24px;">Design System Components</h1>
|
||||
<p style="margin: 0; color: var(--vscode-text-dim);">
|
||||
Browse, preview, and track component adoption
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Filter Bar -->
|
||||
<div style="margin-bottom: 24px; display: flex; gap: 8px; flex-wrap: wrap;">
|
||||
<button class="filter-btn" data-category="All" style="
|
||||
padding: 6px 12px;
|
||||
background: var(--vscode-selection);
|
||||
color: var(--vscode-foreground);
|
||||
border: 1px solid var(--vscode-focusBorder);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
">All</button>
|
||||
${['Inputs', 'Containers', 'Status', 'Helpers', 'Navigation'].map(cat => `
|
||||
<button class="filter-btn" data-category="${cat}" style="
|
||||
padding: 6px 12px;
|
||||
background: var(--vscode-sidebar);
|
||||
color: var(--vscode-foreground);
|
||||
border: 1px solid var(--vscode-border);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
">${cat}</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
||||
<!-- Components Grid -->
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px;">
|
||||
${this.renderComponents()}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderComponents() {
|
||||
const filtered = this.selectedCategory === 'All'
|
||||
? this.components
|
||||
: this.components.filter(c => c.category === this.selectedCategory);
|
||||
|
||||
return filtered.map(component => `
|
||||
<div style="
|
||||
background: var(--vscode-sidebar);
|
||||
border: 1px solid var(--vscode-border);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
">
|
||||
<!-- Header -->
|
||||
<div style="
|
||||
background: var(--vscode-bg);
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--vscode-border);
|
||||
">
|
||||
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 8px;">
|
||||
<div>
|
||||
<div style="font-weight: 600; font-size: 14px; margin-bottom: 2px;">
|
||||
${component.name}
|
||||
</div>
|
||||
<div style="font-size: 11px; color: var(--vscode-text-dim);">
|
||||
${component.category}
|
||||
</div>
|
||||
</div>
|
||||
<span style="
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
">${component.adoption}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div style="padding: 12px;">
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 12px; font-size: 12px;">
|
||||
<span>Variants: <strong>${component.variants}</strong></span>
|
||||
<span>Adoption: <strong>${component.adoption}%</strong></span>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div style="width: 100%; height: 4px; background: var(--vscode-bg); border-radius: 2px; overflow: hidden; margin-bottom: 12px;">
|
||||
<div style="width: ${component.adoption}%; height: 100%; background: #4caf50;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button class="storybook-btn" data-component-id="${component.id}" style="
|
||||
flex: 1;
|
||||
padding: 6px;
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
">📖 Storybook</button>
|
||||
<button class="edit-btn" data-component-id="${component.id}" style="
|
||||
flex: 1;
|
||||
padding: 6px;
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
">✏️ Edit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Filter buttons
|
||||
this.querySelectorAll('.filter-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
this.selectedCategory = btn.dataset.category;
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
});
|
||||
});
|
||||
|
||||
// Storybook buttons
|
||||
this.querySelectorAll('.storybook-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const componentId = btn.dataset.componentId;
|
||||
const component = this.components.find(c => c.id === componentId);
|
||||
if (component) {
|
||||
const url = URLBuilder.getComponentUrl(component);
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Edit buttons
|
||||
this.querySelectorAll('.edit-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const componentId = btn.dataset.componentId;
|
||||
this.dispatchEvent(new CustomEvent('edit-component', {
|
||||
detail: { componentId },
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ds-component-list', ComponentList);
|
||||
249
admin-ui/js/components/listings/ds-icon-list.js
Normal file
249
admin-ui/js/components/listings/ds-icon-list.js
Normal file
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* ds-icon-list.js
|
||||
* Icon gallery and management
|
||||
* Browse and export icons from the design system
|
||||
*/
|
||||
|
||||
export default class IconList extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.icons = [
|
||||
{ id: 'check', name: 'Check', category: 'Status', svg: '✓', tags: ['status', 'success', 'validation'] },
|
||||
{ id: 'x', name: 'Close', category: 'Status', svg: '✕', tags: ['status', 'error', 'dismiss'] },
|
||||
{ id: 'info', name: 'Info', category: 'Status', svg: 'ⓘ', tags: ['status', 'information', 'help'] },
|
||||
{ id: 'warning', name: 'Warning', category: 'Status', svg: '⚠', tags: ['status', 'warning', 'alert'] },
|
||||
{ id: 'arrow-right', name: 'Arrow Right', category: 'Navigation', svg: '→', tags: ['navigation', 'direction', 'next'] },
|
||||
{ id: 'arrow-left', name: 'Arrow Left', category: 'Navigation', svg: '←', tags: ['navigation', 'direction', 'back'] },
|
||||
{ id: 'arrow-up', name: 'Arrow Up', category: 'Navigation', svg: '↑', tags: ['navigation', 'direction', 'up'] },
|
||||
{ id: 'arrow-down', name: 'Arrow Down', category: 'Navigation', svg: '↓', tags: ['navigation', 'direction', 'down'] },
|
||||
{ id: 'search', name: 'Search', category: 'Actions', svg: '🔍', tags: ['action', 'search', 'find'] },
|
||||
{ id: 'settings', name: 'Settings', category: 'Actions', svg: '⚙', tags: ['action', 'settings', 'config'] },
|
||||
{ id: 'download', name: 'Download', category: 'Actions', svg: '⬇', tags: ['action', 'download', 'save'] },
|
||||
{ id: 'upload', name: 'Upload', category: 'Actions', svg: '⬆', tags: ['action', 'upload', 'import'] },
|
||||
];
|
||||
this.selectedCategory = 'All';
|
||||
this.searchTerm = '';
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.innerHTML = `
|
||||
<div style="padding: 24px; height: 100%; overflow-y: auto;">
|
||||
<div style="margin-bottom: 24px;">
|
||||
<h1 style="margin: 0 0 8px 0; font-size: 24px;">Icon Library</h1>
|
||||
<p style="margin: 0; color: var(--vscode-text-dim);">
|
||||
Browse and manage icon assets
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filter -->
|
||||
<div style="margin-bottom: 24px; display: flex; gap: 12px;">
|
||||
<input
|
||||
id="icon-search"
|
||||
type="text"
|
||||
placeholder="Search icons..."
|
||||
style="
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
background: var(--vscode-input-background);
|
||||
color: var(--vscode-foreground);
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
"
|
||||
/>
|
||||
<select id="icon-filter" style="
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
background: var(--vscode-input-background);
|
||||
color: var(--vscode-foreground);
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
">
|
||||
<option value="All">All Categories</option>
|
||||
<option value="Status">Status</option>
|
||||
<option value="Navigation">Navigation</option>
|
||||
<option value="Actions">Actions</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Icon Grid -->
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 12px; margin-bottom: 24px;">
|
||||
${this.renderIconCards()}
|
||||
</div>
|
||||
|
||||
<!-- Export Section -->
|
||||
<div style="background: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px; padding: 16px;">
|
||||
<h3 style="margin: 0 0 12px 0; font-size: 14px;">Export Options</h3>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button id="export-svg-btn" style="
|
||||
padding: 8px 16px;
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
">📦 Export as SVG</button>
|
||||
<button id="export-font-btn" style="
|
||||
padding: 8px 16px;
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
">🔤 Export as Font</button>
|
||||
<button id="export-json-btn" style="
|
||||
padding: 8px 16px;
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
">📄 Export as JSON</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderIconCards() {
|
||||
let filtered = this.icons;
|
||||
|
||||
if (this.selectedCategory !== 'All') {
|
||||
filtered = filtered.filter(i => i.category === this.selectedCategory);
|
||||
}
|
||||
|
||||
if (this.searchTerm) {
|
||||
const term = this.searchTerm.toLowerCase();
|
||||
filtered = filtered.filter(i =>
|
||||
i.name.toLowerCase().includes(term) ||
|
||||
i.id.toLowerCase().includes(term) ||
|
||||
i.tags.some(t => t.includes(term))
|
||||
);
|
||||
}
|
||||
|
||||
return filtered.map(icon => `
|
||||
<div class="icon-card" data-icon-id="${icon.id}" style="
|
||||
background: var(--vscode-sidebar);
|
||||
border: 1px solid var(--vscode-border);
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
">
|
||||
<div style="
|
||||
font-size: 32px;
|
||||
margin-bottom: 8px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--vscode-bg);
|
||||
border-radius: 3px;
|
||||
">${icon.svg}</div>
|
||||
<div style="text-align: center; width: 100%;">
|
||||
<div style="font-size: 11px; font-weight: 500; margin-bottom: 2px;">
|
||||
${icon.name}
|
||||
</div>
|
||||
<div style="font-size: 10px; color: var(--vscode-text-dim); font-family: monospace;">
|
||||
${icon.id}
|
||||
</div>
|
||||
<div style="font-size: 9px; color: var(--vscode-text-dim); margin-top: 4px;">
|
||||
${icon.category}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Search input
|
||||
const searchInput = this.querySelector('#icon-search');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
this.searchTerm = e.target.value;
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
});
|
||||
}
|
||||
|
||||
// Category filter
|
||||
const filterSelect = this.querySelector('#icon-filter');
|
||||
if (filterSelect) {
|
||||
filterSelect.addEventListener('change', (e) => {
|
||||
this.selectedCategory = e.target.value;
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
});
|
||||
}
|
||||
|
||||
// Icon cards (copy on click)
|
||||
this.querySelectorAll('.icon-card').forEach(card => {
|
||||
card.addEventListener('click', () => {
|
||||
const iconId = card.dataset.iconId;
|
||||
navigator.clipboard.writeText(iconId).then(() => {
|
||||
const originalBg = card.style.background;
|
||||
card.style.background = 'var(--vscode-selection)';
|
||||
setTimeout(() => {
|
||||
card.style.background = originalBg;
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Export buttons
|
||||
const exportSvgBtn = this.querySelector('#export-svg-btn');
|
||||
if (exportSvgBtn) {
|
||||
exportSvgBtn.addEventListener('click', () => {
|
||||
this.downloadIcons('svg');
|
||||
});
|
||||
}
|
||||
|
||||
const exportJsonBtn = this.querySelector('#export-json-btn');
|
||||
if (exportJsonBtn) {
|
||||
exportJsonBtn.addEventListener('click', () => {
|
||||
this.downloadIcons('json');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
downloadIcons(format) {
|
||||
const data = format === 'json'
|
||||
? JSON.stringify(this.icons, null, 2)
|
||||
: this.generateSVGSheet();
|
||||
|
||||
const blob = new Blob([data], { type: format === 'json' ? 'application/json' : 'image/svg+xml' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `icons.${format === 'json' ? 'json' : 'svg'}`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
generateSVGSheet() {
|
||||
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 800">
|
||||
${this.icons.map((icon, i) => `
|
||||
<text x="${(i % 12) * 100 + 50}" y="${Math.floor(i / 12) * 100 + 50}" font-size="40" text-anchor="middle">
|
||||
${icon.svg}
|
||||
</text>
|
||||
`).join('')}
|
||||
</svg>`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ds-icon-list', IconList);
|
||||
293
admin-ui/js/components/listings/ds-jira-issues.js
Normal file
293
admin-ui/js/components/listings/ds-jira-issues.js
Normal file
@@ -0,0 +1,293 @@
|
||||
/**
|
||||
* ds-jira-issues.js
|
||||
* Jira issue tracker integration
|
||||
* View project-specific Jira issues for design system work
|
||||
*/
|
||||
|
||||
import contextStore from '../../stores/context-store.js';
|
||||
|
||||
export default class JiraIssues extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
projectId: null,
|
||||
issues: [],
|
||||
filterStatus: 'All',
|
||||
isLoading: false
|
||||
};
|
||||
|
||||
// Mock data for demo
|
||||
this.mockIssues = [
|
||||
{ key: 'DSS-234', summary: 'Add Button component variants', status: 'In Progress', type: 'Task', priority: 'High', assignee: 'John Doe' },
|
||||
{ key: 'DSS-235', summary: 'Update color token naming convention', status: 'To Do', type: 'Story', priority: 'Medium', assignee: 'Unassigned' },
|
||||
{ key: 'DSS-236', summary: 'Fix Card component accessibility', status: 'In Review', type: 'Bug', priority: 'High', assignee: 'Jane Smith' },
|
||||
{ key: 'DSS-237', summary: 'Document Typography system', status: 'Done', type: 'Task', priority: 'Low', assignee: 'Mike Johnson' },
|
||||
{ key: 'DSS-238', summary: 'Create Icon font export', status: 'To Do', type: 'Task', priority: 'Medium', assignee: 'Sarah Wilson' },
|
||||
{ key: 'DSS-239', summary: 'Implement Figma sync automation', status: 'In Progress', type: 'Epic', priority: 'High', assignee: 'John Doe' },
|
||||
];
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
|
||||
// Subscribe to project context changes
|
||||
this.unsubscribe = contextStore.subscribe(({ state }) => {
|
||||
this.state.projectId = state.projectId;
|
||||
this.loadIssues();
|
||||
});
|
||||
|
||||
// Load initial issues
|
||||
this.state.projectId = contextStore.get('projectId');
|
||||
this.loadIssues();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this.unsubscribe) this.unsubscribe();
|
||||
}
|
||||
|
||||
async loadIssues() {
|
||||
this.state.isLoading = true;
|
||||
this.renderLoading();
|
||||
|
||||
// Simulate API call delay
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
|
||||
// In real implementation, fetch from Jira API via backend
|
||||
this.state.issues = this.mockIssues;
|
||||
this.state.isLoading = false;
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
renderLoading() {
|
||||
this.innerHTML = `
|
||||
<div style="padding: 24px; display: flex; align-items: center; justify-content: center; height: 100%;">
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 24px; margin-bottom: 12px;">⏳</div>
|
||||
<div style="font-size: 12px; color: var(--vscode-text-dim);">Loading Jira issues...</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
this.innerHTML = `
|
||||
<div style="padding: 24px; height: 100%; overflow-y: auto;">
|
||||
<div style="margin-bottom: 24px; display: flex; justify-content: space-between; align-items: start;">
|
||||
<div>
|
||||
<h1 style="margin: 0 0 8px 0; font-size: 24px;">Jira Issues</h1>
|
||||
<p style="margin: 0; color: var(--vscode-text-dim);">
|
||||
${this.state.projectId ? `Project: ${this.state.projectId}` : 'Select a project to view issues'}
|
||||
</p>
|
||||
</div>
|
||||
<button id="create-issue-btn" style="
|
||||
padding: 8px 16px;
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
">+ New Issue</button>
|
||||
</div>
|
||||
|
||||
<!-- Status Filter -->
|
||||
<div style="margin-bottom: 24px; display: flex; gap: 8px;">
|
||||
${['All', 'To Do', 'In Progress', 'In Review', 'Done'].map(status => `
|
||||
<button class="status-filter" data-status="${status}" style="
|
||||
padding: 6px 12px;
|
||||
background: ${this.state.filterStatus === status ? 'var(--vscode-selection)' : 'var(--vscode-sidebar)'};
|
||||
color: var(--vscode-foreground);
|
||||
border: 1px solid ${this.state.filterStatus === status ? 'var(--vscode-focusBorder)' : 'var(--vscode-border)'};
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
">${status}</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
||||
<!-- Issues List -->
|
||||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||
${this.renderIssuesList()}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderIssuesList() {
|
||||
const filtered = this.state.filterStatus === 'All'
|
||||
? this.state.issues
|
||||
: this.state.issues.filter(i => i.status === this.state.filterStatus);
|
||||
|
||||
if (filtered.length === 0) {
|
||||
return `
|
||||
<div style="
|
||||
background: var(--vscode-sidebar);
|
||||
border: 1px solid var(--vscode-border);
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
color: var(--vscode-text-dim);
|
||||
">
|
||||
No issues found in this status
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return filtered.map(issue => `
|
||||
<div class="jira-issue" data-issue-key="${issue.key}" style="
|
||||
background: var(--vscode-sidebar);
|
||||
border: 1px solid var(--vscode-border);
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
">
|
||||
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 12px;">
|
||||
<div style="display: flex; gap: 12px; align-items: start; flex: 1;">
|
||||
<!-- Issue Type Badge -->
|
||||
<div style="
|
||||
padding: 4px 8px;
|
||||
background: ${this.getTypeColor(issue.type)};
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
">${issue.type}</div>
|
||||
|
||||
<!-- Issue Content -->
|
||||
<div style="flex: 1;">
|
||||
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 6px;">
|
||||
<span style="font-family: monospace; font-weight: 600; color: #0066CC;">
|
||||
${issue.key}
|
||||
</span>
|
||||
<span style="font-weight: 500; font-size: 13px;">
|
||||
${issue.summary}
|
||||
</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 12px; font-size: 11px; color: var(--vscode-text-dim);">
|
||||
<span>Assignee: ${issue.assignee}</span>
|
||||
<span>Priority: ${issue.priority}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Badge -->
|
||||
<div style="
|
||||
padding: 4px 12px;
|
||||
background: var(--vscode-bg);
|
||||
border: 1px solid var(--vscode-border);
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
">${issue.status}</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div style="display: flex; gap: 8px; padding-top: 12px; border-top: 1px solid var(--vscode-border);">
|
||||
<button class="open-issue-btn" style="
|
||||
padding: 4px 12px;
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
">Open in Jira</button>
|
||||
<button class="link-pr-btn" style="
|
||||
padding: 4px 12px;
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
">Link PR</button>
|
||||
<button class="assign-btn" style="
|
||||
padding: 4px 12px;
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
">Assign</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
getTypeColor(type) {
|
||||
const colors = {
|
||||
'Bug': '#f44336',
|
||||
'Task': '#2196f3',
|
||||
'Story': '#4caf50',
|
||||
'Epic': '#9c27b0',
|
||||
'Subtask': '#ff9800'
|
||||
};
|
||||
return colors[type] || '#999';
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Status filters
|
||||
this.querySelectorAll('.status-filter').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
this.state.filterStatus = btn.dataset.status;
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
});
|
||||
});
|
||||
|
||||
// Create issue button
|
||||
const createBtn = this.querySelector('#create-issue-btn');
|
||||
if (createBtn) {
|
||||
createBtn.addEventListener('click', () => {
|
||||
this.dispatchEvent(new CustomEvent('create-issue', {
|
||||
detail: { projectId: this.state.projectId },
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
// Issue actions
|
||||
this.querySelectorAll('.open-issue-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const issueKey = e.target.closest('.jira-issue').dataset.issueKey;
|
||||
window.open(`https://jira.atlassian.net/browse/${issueKey}`, '_blank');
|
||||
});
|
||||
});
|
||||
|
||||
this.querySelectorAll('.link-pr-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const issueKey = e.target.closest('.jira-issue').dataset.issueKey;
|
||||
this.dispatchEvent(new CustomEvent('link-pr', {
|
||||
detail: { issueKey },
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
this.querySelectorAll('.assign-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const issueKey = e.target.closest('.jira-issue').dataset.issueKey;
|
||||
this.dispatchEvent(new CustomEvent('assign-issue', {
|
||||
detail: { issueKey },
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ds-jira-issues', JiraIssues);
|
||||
197
admin-ui/js/components/listings/ds-token-list.js
Normal file
197
admin-ui/js/components/listings/ds-token-list.js
Normal file
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* ds-token-list.js
|
||||
* Design token listing and management
|
||||
* View, edit, and validate design tokens
|
||||
*/
|
||||
|
||||
import contextStore from '../../stores/context-store.js';
|
||||
|
||||
export default class TokenList extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.tokens = [
|
||||
{ id: 'color-primary', name: 'Primary Color', category: 'Colors', value: '#0066CC', usage: 156 },
|
||||
{ id: 'color-success', name: 'Success Color', category: 'Colors', value: '#4caf50', usage: 89 },
|
||||
{ id: 'color-error', name: 'Error Color', category: 'Colors', value: '#f44336', usage: 76 },
|
||||
{ id: 'color-warning', name: 'Warning Color', category: 'Colors', value: '#ff9800', usage: 54 },
|
||||
{ id: 'spacing-xs', name: 'Extra Small Spacing', category: 'Spacing', value: '4px', usage: 234 },
|
||||
{ id: 'spacing-sm', name: 'Small Spacing', category: 'Spacing', value: '8px', usage: 312 },
|
||||
{ id: 'spacing-md', name: 'Medium Spacing', category: 'Spacing', value: '16px', usage: 445 },
|
||||
{ id: 'spacing-lg', name: 'Large Spacing', category: 'Spacing', value: '24px', usage: 198 },
|
||||
{ id: 'font-body', name: 'Body Font', category: 'Typography', value: 'Inter, sans-serif', usage: 678 },
|
||||
{ id: 'font-heading', name: 'Heading Font', category: 'Typography', value: 'Poppins, sans-serif', usage: 234 },
|
||||
];
|
||||
this.selectedCategory = 'All';
|
||||
this.editingTokenId = null;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.innerHTML = `
|
||||
<div style="padding: 24px; height: 100%; overflow-y: auto;">
|
||||
<div style="margin-bottom: 24px; display: flex; justify-content: space-between; align-items: start;">
|
||||
<div>
|
||||
<h1 style="margin: 0 0 8px 0; font-size: 24px;">Design Tokens</h1>
|
||||
<p style="margin: 0; color: var(--vscode-text-dim);">
|
||||
Manage and track design token usage across the system
|
||||
</p>
|
||||
</div>
|
||||
<button id="export-btn" style="
|
||||
padding: 8px 16px;
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
">⬇️ Export Tokens</button>
|
||||
</div>
|
||||
|
||||
<!-- Category Filter -->
|
||||
<div style="margin-bottom: 24px; display: flex; gap: 8px; flex-wrap: wrap;">
|
||||
${['All', 'Colors', 'Spacing', 'Typography', 'Shadows', 'Borders'].map(cat => `
|
||||
<button class="filter-btn" data-category="${cat}" style="
|
||||
padding: 6px 12px;
|
||||
background: ${this.selectedCategory === cat ? 'var(--vscode-selection)' : 'var(--vscode-sidebar)'};
|
||||
color: var(--vscode-foreground);
|
||||
border: 1px solid ${this.selectedCategory === cat ? 'var(--vscode-focusBorder)' : 'var(--vscode-border)'};
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
">${cat}</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
||||
<!-- Token Table -->
|
||||
<div style="background: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px; overflow: hidden;">
|
||||
<div style="display: grid; grid-template-columns: 2fr 1fr 1fr 0.8fr 0.8fr; gap: 0; font-size: 11px; font-weight: 600; background: var(--vscode-bg); border-bottom: 1px solid var(--vscode-border); padding: 12px; color: var(--vscode-text-dim); text-transform: uppercase; letter-spacing: 0.5px;">
|
||||
<div>Token Name</div>
|
||||
<div>Category</div>
|
||||
<div>Value</div>
|
||||
<div>Usage</div>
|
||||
<div>Actions</div>
|
||||
</div>
|
||||
|
||||
<div id="tokens-container" style="max-height: 500px; overflow-y: auto;">
|
||||
${this.renderTokenRows()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderTokenRows() {
|
||||
const filtered = this.selectedCategory === 'All'
|
||||
? this.tokens
|
||||
: this.tokens.filter(t => t.category === this.selectedCategory);
|
||||
|
||||
return filtered.map(token => `
|
||||
<div style="
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1fr 0.8fr 0.8fr;
|
||||
gap: 0;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--vscode-border);
|
||||
font-size: 12px;
|
||||
">
|
||||
<div>
|
||||
<div style="font-weight: 500; margin-bottom: 2px;">${token.name}</div>
|
||||
<div style="font-size: 10px; color: var(--vscode-text-dim); font-family: monospace;">
|
||||
${token.id}
|
||||
</div>
|
||||
</div>
|
||||
<div style="color: var(--vscode-text-dim); font-size: 11px;">
|
||||
${token.category}
|
||||
</div>
|
||||
<div style="font-family: monospace; background: var(--vscode-bg); padding: 4px 6px; border-radius: 2px;">
|
||||
${token.value}
|
||||
</div>
|
||||
<div style="text-align: center; color: var(--vscode-text-dim);">
|
||||
${token.usage}
|
||||
</div>
|
||||
<div style="display: flex; gap: 4px;">
|
||||
<button class="edit-token-btn" data-token-id="${token.id}" style="
|
||||
padding: 3px 8px;
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
font-size: 10px;
|
||||
">Edit</button>
|
||||
<button class="copy-token-btn" data-token-value="${token.value}" style="
|
||||
padding: 3px 8px;
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
font-size: 10px;
|
||||
">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Filter buttons
|
||||
this.querySelectorAll('.filter-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
this.selectedCategory = btn.dataset.category;
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
});
|
||||
});
|
||||
|
||||
// Export button
|
||||
const exportBtn = this.querySelector('#export-btn');
|
||||
if (exportBtn) {
|
||||
exportBtn.addEventListener('click', () => {
|
||||
const tokenData = JSON.stringify(this.tokens, null, 2);
|
||||
const blob = new Blob([tokenData], { 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);
|
||||
});
|
||||
}
|
||||
|
||||
// Edit token buttons
|
||||
this.querySelectorAll('.edit-token-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const tokenId = btn.dataset.tokenId;
|
||||
const token = this.tokens.find(t => t.id === tokenId);
|
||||
this.dispatchEvent(new CustomEvent('edit-token', {
|
||||
detail: { token },
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
// Copy token buttons
|
||||
this.querySelectorAll('.copy-token-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const value = btn.dataset.tokenValue;
|
||||
navigator.clipboard.writeText(value).then(() => {
|
||||
const originalText = btn.textContent;
|
||||
btn.textContent = '✓ Copied';
|
||||
setTimeout(() => {
|
||||
btn.textContent = originalText;
|
||||
}, 1500);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ds-token-list', TokenList);
|
||||
Reference in New Issue
Block a user