import { FolkShape } from "./folk-shape"; import { css, html } from "./tags"; const styles = css` :host { background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); min-width: 280px; min-height: 200px; } .header { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; background: #d97706; color: white; border-radius: 8px 8px 0 0; font-size: 12px; font-weight: 600; cursor: move; } .header-title { display: flex; align-items: center; gap: 6px; } .header-actions button { background: transparent; border: none; color: white; cursor: pointer; padding: 2px 6px; border-radius: 4px; font-size: 14px; } .header-actions button:hover { background: rgba(255, 255, 255, 0.2); } .budget-body { padding: 12px; } .budget-summary { margin-bottom: 12px; } .budget-row { display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 4px; } .budget-row .label { color: #64748b; } .budget-row .value { font-weight: 600; color: #1e293b; } .budget-row .remaining { color: #059669; } .budget-row .over { color: #dc2626; } .progress-bar { width: 100%; height: 8px; background: #e2e8f0; border-radius: 4px; overflow: hidden; margin: 8px 0 12px; } .progress-fill { height: 100%; border-radius: 4px; transition: width 0.3s ease; } .progress-fill.ok { background: #059669; } .progress-fill.warning { background: #d97706; } .progress-fill.danger { background: #dc2626; } .expense-list { max-height: 200px; overflow-y: auto; } .expense-item { display: flex; justify-content: space-between; align-items: center; padding: 6px 8px; font-size: 12px; border-radius: 4px; margin-bottom: 3px; background: #f8fafc; } .expense-item:hover { background: #f1f5f9; } .expense-desc { color: #1e293b; font-weight: 500; } .expense-cat { font-size: 9px; color: #64748b; } .expense-amount { font-weight: 600; color: #dc2626; } .add-form { display: flex; gap: 4px; padding: 8px 12px; border-top: 1px solid #e2e8f0; } .add-form input { flex: 1; padding: 6px 8px; border: 1px solid #e2e8f0; border-radius: 4px; font-size: 12px; outline: none; } .add-form input:focus { border-color: #d97706; } .add-form input.amount-input { width: 80px; flex: 0; } .empty-state { text-align: center; padding: 16px; color: #94a3b8; font-size: 12px; } `; export interface BudgetExpense { id: string; description: string; amount: number; category: string; date?: string; } declare global { interface HTMLElementTagNameMap { "folk-budget": FolkBudget; } } export class FolkBudget extends FolkShape { static override tagName = "folk-budget"; static { const sheet = new CSSStyleSheet(); const parentRules = Array.from(FolkShape.styles.cssRules) .map((r) => r.cssText) .join("\n"); const childRules = Array.from(styles.cssRules) .map((r) => r.cssText) .join("\n"); sheet.replaceSync(`${parentRules}\n${childRules}`); this.styles = sheet; } #budgetTotal = 0; #currency = "USD"; #expenses: BudgetExpense[] = []; #listEl: HTMLElement | null = null; #summaryEl: HTMLElement | null = null; #progressEl: HTMLElement | null = null; get budgetTotal() { return this.#budgetTotal; } set budgetTotal(v: number) { this.#budgetTotal = v; this.#render(); this.dispatchEvent(new CustomEvent("content-change")); } get currency() { return this.#currency; } set currency(v: string) { this.#currency = v; this.#render(); } get expenses() { return this.#expenses; } set expenses(v: BudgetExpense[]) { this.#expenses = v; this.#render(); this.dispatchEvent(new CustomEvent("content-change")); } get spent() { return this.#expenses.reduce((sum, e) => sum + e.amount, 0); } addExpense(expense: BudgetExpense) { this.#expenses.push(expense); this.#render(); this.dispatchEvent(new CustomEvent("content-change")); } override createRenderRoot() { const root = super.createRenderRoot(); const wrapper = document.createElement("div"); wrapper.innerHTML = html`