diff --git a/ui-v2/src/assets/fonts/CashSansMono-Light.woff2 b/ui-v2/src/assets/fonts/CashSansMono-Light.woff2 new file mode 100644 index 00000000..d0fd7f6d Binary files /dev/null and b/ui-v2/src/assets/fonts/CashSansMono-Light.woff2 differ diff --git a/ui-v2/src/assets/fonts/CashSansMono-Regular.woff2 b/ui-v2/src/assets/fonts/CashSansMono-Regular.woff2 new file mode 100644 index 00000000..79ca6db7 Binary files /dev/null and b/ui-v2/src/assets/fonts/CashSansMono-Regular.woff2 differ diff --git a/ui-v2/src/components/tiles/ChartTile.tsx b/ui-v2/src/components/tiles/ChartTile.tsx index a161658e..ab27f170 100644 --- a/ui-v2/src/components/tiles/ChartTile.tsx +++ b/ui-v2/src/components/tiles/ChartTile.tsx @@ -1,7 +1,12 @@ import React from 'react'; import { useTimelineStyles } from '../../hooks/useTimelineStyles.ts'; -import { ChartConfig, ChartContainer } from "@/components/ui/chart.tsx"; -import { BarChart, Bar, LineChart, Line, ResponsiveContainer, Tooltip } from 'recharts'; +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart"; +import { BarChart, Bar, LineChart, Line, CartesianGrid, XAxis, ResponsiveContainer } from 'recharts'; interface ChartTileProps { title: string; @@ -27,17 +32,14 @@ export default function ChartTile({ // Convert the data array to the format expected by recharts const chartData = data.map((value, index) => ({ value, - index: `Point ${index + 1}` + point: `P${index + 1}` })); - // Chart configuration + // Chart configuration with proper color variables const chartConfig = { value: { label: title, - theme: { - light: variant === 'line' ? '#0B54DE' : '#4CAF50', - dark: variant === 'line' ? '#00CAF7' : '#4CAF50' - } + color: variant === 'line' ? 'var(--chart-2)' : 'var(--chart-1)' } } satisfies ChartConfig; @@ -45,54 +47,94 @@ export default function ChartTile({
{/* Header section with icon */}
-
+
{icon}
-
{title}
-
+
{title}
+
{value} - {trend && {trend}} + {trend && {trend}}
{/* Chart Container */} -
- - {variant === 'line' ? ( - - - - - ) : ( - - - - - )} +
+ + + {variant === 'line' ? ( + + + + + } + /> + + + ) : ( + + + + + } + /> + + + )} +
diff --git a/ui-v2/src/components/tiles/PieChartTile.tsx b/ui-v2/src/components/tiles/PieChartTile.tsx index a161339f..6d0e2d2a 100644 --- a/ui-v2/src/components/tiles/PieChartTile.tsx +++ b/ui-v2/src/components/tiles/PieChartTile.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import React, { useState } from 'react'; import { useTimelineStyles } from '../../hooks/useTimelineStyles'; import { ChartConfig, ChartContainer } from "@/components/ui/chart"; -import { PieChart, Pie, Cell, Tooltip, Legend } from 'recharts'; +import { PieChart, Pie, Cell, Sector, ResponsiveContainer } from 'recharts'; interface PieChartSegment { value: number; @@ -16,6 +16,90 @@ interface PieChartTileProps { date?: Date; } +// Custom label renderer with connecting lines +const renderCustomizedLabel = ({ + cx, + cy, + midAngle, + innerRadius, + outerRadius, + percent, + payload, + fill, +}: any) => { + const RADIAN = Math.PI / 180; + const sin = Math.sin(-RADIAN * midAngle); + const cos = Math.cos(-RADIAN * midAngle); + + // Adjust these values to position labels closer to the pie + const labelOffset = 12; + const labelDistance = 18; + + // Calculate positions with shorter distances + const mx = cx + (outerRadius + labelOffset) * cos; + const my = cy + (outerRadius + labelOffset) * sin; + const ex = mx + (cos >= 0 ? 1 : -1) * labelDistance; + const ey = my; + + // Text anchor based on which side of the pie we're on + const textAnchor = cos >= 0 ? "start" : "end"; + + // Calculate percentage + const value = (percent * 100).toFixed(0); + + // Determine if label should be on top or bottom half for potential y-offset + const isTopHalf = my < cy; + const yOffset = isTopHalf ? -2 : 2; + + // Force specific adjustments for "In Progress" label if needed + const isInProgress = payload.name === "In Progress"; + const adjustedEx = isInProgress ? ex - 5 : ex; + + return ( + + {/* Label line - using absolute coordinates for reliability */} + + {/* Label text with adjusted position */} + = 0 ? 5 : -5)} + y={ey + yOffset} + textAnchor={textAnchor} + fill="var(--text-default)" + className="text-[10px]" + style={{ + pointerEvents: 'none', + }} + > + {payload.name} ({value}%) + + + ); +}; + +// Active shape renderer for hover effect +const renderActiveShape = (props: any) => { + const { cx, cy, innerRadius, outerRadius, startAngle, endAngle, fill } = props; + + return ( + + ); +}; + export default function PieChartTile({ title, icon, @@ -23,102 +107,96 @@ export default function PieChartTile({ date }: PieChartTileProps) { const { contentCardStyle } = useTimelineStyles(date); + const [activeIndex, setActiveIndex] = useState(0); - // Convert segments to the format expected by recharts - const chartData = segments.map(segment => ({ + // Convert segments to the format expected by recharts and assign chart colors + const chartData = segments.map((segment, index) => ({ name: segment.label, - value: segment.value + value: segment.value, + chartColor: `var(--chart-${index + 1})` // Use chart-1, chart-2, chart-3, etc. })); - // Create chart configuration with theme colors - const chartConfig = segments.reduce((config, segment) => { - config[segment.label] = { - label: segment.label, - color: segment.color - }; - return config; - }, {} as ChartConfig); + // Create chart configuration using the chart color variables + const chartConfig = { + [segments[0].label.toLowerCase()]: { + label: segments[0].label, + color: 'var(--chart-1)' + }, + [segments[1].label.toLowerCase()]: { + label: segments[1].label, + color: 'var(--chart-2)' + }, + [segments[2].label.toLowerCase()]: { + label: segments[2].label, + color: 'var(--chart-3)' + } + } satisfies ChartConfig; - // Custom tooltip formatter - const tooltipFormatter = (value: number, name: string) => { - const total = segments.reduce((sum, segment) => sum + segment.value, 0); - const percentage = ((value / total) * 100).toFixed(1); - return [`${percentage}%`, name]; + const onPieEnter = (_: any, index: number) => { + setActiveIndex(index); }; return (
{/* Header */}
-
+
{icon}
-
+
{title}
{/* Pie Chart */} -
-
+
+
- - - {segments.map((segment, index) => ( - - ))} - - - + + + + {chartData.map((entry, index) => ( + + ))} + + +
- - {/* Legend */} -
- {segments.map((segment, index) => { - const percentage = ((segment.value / segments.reduce((sum, s) => sum + s.value, 0)) * 100).toFixed(1); - return ( -
-
-
- - {segment.label} - -
- - {percentage}% - -
- ); - })} -
); diff --git a/ui-v2/src/styles/main.css b/ui-v2/src/styles/main.css index 30758fb3..997da1b4 100644 --- a/ui-v2/src/styles/main.css +++ b/ui-v2/src/styles/main.css @@ -3,167 +3,257 @@ @custom-variant dark (&:is(.dark *)); -@layer base { - :root { - overflow: hidden; - --spring-easing: linear( - 0, - 0.009, - 0.035 2.1%, - 0.141, - 0.281 6.7%, - 0.723 12.9%, - 0.938 16.7%, - 1.017, - 1.077, - 1.121, - 1.149 24.3%, - 1.159, - 1.163, - 1.161, - 1.154 29.9%, - 1.129 32.8%, - 1.051 39.6%, - 1.017 43.1%, - 0.991, - 0.977 51%, - 0.974 53.8%, - 0.975 57.1%, - 0.997 69.8%, - 1.003 76.9%, - 1.004 83.8%, - 1 - ); - --spring-duration: 1.333s; +@theme { + /* reset */ + --color-*: initial; - /* custom slate */ - --slate: #393838; + /* constants */ + --color-white: #ffffff; + --color-black: #000000; - /* block */ - --block-teal: #13bbaf; - --block-orange: #ff4f00; + /* slate */ + --color-slate-100: #f0f2f8; + --color-slate-200: #c8cdd6; + --color-slate-300: #a1a7b0; + --color-slate-400: #6e747e; + --color-slate-500: #4a4e54; + --color-slate-600: #2e2e2e; - /* start arcade colors */ - --constant-white: #ffffff; - --constant-black: #000000; - --grey-10: #101010; - --grey-20: #1e1e1e; - --grey-50: #666666; - --grey-60: #959595; - --grey-80: #cccccc; - --grey-85: #dadada; - --grey-90: #e8e8e8; - --grey-95: #f0f0f0; - --dark-grey-15: #1a1a1a; - --dark-grey-25: #232323; - --dark-grey-30: #2a2a2a; - --dark-grey-40: #333333; - --dark-grey-45: #595959; - --dark-grey-60: #878787; - --dark-grey-90: #e1e1e1; + /* utility */ + --color-red-100: #ff6b6b; + --color-red-200: #f94b4b; - --background-app: var(--constant-white); - --background-prominent: var(--grey-80); - --background-standard: var(--grey-90); - --background-subtle: var(--grey-95); - --background-app-inverse: var(--constant-black); - --background-subtle-inverse: var(--dark-grey-15); - --background-standard-inverse: var(--dark-grey-25); - --background-prominent-inverse: var(--dark-grey-40); + --color-blue-100: #7cacff; + --color-blue-200: #5c98f9; - --border-divider: var(--grey-90); - --border-inverse: var(--constant-white); - --border-prominent: var(--grey-10); - --border-standard: var(--grey-60); - --border-subtle: var(--grey-90); + --color-green-100: #a3d795; + --color-green-200: #91cb80; - --icon-disabled: var(--grey-60); - --icon-extra-subtle: var(--grey-60); - --icon-inverse: var(--constant-white); - --icon-prominent: var(--grey-10); - --icon-standard: var(--grey-20); - --icon-subtle: var(--grey-50); + --color-yellow-100: #ffd966; + --color-yellow-200: #fbcd44; +} - --text-placeholder: var(--grey-60); - --text-prominent: var(--grey-10); - --text-standard: var(--grey-20); - --text-standard-inverse: var(--dark-grey-90); - --text-subtle: var(--grey-50); - --text-subtle-inverse: var(--dark-grey-60); - --text-prominent-inverse: var(--constant-white); - } +:root { + overflow: hidden; - @media (prefers-color-scheme: dark) { - :root { - --background-app: var(--constant-black); - --background-prominent: var(--dark-grey-40); - --background-standard: var(--dark-grey-25); - --background-subtle: var(--dark-grey-15); - --background-app-inverse: var(--constant-white); - --background-subtle-inverse: var(--grey-95); - --background-standard-inverse: var(--grey-90); - --background-prominent-inverse: var(--grey-80); + /* shape */ + --radius: 0.5rem; - --border-divider: var(--dark-grey-25); - --border-inverse: var(--constant-black); - --border-prominent: var(--constant-white); - --border-standard: var(--dark-grey-45); - --border-subtle: var(--dark-grey-25); + /* theming accents */ + --background-accent: var(--color-black); + --border-accent: var(--color-black); + --text-accent: var(--color-black); - --icon-disabled: var(--dark-grey-45); - --icon-extra-subtle: var(--dark-grey-45); - --icon-inverse: var(--constant-black); - --icon-prominent: var(--constant-white); - --icon-standard: var(--dark-grey-90); - --icon-subtle: var(--dark-grey-60); + /* Semantic */ + --background-default: var(--color-white); + --background-medium: var(--color-slate-200); + --background-muted: var(--color-slate-100); + --background-inverse: var(--color-black); + --background-danger: var(--color-red-200); + --background-success: var(--color-green-200); + --background-info: var(--color-blue-200); + --background-warning: var(--color-yellow-200); - --text-placeholder: var(--dark-grey-45); - --text-prominent: var(--constant-white); - --text-standard: var(--dark-grey-90); - --text-standard-inverse: var(--grey-20); - --text-subtle: var(--dark-grey-60); - --text-subtle-inverse: var(--grey-50); - --text-prominent-inverse: var(--grey-20); - } - } + --border-default: var(--color-slate-200); + --border-input: var(--color-slate-200); + --border-strong: var(--color-slate-300); + --border-inverse: var(--color-black); + --border-danger: var(--color-red-200); + --border-success: var(--color-green-200); + --border-warning: var(--color-yellow-200); + --border-info: var(--color-blue-200); - body { - background-color: var(--background-app); - } + --text-default: var(--color-slate-600); + --text-muted: var(--color-slate-400); + --text-inverse: var(--color-white); + --text-danger: var(--color-red-200); + --text-success: var(--color-green-200); + --text-warning: var(--color-yellow-200); + --text-info: var(--color-blue-200); + + --ring: var(--border-strong); + + --chart-1: #f6b44a; + --chart-2: #7585ff; + --chart-3: #d76a6a; + --chart-4: #d185e0; + --chart-5: #91cb80; + + --sidebar: var(--background-default); + --sidebar-foreground: var(--text-default); + --sidebar-primary: var(--background-accent); + --sidebar-primary-foreground: var(--text-inverse); + --sidebar-accent: var(--background-muted); + --sidebar-accent-foreground: var(--text-default); + --sidebar-border: var(--border-default); + --sidebar-ring: var(--border-default); +} + +.dark { + /* theming accents */ + --background-accent: var(--color-white); + --border-accent: var(--color-white); + --text-accent: var(--color-white); + + /* semantic */ + --background-default: var(--color-black); + --background-medium: var(--color-slate-500); + --background-muted: var(--color-slate-600); + --background-inverse: var(--color-white); + --background-danger: var(--color-red-100); + --background-success: var(--color-green-100); + --background-info: var(--color-blue-100); + --background-warning: var(--color-yellow-100); + + --border-default: var(--color-slate-600); + --border-input: var(--color-slate-600); + --border-strong: var(--color-slate-200); + --border-inverse: var(--color-white); + --border-danger: var(--color-red-200); + --border-success: var(--color-green-200); + --border-warning: var(--color-yellow-200); + --border-info: var(--color-blue-200); + + --text-default: var(--color-white); + --text-muted: var(--color-slate-300); + --text-inverse: var(--color-black); + --text-danger: var(--color-red-100); + --text-success: var(--color-green-100); + --text-warning: var(--color-yellow-100); + --text-info: var(--color-blue-100); + + --ring: var(--border-strong); + + --chart-1: #f6b44a; + --chart-2: #7585ff; + --chart-3: #d76a6a; + --chart-4: #d185e0; + --chart-5: #91cb80; + + --sidebar: var(--background-default); + --sidebar-foreground: var(--text-default); + --sidebar-primary: var(--background-accent); + --sidebar-primary-foreground: var(--text-inverse); + --sidebar-accent: var(--background-muted); + --sidebar-accent-foreground: var(--text-default); + --sidebar-border: var(--border-default); + --sidebar-ring: var(--border-default); +} + +@theme inline { + /* semantic */ + --color-background-default: var(--background-default); + --color-background-medium: var(--background-medium); + --color-background-inverse: var(--background-inverse); + --color-background-muted: var(--background-muted); + --color-background-danger: var(--background-danger); + --color-background-success: var(--background-success); + --color-background-info: var(--background-info); + --color-background-warning: var(--background-warning); + + --color-background-accent: var(--background-accent); + --color-border-accent: var(--border-accent); + --color-text-accent: var(--text-accent); + + --color-border-default: var(--border-default); + --color-border-input: var(--border-input); + --color-border-strong: var(--border-strong); + --color-border-inverse: var(--border-inverse); + --color-border-danger: var(--border-danger); + --color-border-success: var(--border-success); + --color-border-warning: var(--border-warning); + --color-border-info: var(--border-info); + + --color-text-default: var(--text); + --color-text-muted: var(--text-muted); + --color-text-inverse: var(--text-inverse); + --color-text-danger: var(--text-danger); + --color-text-success: var(--text-success); + --color-text-warning: var(--text-warning); + --color-text-info: var(--text-info); + + /* fonts */ + --font-sans: "Cash Sans", sans-serif; + --font-mono: "Cash Sans Mono", monospace; + --font-serif: serif; + + /* shape */ + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + + --color-ring: var(--ring); + + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); } @font-face { - font-family: 'Cash Sans'; - src: - url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Regular.woff2) - format('woff2'), - url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff/CashSans-Regular.woff) - format('woff'); + font-family: "Cash Sans"; + src: url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Light.woff2) + format("woff2"), + url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff/CashSans-Light.woff) + format("woff"); + font-weight: 300; + font-style: normal; +} + +@font-face { + font-family: "Cash Sans"; + src: url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Regular.woff2) + format("woff2"), + url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff/CashSans-Regular.woff) + format("woff"); font-weight: 400; font-style: normal; } @font-face { - font-family: 'Cash Sans'; - src: - url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Medium.woff2) - format('woff2'), - url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff/CashSans-Medium.woff) format('woff'); + font-family: "Cash Sans"; + src: url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Medium.woff2) + format("woff2"), + url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff/CashSans-Medium.woff) + format("woff"); font-weight: 500; font-style: normal; } @font-face { - font-family: 'Cash Sans Mono'; - src: - url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSansMono-Regular.woff2) - format('woff2'), - url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff/CashSansMono-Regular.woff) - format('woff'); + font-family: "Cash Sans Mono"; + src: url(../assets/fonts/CashSansMono-Light.woff2) format("woff2"); + font-weight: 300; + font-style: normal; +} + +@font-face { + font-family: "Cash Sans Mono"; + src: url(../assets/fonts/CashSansMono-Regular.woff2) format("woff2"); font-weight: 400; font-style: normal; } +@layer base { + * { + @apply border-border-default; + } + body { + @apply bg-background-default text-text-default; + } +} + .titlebar-drag-region { -webkit-app-region: drag; height: 32px; @@ -173,119 +263,3 @@ left: 0; z-index: 50; } - -@theme inline { - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); - --color-background: var(--background); - --color-foreground: var(--foreground); - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); - --color-popover: var(--popover); - --color-popover-foreground: var(--popover-foreground); - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-secondary: var(--secondary); - --color-secondary-foreground: var(--secondary-foreground); - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); - --color-accent: var(--accent); - --color-accent-foreground: var(--accent-foreground); - --color-destructive: var(--destructive); - --color-border: var(--border); - --color-input: var(--input); - --color-ring: var(--ring); - --color-chart-1: var(--chart-1); - --color-chart-2: var(--chart-2); - --color-chart-3: var(--chart-3); - --color-chart-4: var(--chart-4); - --color-chart-5: var(--chart-5); - --color-sidebar: var(--sidebar); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-ring: var(--sidebar-ring); -} - -:root { - --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); -} - -.dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); -} - -@layer base { - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - } -}