feat(03-01): add CSS animation tokens, i18n keys, and carryover display
- Add collapsible-open/close keyframes and CSS animation tokens to index.css - Add dashboard.sections and dashboard.carryoverIncludes keys to en.json and de.json - Add optional subtitle/subtitleClassName props to StatCard - Extend SummaryStrip balance prop with carryoverSubtitle/carryoverIsNegative - Compute and pass carryover subtitle from DashboardContent to SummaryStrip
This commit is contained in:
@@ -7,6 +7,8 @@ interface StatCardProps {
|
||||
title: string
|
||||
value: string
|
||||
valueClassName?: string
|
||||
subtitle?: string
|
||||
subtitleClassName?: string
|
||||
variance?: {
|
||||
amount: string
|
||||
direction: "up" | "down" | "neutral"
|
||||
@@ -24,6 +26,8 @@ export function StatCard({
|
||||
title,
|
||||
value,
|
||||
valueClassName,
|
||||
subtitle,
|
||||
subtitleClassName,
|
||||
variance,
|
||||
}: StatCardProps) {
|
||||
return (
|
||||
@@ -42,6 +46,11 @@ export function StatCard({
|
||||
>
|
||||
{value}
|
||||
</p>
|
||||
{subtitle && (
|
||||
<p className={cn("mt-0.5 text-xs text-muted-foreground", subtitleClassName)}>
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
{variance && (
|
||||
<div className="mt-1 flex items-center gap-1 text-xs text-muted-foreground">
|
||||
{(() => {
|
||||
|
||||
@@ -3,7 +3,12 @@ import { StatCard } from "./StatCard"
|
||||
interface SummaryStripProps {
|
||||
income: { value: string; budgeted: string }
|
||||
expenses: { value: string; budgeted: string }
|
||||
balance: { value: string; isPositive: boolean }
|
||||
balance: {
|
||||
value: string
|
||||
isPositive: boolean
|
||||
carryoverSubtitle?: string
|
||||
carryoverIsNegative?: boolean
|
||||
}
|
||||
t: (key: string) => string
|
||||
}
|
||||
|
||||
@@ -39,6 +44,8 @@ export function SummaryStrip({
|
||||
title={t("dashboard.availableBalance")}
|
||||
value={balance.value}
|
||||
valueClassName={balance.isPositive ? "text-on-budget" : "text-over-budget"}
|
||||
subtitle={balance.carryoverSubtitle}
|
||||
subtitleClassName={balance.carryoverIsNegative ? "text-over-budget" : undefined}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -84,7 +84,12 @@
|
||||
"actual": "Tatsaechlich",
|
||||
"noBudgetForMonth": "Kein Budget fuer diesen Monat",
|
||||
"createBudget": "Budget erstellen",
|
||||
"generateFromTemplate": "Aus Vorlage generieren"
|
||||
"generateFromTemplate": "Aus Vorlage generieren",
|
||||
"sections": {
|
||||
"itemName": "Posten",
|
||||
"groupTotal": "{{label}} Gesamt"
|
||||
},
|
||||
"carryoverIncludes": "Inkl. {{amount}} Übertrag"
|
||||
},
|
||||
"quickAdd": {
|
||||
"title": "Schnelleingabe-Bibliothek",
|
||||
|
||||
@@ -84,7 +84,12 @@
|
||||
"actual": "Actual",
|
||||
"noBudgetForMonth": "No budget for this month",
|
||||
"createBudget": "Create Budget",
|
||||
"generateFromTemplate": "Generate from Template"
|
||||
"generateFromTemplate": "Generate from Template",
|
||||
"sections": {
|
||||
"itemName": "Item",
|
||||
"groupTotal": "{{label}} Total"
|
||||
},
|
||||
"carryoverIncludes": "Includes {{amount}} carryover"
|
||||
},
|
||||
"quickAdd": {
|
||||
"title": "Quick Add Library",
|
||||
|
||||
@@ -71,9 +71,23 @@
|
||||
|
||||
--radius: 0.625rem;
|
||||
|
||||
/* Collapsible animation */
|
||||
--animate-collapsible-open: collapsible-open 200ms ease-out;
|
||||
--animate-collapsible-close: collapsible-close 200ms ease-out;
|
||||
|
||||
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
|
||||
@keyframes collapsible-open {
|
||||
from { height: 0; overflow: hidden; }
|
||||
to { height: var(--radix-collapsible-content-height); overflow: hidden; }
|
||||
}
|
||||
|
||||
@keyframes collapsible-close {
|
||||
from { height: var(--radix-collapsible-content-height); overflow: hidden; }
|
||||
to { height: 0; overflow: hidden; }
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
|
||||
@@ -124,6 +124,12 @@ function DashboardContent({ budgetId }: { budgetId: string }) {
|
||||
const currency = budget.currency
|
||||
const availableBalance = totalIncome - totalExpenses + budget.carryover_amount
|
||||
|
||||
const carryover = budget.carryover_amount
|
||||
const carryoverSubtitle = carryover !== 0
|
||||
? t("dashboard.carryoverIncludes", { amount: formatCurrency(Math.abs(carryover), currency) })
|
||||
: undefined
|
||||
const carryoverIsNegative = carryover < 0
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Summary cards */}
|
||||
@@ -139,6 +145,8 @@ function DashboardContent({ budgetId }: { budgetId: string }) {
|
||||
balance={{
|
||||
value: formatCurrency(availableBalance, currency),
|
||||
isPositive: availableBalance >= 0,
|
||||
carryoverSubtitle,
|
||||
carryoverIsNegative,
|
||||
}}
|
||||
t={t}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user