feat(01-01): implement palette.ts as single source of truth for category colors
- Export CategoryType union type with 7 category strings - Export CategoryShades interface with light/medium/base fields - Export palette Record with oklch values matching --chart-* CSS tokens - Export headerGradient() returning CSSProperties with linear-gradient - Export overviewHeaderGradient() with multi-stop sky/lavender/green gradient - Export amountColorClass() for text-success/text-warning/text-destructive - All 20 unit tests pass
This commit is contained in:
102
frontend/src/lib/palette.ts
Normal file
102
frontend/src/lib/palette.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import type React from 'react'
|
||||
|
||||
export type CategoryType =
|
||||
| 'income'
|
||||
| 'bill'
|
||||
| 'variable_expense'
|
||||
| 'debt'
|
||||
| 'saving'
|
||||
| 'investment'
|
||||
| 'carryover'
|
||||
|
||||
export interface CategoryShades {
|
||||
light: string
|
||||
medium: string
|
||||
base: string
|
||||
}
|
||||
|
||||
export const palette: Record<CategoryType, CategoryShades> = {
|
||||
income: {
|
||||
light: 'oklch(0.96 0.04 145)',
|
||||
medium: 'oklch(0.88 0.08 145)',
|
||||
base: 'oklch(0.76 0.14 145)',
|
||||
},
|
||||
bill: {
|
||||
light: 'oklch(0.96 0.03 250)',
|
||||
medium: 'oklch(0.88 0.07 250)',
|
||||
base: 'oklch(0.76 0.12 250)',
|
||||
},
|
||||
variable_expense: {
|
||||
light: 'oklch(0.97 0.04 85)',
|
||||
medium: 'oklch(0.90 0.08 85)',
|
||||
base: 'oklch(0.80 0.14 85)',
|
||||
},
|
||||
debt: {
|
||||
light: 'oklch(0.96 0.04 15)',
|
||||
medium: 'oklch(0.88 0.08 15)',
|
||||
base: 'oklch(0.76 0.13 15)',
|
||||
},
|
||||
saving: {
|
||||
light: 'oklch(0.95 0.04 280)',
|
||||
medium: 'oklch(0.87 0.08 280)',
|
||||
base: 'oklch(0.75 0.13 280)',
|
||||
},
|
||||
investment: {
|
||||
light: 'oklch(0.96 0.04 320)',
|
||||
medium: 'oklch(0.88 0.07 320)',
|
||||
base: 'oklch(0.76 0.12 320)',
|
||||
},
|
||||
carryover: {
|
||||
light: 'oklch(0.96 0.03 210)',
|
||||
medium: 'oklch(0.88 0.06 210)',
|
||||
base: 'oklch(0.76 0.11 210)',
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CSSProperties object with a linear-gradient background
|
||||
* using the light and medium shades of the given category type.
|
||||
*/
|
||||
export function headerGradient(type: CategoryType): React.CSSProperties {
|
||||
const shades = palette[type]
|
||||
return {
|
||||
background: `linear-gradient(to right, ${shades.light}, ${shades.medium})`,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CSSProperties object with a multi-stop linear-gradient
|
||||
* for the FinancialOverview header (sky via lavender to green).
|
||||
*/
|
||||
export function overviewHeaderGradient(): React.CSSProperties {
|
||||
return {
|
||||
background: `linear-gradient(to right, ${palette.carryover.light}, ${palette.saving.light}, ${palette.income.light})`,
|
||||
}
|
||||
}
|
||||
|
||||
export interface AmountColorClassOpts {
|
||||
type: CategoryType
|
||||
actual: number
|
||||
budgeted: number
|
||||
isIncome?: boolean
|
||||
isAvailable?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Tailwind utility class for coloring a monetary amount:
|
||||
* - isIncome or isAvailable: positive → 'text-success', negative → 'text-destructive', zero → ''
|
||||
* - Expense: actual > budgeted → 'text-warning', else → ''
|
||||
*/
|
||||
export function amountColorClass(opts: AmountColorClassOpts): string {
|
||||
const { actual, budgeted, isIncome, isAvailable } = opts
|
||||
|
||||
if (isIncome || isAvailable) {
|
||||
if (actual > 0) return 'text-success'
|
||||
if (actual < 0) return 'text-destructive'
|
||||
return ''
|
||||
}
|
||||
|
||||
// Expense path
|
||||
if (actual > budgeted) return 'text-warning'
|
||||
return ''
|
||||
}
|
||||
Reference in New Issue
Block a user