mirror of
https://github.com/aljazceru/goose.git
synced 2025-12-18 14:44:21 +01:00
ui-v2 cleanup (#2701)
This commit is contained in:
@@ -5,3 +5,9 @@ if git diff --cached --name-only | grep -q "^ui/desktop/"; then
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
cd ui/desktop && npx lint-staged
|
||||
fi
|
||||
|
||||
# Only auto-format ui-v2 TS code if relevant files are modified
|
||||
if git diff --cached --name-only | grep -q "^ui-v2/"; then
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
cd ui-v2 && npx lint-staged
|
||||
fi
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
"at-rule-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen"]
|
||||
"ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen", "theme", "custom-variant"]
|
||||
}
|
||||
],
|
||||
"at-rule-no-deprecated": null,
|
||||
"custom-property-pattern": null,
|
||||
"no-descending-specificity": null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ async function createWindow() {
|
||||
// Create the browser window with headless options when needed
|
||||
const mainWindow = new BrowserWindow({
|
||||
titleBarStyle: process.platform === 'darwin' ? 'hidden' : 'default',
|
||||
trafficLightPosition: process.platform === 'darwin' ? { x: 16, y: 10 } : undefined,
|
||||
...(process.platform === 'darwin' ? { trafficLightPosition: { x: 16, y: 10 } } : {}),
|
||||
frame: false,
|
||||
width: 1200,
|
||||
height: 800,
|
||||
|
||||
@@ -89,8 +89,18 @@ module.exports = [
|
||||
navigator: 'readonly',
|
||||
console: 'readonly',
|
||||
setTimeout: 'readonly',
|
||||
Blob: 'readonly',
|
||||
setInterval: 'readonly',
|
||||
clearTimeout: 'readonly',
|
||||
clearInterval: 'readonly',
|
||||
requestAnimationFrame: 'readonly',
|
||||
localStorage: 'readonly',
|
||||
HTMLDivElement: 'readonly',
|
||||
HTMLTextAreaElement: 'readonly',
|
||||
HTMLFormElement: 'readonly',
|
||||
HTMLInputElement: 'readonly',
|
||||
MutationObserver: 'readonly',
|
||||
IntersectionObserver: 'readonly',
|
||||
Blob: 'readonly',
|
||||
SVGSVGElement: 'readonly',
|
||||
},
|
||||
},
|
||||
@@ -106,6 +116,15 @@ module.exports = [
|
||||
},
|
||||
},
|
||||
},
|
||||
// UI components (shadcn/ui) - more relaxed rules
|
||||
{
|
||||
files: ['src/components/ui/**/*.{ts,tsx}'],
|
||||
rules: {
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'react/prop-types': 'off',
|
||||
},
|
||||
},
|
||||
// Test configuration
|
||||
{
|
||||
files: ['**/*.test.{ts,tsx}', 'src/test/**/*.{ts,tsx}'],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
export function cn(...inputs: ClassValue[]): string {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
106
ui-v2/package-lock.json
generated
106
ui-v2/package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@tanstack/react-router": "^1.120.5",
|
||||
"@tanstack/react-router-devtools": "^1.120.11",
|
||||
"@tanstack/router": "^0.0.1-beta.53",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -3437,14 +3438,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-router": {
|
||||
"version": "1.120.5",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.120.5.tgz",
|
||||
"integrity": "sha512-A+YRftGwAeFBxa8DF5ujNYqkSEbjCa1KjxDNYr+jWj16jjTxrz/XqgOJCv5ZfbAqqqOa3yLYoQbWa7OGz5jHuA==",
|
||||
"version": "1.120.11",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.120.11.tgz",
|
||||
"integrity": "sha512-VpG8gT+kibsdF9yQIOMfnCGe1pmUlrAG/fOoTm0gru1OEkJ2Tzc80codqiocRHQ9ULmlB4H/Zx56EZyQyF3ELw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/history": "1.115.0",
|
||||
"@tanstack/react-store": "^0.7.0",
|
||||
"@tanstack/router-core": "1.120.5",
|
||||
"@tanstack/router-core": "1.120.10",
|
||||
"jsesc": "^3.1.0",
|
||||
"tiny-invariant": "^1.3.3",
|
||||
"tiny-warning": "^1.0.3"
|
||||
@@ -3461,6 +3462,28 @@
|
||||
"react-dom": ">=18.0.0 || >=19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-router-devtools": {
|
||||
"version": "1.120.11",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-router-devtools/-/react-router-devtools-1.120.11.tgz",
|
||||
"integrity": "sha512-bk34Kn7SubkUq3TbVN6wfALvOZ63ou/dzPqhijZAwHKXpatE90BwB/Y8mLhcoH+64iXtpf/ZP2lqqsrxLXz0pw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/router-devtools-core": "^1.120.10",
|
||||
"solid-js": "^1.9.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tanstack/react-router": "^1.120.11",
|
||||
"react": ">=18.0.0 || >=19.0.0",
|
||||
"react-dom": ">=18.0.0 || >=19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-store": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.7.0.tgz",
|
||||
@@ -3498,9 +3521,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/router-core": {
|
||||
"version": "1.120.5",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.120.5.tgz",
|
||||
"integrity": "sha512-IXLNv3j7rpTL/YNCWHijZgrnxFuvD4Nz/nUiGSak4x5BKzlnuZEso81xFcIuczVrEW72NxZv8IfzpR5M5Tuc0A==",
|
||||
"version": "1.120.10",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.120.10.tgz",
|
||||
"integrity": "sha512-AmEJAYt+6w/790zTnfddVhnK1QJCnd96H4xg1aD65Oohc8+OTQBxgWky/wzqwhHRdkdsBgRT7iWac9x5Y8UrQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/history": "1.115.0",
|
||||
@@ -3515,6 +3538,34 @@
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/router-devtools-core": {
|
||||
"version": "1.120.10",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/router-devtools-core/-/router-devtools-core-1.120.10.tgz",
|
||||
"integrity": "sha512-fysPrKH7dL/G/guHm0HN+ceFEBZnbKaU9R8KZHo/Qzue7WxQV+g4or2EWnbBJ8/aF+C/WYgxR1ATFqfZEjHSfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1",
|
||||
"goober": "^2.1.16"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tanstack/router-core": "^1.120.10",
|
||||
"csstype": "^3.0.10",
|
||||
"solid-js": ">=1.9.5",
|
||||
"tiny-invariant": "^1.3.3"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"csstype": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/router/node_modules/@tanstack/store": {
|
||||
"version": "0.0.1-beta.52",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.0.1-beta.52.tgz",
|
||||
@@ -8712,6 +8763,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/goober": {
|
||||
"version": "2.1.16",
|
||||
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz",
|
||||
"integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"csstype": "^3.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
@@ -13234,6 +13294,27 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/seroval": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz",
|
||||
"integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/seroval-plugins": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.2.tgz",
|
||||
"integrity": "sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"seroval": "^1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.16.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||
@@ -13541,6 +13622,17 @@
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/solid-js": {
|
||||
"version": "1.9.7",
|
||||
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.7.tgz",
|
||||
"integrity": "sha512-/saTKi8iWEM233n5OSi1YHCCuh66ZIQ7aK2hsToPe4tqGm7qAejU1SwNuTPivbWAYq7SjuHVVYxxuZQNRbICiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.1.0",
|
||||
"seroval": "~1.3.0",
|
||||
"seroval-plugins": "~1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
|
||||
@@ -29,11 +29,12 @@
|
||||
"prettier:fix": "prettier --write \"src/**/*.{ts,tsx,js,jsx,css}\" \"electron/**/*.{ts,tsx,js,jsx,css}\"",
|
||||
"format": "npm run prettier:fix && npm run lint:style:fix",
|
||||
"check-all": "npm run typecheck && npm run lint && npm run prettier",
|
||||
"prepare": "cd ../.. && husky install"
|
||||
"prepare": "cd .. && npx husky"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@tanstack/react-router": "^1.120.5",
|
||||
"@tanstack/react-router-devtools": "^1.120.11",
|
||||
"@tanstack/router": "^0.0.1-beta.53",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import React from 'react';
|
||||
import { MainLayout } from './layout/MainLayout';
|
||||
|
||||
const App: React.FC = (): React.ReactElement => {
|
||||
return <MainLayout />;
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
interface BrandCardProps {
|
||||
date?: Date;
|
||||
@@ -7,14 +7,14 @@ interface BrandCardProps {
|
||||
|
||||
// Array of congratulatory messages for past days
|
||||
const pastDayMessages = [
|
||||
{ title: "Great work!", message: "You accomplished so much" },
|
||||
{ title: "Well done!", message: "Another successful day" },
|
||||
{ title: "Fantastic job!", message: "Making progress every day" },
|
||||
{ title: "Nice one!", message: "Another day in the books" },
|
||||
{ title: "Awesome work!", message: "Keep up the momentum" }
|
||||
{ title: 'Great work!', message: 'You accomplished so much' },
|
||||
{ title: 'Well done!', message: 'Another successful day' },
|
||||
{ title: 'Fantastic job!', message: 'Making progress every day' },
|
||||
{ title: 'Nice one!', message: 'Another day in the books' },
|
||||
{ title: 'Awesome work!', message: 'Keep up the momentum' },
|
||||
];
|
||||
|
||||
export default function BrandCard({ date, className = '' }: BrandCardProps) {
|
||||
export default function BrandCard({ date, className = '' }: BrandCardProps): ReactElement {
|
||||
const isToday = date ? new Date().toDateString() === date.toDateString() : true;
|
||||
|
||||
// Get a consistent message for each date
|
||||
@@ -33,10 +33,7 @@ export default function BrandCard({ date, className = '' }: BrandCardProps) {
|
||||
flex flex-col justify-between
|
||||
p-4
|
||||
w-[366px] h-[256px]
|
||||
${isToday
|
||||
? 'bg-textStandard dark:bg-white'
|
||||
: 'bg-gray-400/40 dark:bg-gray-400/40'
|
||||
}
|
||||
${isToday ? 'bg-textStandard dark:bg-white' : 'bg-gray-400/40 dark:bg-gray-400/40'}
|
||||
rounded-[18px]
|
||||
relative
|
||||
overflow-hidden
|
||||
@@ -49,13 +46,16 @@ export default function BrandCard({ date, className = '' }: BrandCardProps) {
|
||||
{/* Content */}
|
||||
<div className="relative z-10 w-full">
|
||||
{/* Logo */}
|
||||
<div className={`
|
||||
<div
|
||||
className={`
|
||||
w-6 h-6
|
||||
${isToday
|
||||
${
|
||||
isToday
|
||||
? '[&_path]:fill-current text-white dark:text-gray-900'
|
||||
: '[&_path]:fill-current text-white/60 dark:text-white/60'
|
||||
}
|
||||
`}>
|
||||
`}
|
||||
>
|
||||
<svg width="24" height="23" viewBox="0 0 24 23" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path d="M0.5 10.5733C0.5 8.19815 2.41385 6.27271 4.77471 6.27271H6.67984C9.04069 6.27271 10.9545 8.19816 10.9545 10.5733V18.6994C10.9545 21.0745 9.04069 23 6.67983 23H4.77471C2.41385 23 0.5 21.0745 0.5 18.6994V10.5733Z" />
|
||||
@@ -101,7 +101,7 @@ export default function BrandCard({ date, className = '' }: BrandCardProps) {
|
||||
transition-colors
|
||||
`}
|
||||
>
|
||||
You've got 3 major updates this morning
|
||||
You've got 3 major updates this morning
|
||||
</h1>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useEffect, useState, ReactElement } from 'react';
|
||||
|
||||
import { Moon, Sun } from 'lucide-react';
|
||||
|
||||
export function DarkModeToggle() {
|
||||
export function DarkModeToggle(): ReactElement {
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useEffect, useState, ReactElement } from 'react';
|
||||
|
||||
import { useTimeline } from '../contexts/TimelineContext';
|
||||
|
||||
export function DateDisplay() {
|
||||
export function DateDisplay(): ReactElement {
|
||||
const { currentDate } = useTimeline();
|
||||
const [displayDate, setDisplayDate] = useState(currentDate);
|
||||
const [isFlipping, setIsFlipping] = useState(false);
|
||||
@@ -17,13 +18,26 @@ export function DateDisplay() {
|
||||
}, [currentDate]);
|
||||
|
||||
const formatDate = (date: Date) => {
|
||||
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
const monthNames = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
];
|
||||
const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
|
||||
return {
|
||||
month: monthNames[date.getMonth()],
|
||||
day: date.getDate(),
|
||||
weekday: dayNames[date.getDay()]
|
||||
weekday: dayNames[date.getDay()],
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
42
ui-v2/src/components/GooseLogo.tsx
Normal file
42
ui-v2/src/components/GooseLogo.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { FC } from 'react';
|
||||
|
||||
import { Goose, Rain } from './icons/Goose';
|
||||
|
||||
interface GooseLogoProps {
|
||||
className?: string;
|
||||
size?: 'default' | 'small';
|
||||
hover?: boolean;
|
||||
}
|
||||
|
||||
const GooseLogo: FC<GooseLogoProps> = ({ className = '', size = 'default', hover = true }) => {
|
||||
const sizes = {
|
||||
default: {
|
||||
frame: 'w-16 h-16',
|
||||
rain: 'w-[275px] h-[275px]',
|
||||
goose: 'w-16 h-16',
|
||||
},
|
||||
small: {
|
||||
frame: 'w-8 h-8',
|
||||
rain: 'w-[150px] h-[150px]',
|
||||
goose: 'w-8 h-8',
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`${className} ${sizes[size].frame} group relative`}>
|
||||
{/* Rain with enhanced visibility for testing */}
|
||||
<div
|
||||
className={`${sizes[size].rain} absolute left-0 bottom-0 ${hover ? 'opacity-0 group-hover:opacity-100' : 'opacity-100'} transition-all duration-500 z-10`}
|
||||
style={{
|
||||
filter: 'brightness(2) contrast(2) saturate(2)',
|
||||
mixBlendMode: 'multiply',
|
||||
}}
|
||||
>
|
||||
<Rain className="w-full h-full" />
|
||||
</div>
|
||||
<Goose className={`${sizes[size].goose} absolute left-0 bottom-0 z-20`} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GooseLogo;
|
||||
12
ui-v2/src/components/Home.tsx
Normal file
12
ui-v2/src/components/Home.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
import GooseLogo from '../components/GooseLogo';
|
||||
|
||||
export default function Home(): ReactElement {
|
||||
return (
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<GooseLogo />
|
||||
<h1 className="text-2xl font-bold text-textProminent">Goose v2</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,5 @@
|
||||
import React, { useRef, useMemo, useEffect } from 'react';
|
||||
import { useTimeline } from '../contexts/TimelineContext';
|
||||
import ChartTile from './tiles/ChartTile.tsx';
|
||||
import HighlightTile from './tiles/HighlightTile.tsx';
|
||||
import PieChartTile from './tiles/PieChartTile.tsx';
|
||||
import ListTile from './tiles/ListTile.tsx';
|
||||
import ClockTile from './tiles/ClockTile.tsx';
|
||||
import TimelineDots from './TimelineDots';
|
||||
import { useRef, useMemo, useEffect, ReactElement } from 'react';
|
||||
|
||||
import {
|
||||
ChartLineIcon,
|
||||
ChartBarIcon,
|
||||
@@ -14,6 +8,13 @@ import {
|
||||
StarIcon,
|
||||
TrendingUpIcon,
|
||||
} from './icons';
|
||||
import { useTimeline } from '../contexts/TimelineContext';
|
||||
import ChartTile from './tiles/ChartTile.tsx';
|
||||
import ClockTile from './tiles/ClockTile.tsx';
|
||||
import HighlightTile from './tiles/HighlightTile.tsx';
|
||||
import ListTile from './tiles/ListTile.tsx';
|
||||
import PieChartTile from './tiles/PieChartTile.tsx';
|
||||
import TimelineDots from './TimelineDots';
|
||||
|
||||
const generateRandomData = (length: number) =>
|
||||
Array.from({ length }, () => Math.floor(Math.random() * 100));
|
||||
@@ -242,7 +243,7 @@ const generateTileData = (date: Date) => {
|
||||
};
|
||||
};
|
||||
|
||||
export default function Timeline() {
|
||||
export default function Timeline(): ReactElement {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const sectionRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||
const { setCurrentDate } = useTimeline();
|
||||
@@ -269,8 +270,11 @@ export default function Timeline() {
|
||||
}, []);
|
||||
|
||||
// Function to center the timeline in a section
|
||||
const centerTimeline = (sectionElement: HTMLDivElement, animate: boolean = true) => {
|
||||
if (!sectionElement) return;
|
||||
const centerTimeline = (
|
||||
sectionElement: HTMLDivElement | null,
|
||||
animate: boolean = true
|
||||
): HTMLDivElement | null => {
|
||||
if (!sectionElement) return sectionElement;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const totalWidth = sectionElement.scrollWidth;
|
||||
@@ -280,15 +284,21 @@ export default function Timeline() {
|
||||
if (animate) {
|
||||
sectionElement.scrollTo({
|
||||
left: scrollToX,
|
||||
behavior: 'smooth'
|
||||
behavior: 'smooth',
|
||||
});
|
||||
} else {
|
||||
sectionElement.scrollLeft = scrollToX;
|
||||
}
|
||||
});
|
||||
|
||||
return sectionElement;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Capture ref values at the start of the effect
|
||||
const currentContainer = containerRef.current;
|
||||
const currentSections = [...sectionRefs.current];
|
||||
|
||||
// Create the intersection observer
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
@@ -299,7 +309,7 @@ export default function Timeline() {
|
||||
if (entry.isIntersecting) {
|
||||
// Update current date
|
||||
const sectionIndex = sectionRefs.current.indexOf(section);
|
||||
if (sectionIndex !== -1) {
|
||||
if (sectionIndex !== -1 && sections[sectionIndex]) {
|
||||
const date = sections[sectionIndex].date;
|
||||
setCurrentDate(date);
|
||||
}
|
||||
@@ -319,7 +329,7 @@ export default function Timeline() {
|
||||
|
||||
// Add scroll handler for even faster updates
|
||||
const handleScroll = () => {
|
||||
if (!containerRef.current) return;
|
||||
if (!currentContainer) return;
|
||||
|
||||
// Find the section closest to the middle of the viewport
|
||||
const viewportMiddle = window.innerHeight / 2;
|
||||
@@ -340,7 +350,7 @@ export default function Timeline() {
|
||||
|
||||
if (closestSection) {
|
||||
const sectionIndex = sectionRefs.current.indexOf(closestSection);
|
||||
if (sectionIndex !== -1) {
|
||||
if (sectionIndex !== -1 && sections[sectionIndex]) {
|
||||
const date = sections[sectionIndex].date;
|
||||
setCurrentDate(date);
|
||||
}
|
||||
@@ -351,13 +361,14 @@ export default function Timeline() {
|
||||
let lastScrollTime = 0;
|
||||
const throttledScrollHandler = () => {
|
||||
const now = Date.now();
|
||||
if (now - lastScrollTime >= 150) { // Throttle to ~6-7 times per second
|
||||
if (now - lastScrollTime >= 150) {
|
||||
// Throttle to ~6-7 times per second
|
||||
handleScroll();
|
||||
lastScrollTime = now;
|
||||
}
|
||||
};
|
||||
|
||||
containerRef.current?.addEventListener('scroll', throttledScrollHandler, { passive: true });
|
||||
currentContainer?.addEventListener('scroll', throttledScrollHandler, { passive: true });
|
||||
|
||||
// Add resize handler
|
||||
const handleResize = () => {
|
||||
@@ -385,30 +396,41 @@ export default function Timeline() {
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup function
|
||||
// Cleanup function using captured values
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
containerRef.current?.removeEventListener('scroll', throttledScrollHandler);
|
||||
sectionRefs.current.forEach((section) => {
|
||||
currentContainer?.removeEventListener('scroll', throttledScrollHandler);
|
||||
currentSections.forEach((section) => {
|
||||
if (section) {
|
||||
observer.unobserve(section);
|
||||
}
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
}, [sections, setCurrentDate]);
|
||||
|
||||
const renderTile = (tile: any, index: number) => {
|
||||
interface TileProps {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface Tile {
|
||||
type: string;
|
||||
props: TileProps;
|
||||
}
|
||||
|
||||
const renderTile = (tile: Tile, index: number): ReactElement | null => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const props = tile.props as any; // Use any for flexibility with different tile prop types
|
||||
switch (tile.type) {
|
||||
case 'chart':
|
||||
return <ChartTile key={index} {...tile.props} />;
|
||||
return <ChartTile key={index} {...props} />;
|
||||
case 'highlight':
|
||||
return <HighlightTile key={index} {...tile.props} />;
|
||||
return <HighlightTile key={index} {...props} />;
|
||||
case 'pie':
|
||||
return <PieChartTile key={index} {...tile.props} />;
|
||||
return <PieChartTile key={index} {...props} />;
|
||||
case 'list':
|
||||
return <ListTile key={index} {...tile.props} />;
|
||||
return <ListTile key={index} {...props} />;
|
||||
case 'clock':
|
||||
return <ClockTile key={index} {...tile.props} />;
|
||||
return <ClockTile key={index} {...props} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -422,7 +444,9 @@ export default function Timeline() {
|
||||
{sections.map((section, index) => (
|
||||
<div
|
||||
key={index}
|
||||
ref={(el) => (sectionRefs.current[index] = el)}
|
||||
ref={(el) => {
|
||||
sectionRefs.current[index] = el;
|
||||
}}
|
||||
className="h-screen relative snap-center snap-always overflow-y-hidden overflow-x-scroll snap-x snap-mandatory scrollbar-hide animate-[fadein_300ms_ease-in-out]"
|
||||
>
|
||||
<div className="relative min-w-[calc(200vw+100px)] h-full flex items-center">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { createContext, useContext, useState, useCallback } from 'react';
|
||||
import { createContext, useContext, useState, useCallback, ReactElement, ReactNode } from 'react';
|
||||
|
||||
interface TimelineContextType {
|
||||
currentDate: Date;
|
||||
@@ -8,10 +8,10 @@ interface TimelineContextType {
|
||||
|
||||
const TimelineContext = createContext<TimelineContextType | undefined>(undefined);
|
||||
|
||||
export function TimelineProvider({ children }: { children: React.ReactNode }) {
|
||||
export function TimelineProvider({ children }: { children: ReactNode }): ReactElement {
|
||||
const [currentDate, setCurrentDate] = useState(new Date());
|
||||
|
||||
const isCurrentDate = useCallback((date: Date) => {
|
||||
const isCurrentDate = useCallback((date: Date): boolean => {
|
||||
return date.toDateString() === new Date().toDateString();
|
||||
}, []);
|
||||
|
||||
@@ -22,7 +22,7 @@ export function TimelineProvider({ children }: { children: React.ReactNode }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function useTimeline() {
|
||||
export function useTimeline(): TimelineContextType {
|
||||
const context = useContext(TimelineContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useTimeline must be used within a TimelineProvider');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMemo, ReactElement } from 'react';
|
||||
|
||||
interface TimelineDotsProps {
|
||||
height: number | string;
|
||||
@@ -12,7 +12,11 @@ interface Dot {
|
||||
opacity: number;
|
||||
}
|
||||
|
||||
export default function TimelineDots({ height, isUpper = false, isCurrentDay = false }: TimelineDotsProps) {
|
||||
export default function TimelineDots({
|
||||
height,
|
||||
isUpper = false,
|
||||
isCurrentDay = false,
|
||||
}: TimelineDotsProps): ReactElement {
|
||||
// Generate random dots with clusters
|
||||
const dots = useMemo(() => {
|
||||
const generateDots = () => {
|
||||
@@ -20,21 +24,27 @@ export default function TimelineDots({ height, isUpper = false, isCurrentDay = f
|
||||
const numDots = Math.floor(Math.random() * 8) + 8; // 8-15 dots
|
||||
|
||||
// Create 2-3 cluster points
|
||||
const clusterPoints = Array.from({ length: Math.floor(Math.random() * 2) + 2 },
|
||||
() => Math.random() * 100);
|
||||
const clusterPoints = Array.from(
|
||||
{ length: Math.floor(Math.random() * 2) + 2 },
|
||||
() => Math.random() * 100
|
||||
);
|
||||
|
||||
for (let i = 0; i < numDots; i++) {
|
||||
// Decide if this dot should be part of a cluster
|
||||
const isCluster = Math.random() < 0.7; // 70% chance of being in a cluster
|
||||
|
||||
let top;
|
||||
if (isCluster) {
|
||||
if (isCluster && clusterPoints.length > 0) {
|
||||
// Pick a random cluster point and add some variation
|
||||
const clusterPoint = clusterPoints[Math.floor(Math.random() * clusterPoints.length)];
|
||||
if (clusterPoint !== undefined) {
|
||||
top = clusterPoint + (Math.random() - 0.5) * 15; // ±7.5% variation
|
||||
} else {
|
||||
top = Math.random() * 100;
|
||||
}
|
||||
} else {
|
||||
top = Math.random() * 100;
|
||||
}
|
||||
|
||||
// Ensure dot is within bounds
|
||||
top = Math.max(5, Math.min(95, top));
|
||||
@@ -57,7 +67,7 @@ export default function TimelineDots({ height, isUpper = false, isCurrentDay = f
|
||||
style={{
|
||||
height: height,
|
||||
bottom: isUpper ? 'calc(50% + 96px)' : '0',
|
||||
top: isUpper ? undefined : 'calc(50% + 96px)'
|
||||
top: isUpper ? undefined : 'calc(50% + 96px)',
|
||||
}}
|
||||
>
|
||||
{/* Main line */}
|
||||
@@ -71,7 +81,7 @@ export default function TimelineDots({ height, isUpper = false, isCurrentDay = f
|
||||
height: '4px',
|
||||
left: '-1.625px', // Center 4px dot on 0.75px line
|
||||
top: '0',
|
||||
transform: 'translateY(-50%)'
|
||||
transform: 'translateY(-50%)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -93,4 +103,4 @@ export default function TimelineDots({ height, isUpper = false, isCurrentDay = f
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import React from 'react';
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
interface BrandCardProps {
|
||||
date?: Date;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function BrandCard({ }: BrandCardProps) {
|
||||
const pastDayMessages = [
|
||||
{ title: 'Great work!', message: 'You accomplished so much' },
|
||||
{ title: 'Well done!', message: 'Another successful day' },
|
||||
{ title: 'Fantastic job!', message: 'Making progress every day' },
|
||||
{ title: 'Nice one!', message: 'Another day in the books' },
|
||||
{ title: 'Awesome work!', message: 'Keep up the momentum' },
|
||||
];
|
||||
|
||||
export default function BrandCard({ date, className }: BrandCardProps): ReactElement {
|
||||
const isToday = date ? new Date().toDateString() === date.toDateString() : true;
|
||||
|
||||
// Get a consistent message for each date
|
||||
@@ -24,29 +32,29 @@ export default function BrandCard({ }: BrandCardProps) {
|
||||
flex flex-col justify-between
|
||||
p-4
|
||||
w-[366px] h-[256px]
|
||||
${isToday
|
||||
? 'bg-textStandard dark:bg-white'
|
||||
: 'bg-gray-400/40 dark:bg-gray-400/40'
|
||||
}
|
||||
${isToday ? 'bg-textStandard dark:bg-white' : 'bg-gray-400/40 dark:bg-gray-400/40'}
|
||||
rounded-[18px]
|
||||
relative
|
||||
overflow-hidden
|
||||
transition-all duration-200
|
||||
shadow-[0_0_13.7px_rgba(0,0,0,0.04)]
|
||||
dark:shadow-[0_0_24px_rgba(255,255,255,0.02)]
|
||||
${className}
|
||||
${className || ''}
|
||||
`}
|
||||
>
|
||||
{/* Content */}
|
||||
<div className="relative z-10 w-full">
|
||||
{/* Logo */}
|
||||
<div className={`
|
||||
<div
|
||||
className={`
|
||||
w-6 h-6
|
||||
${isToday
|
||||
${
|
||||
isToday
|
||||
? '[&_path]:fill-current text-white dark:text-gray-900'
|
||||
: '[&_path]:fill-current text-white/60 dark:text-white/60'
|
||||
}
|
||||
`}>
|
||||
`}
|
||||
>
|
||||
<svg width="24" height="23" viewBox="0 0 24 23" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path d="M0.5 10.5733C0.5 8.19815 2.41385 6.27271 4.77471 6.27271H6.67984C9.04069 6.27271 10.9545 8.19816 10.9545 10.5733V18.6994C10.9545 21.0745 9.04069 23 6.67983 23H4.77471C2.41385 23 0.5 21.0745 0.5 18.6994V10.5733Z" />
|
||||
@@ -92,7 +100,7 @@ export default function BrandCard({ }: BrandCardProps) {
|
||||
transition-colors
|
||||
`}
|
||||
>
|
||||
You've got 3 major updates this morning
|
||||
You've got 3 major updates this morning
|
||||
</h1>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface ChatDockProps {
|
||||
@@ -13,9 +14,9 @@ export const ChatDock: React.FC<ChatDockProps> = ({ onTileCreatorToggle }) => {
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{
|
||||
delay: 0.2,
|
||||
type: "spring",
|
||||
type: 'spring',
|
||||
stiffness: 300,
|
||||
damping: 30
|
||||
damping: 30,
|
||||
}}
|
||||
>
|
||||
<motion.button
|
||||
|
||||
@@ -1,51 +1,49 @@
|
||||
import React, { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "../ui/tooltip";
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
|
||||
|
||||
// Define the tool items
|
||||
const CHAT_TOOLS = [
|
||||
{
|
||||
icon: (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="white" stroke="none">
|
||||
<path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"/>
|
||||
<path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z" />
|
||||
</svg>
|
||||
),
|
||||
label: "Make a Tile",
|
||||
color: "bg-[#4F6BFF] hover:bg-[#4F6BFF]/90",
|
||||
label: 'Make a Tile',
|
||||
color: 'bg-[#4F6BFF] hover:bg-[#4F6BFF]/90',
|
||||
rotation: -3,
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="white" stroke="none">
|
||||
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 14l-5-5 1.41-1.41L12 14.17l4.59-4.58L18 11l-6 6z"/>
|
||||
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 14l-5-5 1.41-1.41L12 14.17l4.59-4.58L18 11l-6 6z" />
|
||||
</svg>
|
||||
),
|
||||
label: "Tasks",
|
||||
color: "bg-[#E042A5] hover:bg-[#E042A5]/90",
|
||||
label: 'Tasks',
|
||||
color: 'bg-[#E042A5] hover:bg-[#E042A5]/90',
|
||||
rotation: 2,
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="white" stroke="none">
|
||||
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||
</svg>
|
||||
),
|
||||
label: "Add",
|
||||
color: "bg-[#05C168] hover:bg-[#05C168]/90",
|
||||
label: 'Add',
|
||||
color: 'bg-[#05C168] hover:bg-[#05C168]/90',
|
||||
rotation: -2,
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="white" stroke="none">
|
||||
<path d="M12 2L1 21h22L12 2zm0 3.83L19.17 19H4.83L12 5.83zM11 16h2v2h-2zm0-6h2v4h-2z"/>
|
||||
<path d="M12 2L1 21h22L12 2zm0 3.83L19.17 19H4.83L12 5.83zM11 16h2v2h-2zm0-6h2v4h-2z" />
|
||||
</svg>
|
||||
),
|
||||
label: "Issues",
|
||||
color: "bg-[#FF9900] hover:bg-[#FF9900]/90",
|
||||
label: 'Issues',
|
||||
color: 'bg-[#FF9900] hover:bg-[#FF9900]/90',
|
||||
rotation: 3,
|
||||
},
|
||||
];
|
||||
@@ -66,7 +64,7 @@ export const ChatIcons: React.FC<ChatIconsProps> = ({ className }) => {
|
||||
if (hoveredIndex === null) return 0;
|
||||
const spread = 16;
|
||||
const centerOffset = hoveredIndex * -spread;
|
||||
return (index * spread) + centerOffset;
|
||||
return index * spread + centerOffset;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -81,8 +79,8 @@ export const ChatIcons: React.FC<ChatIconsProps> = ({ className }) => {
|
||||
}}
|
||||
transition={{
|
||||
duration: 0.2,
|
||||
ease: "easeOut",
|
||||
scale: { duration: 0.1 }
|
||||
ease: 'easeOut',
|
||||
scale: { duration: 0.1 },
|
||||
}}
|
||||
onHoverStart={() => setHoveredIndex(index)}
|
||||
onHoverEnd={() => setHoveredIndex(null)}
|
||||
@@ -95,15 +93,13 @@ export const ChatIcons: React.FC<ChatIconsProps> = ({ className }) => {
|
||||
flex h-12 w-12 items-center justify-center rounded-xl
|
||||
transition-all duration-200 shadow-sm
|
||||
${tool.color}
|
||||
${hoveredIndex !== null && hoveredIndex !== index ? "opacity-50" : ""}
|
||||
${hoveredIndex !== null && hoveredIndex !== index ? 'opacity-50' : ''}
|
||||
`}
|
||||
>
|
||||
{tool.icon}
|
||||
</motion.button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent sideOffset={5}>
|
||||
{tool.label}
|
||||
</TooltipContent>
|
||||
<TooltipContent sideOffset={5}>{tool.label}</TooltipContent>
|
||||
</Tooltip>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface ChatInputProps {
|
||||
@@ -11,7 +12,7 @@ interface ChatInputProps {
|
||||
export const ChatInput: React.FC<ChatInputProps> = ({
|
||||
handleSubmit,
|
||||
isLoading = false,
|
||||
onStop,
|
||||
onStop: _onStop,
|
||||
initialValue = '',
|
||||
}) => {
|
||||
const [input, setInput] = useState(initialValue);
|
||||
@@ -36,7 +37,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.attributeName === 'class') {
|
||||
setKey(prev => prev + 1); // Force textarea to re-render
|
||||
setKey((prev) => prev + 1); // Force textarea to re-render
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -73,9 +74,9 @@ export const ChatInput: React.FC<ChatInputProps> = ({
|
||||
initial={{ opacity: 0, y: 20, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
type: 'spring',
|
||||
stiffness: 300,
|
||||
damping: 30
|
||||
damping: 30,
|
||||
}}
|
||||
>
|
||||
<form onSubmit={handleFormSubmit} className="relative px-4 py-3">
|
||||
@@ -103,7 +104,8 @@ export const ChatInput: React.FC<ChatInputProps> = ({
|
||||
className={`
|
||||
p-2 rounded-lg w-10 h-10 flex items-center justify-center
|
||||
transition-colors duration-200
|
||||
${input.trim()
|
||||
${
|
||||
input.trim()
|
||||
? 'hover:bg-zinc-800 active:bg-zinc-700 dark:hover:bg-zinc-100 dark:active:bg-zinc-200'
|
||||
: 'cursor-not-allowed'
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { ChatIcons } from './ChatIcons';
|
||||
|
||||
interface FloatingChatProps {
|
||||
@@ -11,9 +12,12 @@ export const FloatingChat: React.FC<FloatingChatProps> = ({ children }) => {
|
||||
|
||||
// Create a debounced version of setIsVisible
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
const timer = setTimeout(
|
||||
() => {
|
||||
setIsVisible(isHovering);
|
||||
}, isHovering ? 0 : 200);
|
||||
},
|
||||
isHovering ? 0 : 200
|
||||
);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [isHovering]);
|
||||
@@ -42,13 +46,10 @@ export const FloatingChat: React.FC<FloatingChatProps> = ({ children }) => {
|
||||
<div
|
||||
className={`
|
||||
transform transition-all duration-300 ease-out
|
||||
${isVisible
|
||||
? 'translate-y-0 opacity-100'
|
||||
: '-translate-y-4 opacity-0'
|
||||
}
|
||||
${isVisible ? 'translate-y-0 opacity-100' : '-translate-y-4 opacity-0'}
|
||||
`}
|
||||
style={{
|
||||
paddingBottom: "env(safe-area-inset-bottom, 16px)"
|
||||
paddingBottom: 'env(safe-area-inset-bottom, 16px)',
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-center w-full px-4 pb-4">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, ReactElement } from 'react';
|
||||
|
||||
import { FilterOption } from './types';
|
||||
|
||||
interface FloatingFiltersProps {
|
||||
@@ -6,7 +7,7 @@ interface FloatingFiltersProps {
|
||||
}
|
||||
|
||||
const getBarColor = (filters: FilterOption[], isDarkMode: boolean): string => {
|
||||
const activeFilter = filters.find(f => f.isActive);
|
||||
const activeFilter = filters.find((f) => f.isActive);
|
||||
switch (activeFilter?.id) {
|
||||
case 'tasks':
|
||||
return '#05C168';
|
||||
@@ -21,13 +22,15 @@ const getBarColor = (filters: FilterOption[], isDarkMode: boolean): string => {
|
||||
}
|
||||
};
|
||||
|
||||
export function FloatingFilters({ children }: FloatingFiltersProps) {
|
||||
export function FloatingFilters({ children }: FloatingFiltersProps): ReactElement {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [activeFilters, setActiveFilters] = useState<FilterOption[]>([]);
|
||||
const [isDarkMode, setIsDarkMode] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const filterPills = React.Children.toArray(children)[0] as React.ReactElement;
|
||||
const filterPills = React.Children.toArray(children)[0] as React.ReactElement<{
|
||||
filters?: FilterOption[];
|
||||
}>;
|
||||
if (filterPills?.props?.filters) {
|
||||
setActiveFilters(filterPills.props.filters);
|
||||
}
|
||||
@@ -47,7 +50,7 @@ export function FloatingFilters({ children }: FloatingFiltersProps) {
|
||||
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
attributeFilter: ['class'],
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
@@ -83,7 +86,8 @@ export function FloatingFilters({ children }: FloatingFiltersProps) {
|
||||
<div
|
||||
className={`
|
||||
transform transition-all duration-300 ease-out w-full
|
||||
${isVisible
|
||||
${
|
||||
isVisible
|
||||
? 'translate-y-0 opacity-100 scale-y-100 origin-top'
|
||||
: 'translate-y-[calc(-100%+6px)] opacity-0 scale-y-95 origin-top'
|
||||
}
|
||||
|
||||
@@ -1,37 +1,73 @@
|
||||
import React from 'react';
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
export const ChartLineIcon = () => (
|
||||
export const ChartLineIcon = (): ReactElement => (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 16L8 11L13 16L21 8" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path
|
||||
d="M3 16L8 11L13 16L21 8"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const ChartBarIcon = () => (
|
||||
export const ChartBarIcon = (): ReactElement => (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 20V4H16V20H20ZM14 20V10H10V20H14ZM8 20V14H4V20H8Z" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path
|
||||
d="M20 20V4H16V20H20ZM14 20V10H10V20H14ZM8 20V14H4V20H8Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const PieChartIcon = () => (
|
||||
export const PieChartIcon = (): ReactElement => (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2V12H22C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2Z" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path
|
||||
d="M12 2V12H22C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const ListIcon = () => (
|
||||
export const ListIcon = (): ReactElement => (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 6H21M8 12H21M8 18H21M3 6H3.01M3 12H3.01M3 18H3.01" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path
|
||||
d="M8 6H21M8 12H21M8 18H21M3 6H3.01M3 12H3.01M3 18H3.01"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const StarIcon = () => (
|
||||
export const StarIcon = (): ReactElement => (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path
|
||||
d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const TrendingUpIcon = () => (
|
||||
export const TrendingUpIcon = (): ReactElement => (
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M23 6L13.5 15.5L8.5 10.5L1 18M23 6H17M23 6V12" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path
|
||||
d="M23 6L13.5 15.5L8.5 10.5L1 18M23 6H17M23 6V12"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
396
ui-v2/src/components/icons/Goose.tsx
Normal file
396
ui-v2/src/components/icons/Goose.tsx
Normal file
@@ -0,0 +1,396 @@
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
interface GooseProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Goose({ className = '' }: GooseProps): ReactElement {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g clipPath="url(#clip0_2096_5193)">
|
||||
<path
|
||||
d="M20.9093 19.3861L19.5185 18.2413C18.7624 17.619 18.1189 16.8713 17.6157 16.0313C16.9205 14.8706 15.9599 13.8912 14.8133 13.1735L14.2533 12.8475C14.0614 12.7141 13.9276 12.5062 13.9086 12.2716C13.8963 12.1204 13.9326 11.9852 14.0171 11.8662C14.3087 11.4553 15.896 9.74698 16.1722 9.51845C16.528 9.22442 16.9243 8.97987 17.2921 8.69986C17.3443 8.66 17.3968 8.62035 17.4485 8.57989C17.4503 8.57808 17.4529 8.57668 17.4545 8.57508C17.5725 8.48195 17.6838 8.383 17.7724 8.26563C18.2036 7.76631 18.195 7.3443 18.195 7.3443C18.195 7.3443 18.1954 7.3439 18.1956 7.3437C18.1497 7.23133 17.9847 6.88163 17.6492 6.71759C17.9458 6.71178 18.2805 6.82294 18.4323 6.97156C18.6148 6.68534 18.7328 6.49967 18.9162 6.18762C18.9599 6.11352 18.9831 5.97652 18.8996 5.89981C18.8996 5.89981 18.8992 5.89981 18.8988 5.89981C18.8988 5.89981 18.8988 5.8994 18.8988 5.899C18.8972 5.8974 18.8952 5.8962 18.8936 5.8946C18.892 5.893 18.891 5.89119 18.8892 5.88939C18.8892 5.88939 18.8888 5.88939 18.8884 5.88939C18.8884 5.88939 18.8884 5.88899 18.8884 5.88859C18.885 5.88518 18.8812 5.88258 18.8776 5.87938C18.8754 5.87717 18.8736 5.87457 18.8716 5.87217C18.8692 5.87016 18.8665 5.86836 18.8643 5.86616C18.8609 5.86275 18.8587 5.85855 18.8551 5.85534C18.8551 5.85534 18.8545 5.85514 18.8543 5.85534C18.8543 5.85534 18.8543 5.85494 18.8543 5.85454C18.8527 5.85294 18.8507 5.85174 18.8491 5.85013C18.8475 5.84853 18.8463 5.84653 18.8447 5.84493C18.8447 5.84493 18.8441 5.84473 18.8439 5.84493C18.8439 5.84493 18.8439 5.84453 18.8439 5.84413C18.7672 5.7606 18.6302 5.78384 18.5561 5.8275C18.1503 6.06625 17.7555 6.32322 17.3996 6.54855C17.3996 6.54855 16.9778 6.53973 16.4783 6.97116C16.3607 7.05989 16.2618 7.17125 16.1688 7.28902C16.167 7.29082 16.1654 7.29322 16.164 7.29503C16.1234 7.3465 16.0837 7.39898 16.0441 7.45145C15.7639 7.81939 15.5195 8.21556 15.2255 8.57128C14.9971 8.84768 13.2887 10.4348 12.8777 10.7264C12.7587 10.8109 12.6237 10.8474 12.4723 10.835C12.2379 10.8161 12.0298 10.6821 11.8965 10.4903L11.5704 9.93024C10.8527 8.78318 9.87332 7.82299 8.71264 7.12778C7.87262 6.62466 7.12514 5.98092 6.50264 5.22503L5.35778 3.83421C5.3013 3.76571 5.19314 3.77693 5.15268 3.85585C5.02249 4.10941 4.77393 4.64479 4.58346 5.36483C4.57885 5.38186 4.58286 5.39988 4.59407 5.4135C4.83082 5.69952 5.37901 6.32983 6.03196 6.863C6.07742 6.90005 6.04017 6.97336 5.98369 6.95774C5.42047 6.80432 4.87288 6.55796 4.46308 6.34805C4.42964 6.33103 4.38918 6.35226 4.38437 6.38951C4.32068 6.89985 4.30425 7.46027 4.37155 8.05112C4.37355 8.07035 4.38577 8.08697 4.4036 8.09479C4.87088 8.29808 5.61816 8.59311 6.40269 8.78078C6.45958 8.7944 6.45777 8.87632 6.40029 8.88733C5.78941 9.0023 5.14968 9.02794 4.62973 9.02113C4.59327 9.02073 4.56643 9.05518 4.57625 9.09023C4.6806 9.45896 4.822 9.8339 5.00847 10.2115C5.08559 10.3811 5.16951 10.5475 5.25944 10.7104C5.27486 10.7382 5.3047 10.7548 5.33655 10.7534C5.76577 10.7324 6.28452 10.6871 6.80608 10.595C6.89501 10.5794 6.94268 10.6964 6.86757 10.7466C6.51345 10.9834 6.13571 11.1873 5.7844 11.3551C5.73733 11.3777 5.72211 11.4378 5.75315 11.4797C5.96186 11.7625 6.19139 12.0301 6.44075 12.2794C6.44075 12.2794 7.66853 13.5441 7.70198 13.6432C8.41841 12.9096 9.59612 12.0964 10.8966 11.3864C9.15488 12.8036 8.18387 13.8499 7.69517 14.4444L7.35447 14.9225C7.17742 15.1708 7.02379 15.4346 6.89541 15.7112C6.46579 16.6356 5.75756 18.5051 5.75756 18.5051C5.70328 18.6515 5.74754 18.7959 5.84168 18.89C5.84388 18.8922 5.84609 18.8944 5.84849 18.8964C5.85069 18.8986 5.8527 18.901 5.8549 18.9032C5.94924 18.9976 6.09345 19.0416 6.23986 18.9874C6.23986 18.9874 8.10897 18.2791 9.03371 17.8495C9.31031 17.7211 9.57429 17.5673 9.82245 17.3905L10.349 17.0153C10.6278 16.8166 11.0096 16.8483 11.2517 17.0904L12.4655 18.3042C12.7148 18.5535 12.9824 18.7831 13.2652 18.9918C13.3073 19.0226 13.3672 19.0076 13.3898 18.9605C13.5579 18.6094 13.7618 18.2313 13.9983 17.8774C14.0486 17.8022 14.1657 17.8501 14.1499 17.9388C14.0576 18.4606 14.0127 18.9794 13.9915 19.4084C13.9899 19.44 14.0067 19.4701 14.0345 19.4855C14.1972 19.5756 14.3636 19.6595 14.5335 19.7364C14.911 19.9229 15.2862 20.0645 15.6547 20.1687C15.6897 20.1785 15.7242 20.1516 15.7238 20.1152C15.7168 19.595 15.7424 18.9553 15.8576 18.3446C15.8684 18.2869 15.9503 18.2851 15.9641 18.3422C16.1516 19.127 16.4466 19.8742 16.6501 20.3413C16.6579 20.3591 16.6744 20.3712 16.6938 20.3734C17.2847 20.4407 17.8451 20.4242 18.3554 20.3606C18.3929 20.3559 18.4141 20.3155 18.3969 20.2818C18.187 19.872 17.9406 19.3241 17.7872 18.7612C17.7718 18.7046 17.8449 18.6675 17.8819 18.713C18.4151 19.3659 19.0454 19.9141 19.3314 20.1508C19.345 20.1621 19.3633 20.1659 19.3801 20.1615C20.1003 19.9712 20.6357 19.7226 20.8891 19.5922C20.968 19.5518 20.9792 19.4436 20.9107 19.3871L20.9093 19.3861Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2096_5193">
|
||||
<rect width="24" height="24" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
interface RainProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Rain({ className = '' }: RainProps): ReactElement {
|
||||
return (
|
||||
<svg
|
||||
width="103"
|
||||
height="103"
|
||||
viewBox="0 0 103 103"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g id="wind">
|
||||
<g id="1" className="animate-[wind_2s_linear_infinite]">
|
||||
<path
|
||||
id="Vector 42_2"
|
||||
d="M66 33L62 37"
|
||||
stroke="url(#paint10_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 41_2"
|
||||
d="M70 43L68 45"
|
||||
stroke="url(#paint11_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 51_2"
|
||||
d="M60 34L59 35"
|
||||
stroke="url(#paint12_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 43_2"
|
||||
d="M56 47L52 51"
|
||||
stroke="url(#paint13_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 46_2"
|
||||
d="M98 1L94 5"
|
||||
stroke="url(#paint14_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 47_2"
|
||||
d="M102 11L100 13"
|
||||
stroke="url(#paint15_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 48_2"
|
||||
d="M88 15L84 19"
|
||||
stroke="url(#paint16_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 45_2"
|
||||
d="M76 26L72 30"
|
||||
stroke="url(#paint17_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 49_2"
|
||||
d="M87 28L83 32"
|
||||
stroke="url(#paint18_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 50_2"
|
||||
d="M76 34L74 36"
|
||||
stroke="url(#paint19_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</g>
|
||||
<g id="2" className="animate-[wind_2s_linear_1s_infinite]">
|
||||
<path
|
||||
id="Vector 42"
|
||||
d="M66 33L62 37"
|
||||
stroke="url(#paint0_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 41"
|
||||
d="M70 43L68 45"
|
||||
stroke="url(#paint1_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 51"
|
||||
d="M60 34L59 35"
|
||||
stroke="url(#paint2_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 43"
|
||||
d="M56 47L52 51"
|
||||
stroke="url(#paint3_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 46"
|
||||
d="M98 1L94 5"
|
||||
stroke="url(#paint4_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 47"
|
||||
d="M102 11L100 13"
|
||||
stroke="url(#paint5_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 48"
|
||||
d="M88 15L84 19"
|
||||
stroke="url(#paint6_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 45"
|
||||
d="M76 26L72 30"
|
||||
stroke="url(#paint7_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 49"
|
||||
d="M87 28L83 32"
|
||||
stroke="url(#paint8_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
id="Vector 50"
|
||||
d="M76 34L74 36"
|
||||
stroke="url(#paint9_linear_2096_5587)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_2096_5587"
|
||||
x1="64"
|
||||
y1="32.9437"
|
||||
x2="64"
|
||||
y2="37.0563"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_2096_5587"
|
||||
x1="69"
|
||||
y1="42.9719"
|
||||
x2="69"
|
||||
y2="45.0281"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_2096_5587"
|
||||
x1="59.5"
|
||||
y1="33.9859"
|
||||
x2="59.5"
|
||||
y2="35.0141"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_2096_5587"
|
||||
x1="54"
|
||||
y1="46.9437"
|
||||
x2="54"
|
||||
y2="51.0563"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint4_linear_2096_5587"
|
||||
x1="96"
|
||||
y1="0.943728"
|
||||
x2="96"
|
||||
y2="5.05625"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint5_linear_2096_5587"
|
||||
x1="101"
|
||||
y1="10.9719"
|
||||
x2="101"
|
||||
y2="13.0281"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint6_linear_2096_5587"
|
||||
x1="86"
|
||||
y1="14.9437"
|
||||
x2="86"
|
||||
y2="19.0563"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint7_linear_2096_5587"
|
||||
x1="74"
|
||||
y1="25.9437"
|
||||
x2="74"
|
||||
y2="30.0563"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint8_linear_2096_5587"
|
||||
x1="85"
|
||||
y1="27.9437"
|
||||
x2="85"
|
||||
y2="32.0563"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint9_linear_2096_5587"
|
||||
x1="75"
|
||||
y1="33.9719"
|
||||
x2="75"
|
||||
y2="36.0281"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint10_linear_2096_5587"
|
||||
x1="64"
|
||||
y1="32.9437"
|
||||
x2="64"
|
||||
y2="37.0563"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint11_linear_2096_5587"
|
||||
x1="69"
|
||||
y1="42.9719"
|
||||
x2="69"
|
||||
y2="45.0281"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint12_linear_2096_5587"
|
||||
x1="59.5"
|
||||
y1="33.9859"
|
||||
x2="59.5"
|
||||
y2="35.0141"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint13_linear_2096_5587"
|
||||
x1="54"
|
||||
y1="46.9437"
|
||||
x2="54"
|
||||
y2="51.0563"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint14_linear_2096_5587"
|
||||
x1="96"
|
||||
y1="0.943728"
|
||||
x2="96"
|
||||
y2="5.05625"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint15_linear_2096_5587"
|
||||
x1="101"
|
||||
y1="10.9719"
|
||||
x2="101"
|
||||
y2="13.0281"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint16_linear_2096_5587"
|
||||
x1="86"
|
||||
y1="14.9437"
|
||||
x2="86"
|
||||
y2="19.0563"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint17_linear_2096_5587"
|
||||
x1="74"
|
||||
y1="25.9437"
|
||||
x2="74"
|
||||
y2="30.0563"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint18_linear_2096_5587"
|
||||
x1="85"
|
||||
y1="27.9437"
|
||||
x2="85"
|
||||
y2="32.0563"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint19_linear_2096_5587"
|
||||
x1="75"
|
||||
y1="33.9719"
|
||||
x2="75"
|
||||
y2="36.0281"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EC5D2A" />
|
||||
<stop offset="1" stopColor="#57B9AF" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +1,22 @@
|
||||
import React from 'react';
|
||||
import { ReactElement, ReactNode } from 'react';
|
||||
|
||||
import { BarChart, Bar, LineChart, Line, CartesianGrid, XAxis } from 'recharts';
|
||||
|
||||
import { useTimelineStyles } from '../../hooks/useTimelineStyles.ts';
|
||||
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/components/ui/chart";
|
||||
import { BarChart, Bar, LineChart, Line, CartesianGrid, XAxis } from 'recharts';
|
||||
} from '@/components/ui/chart';
|
||||
|
||||
interface ChartTileProps {
|
||||
title: string;
|
||||
value: string;
|
||||
trend?: string;
|
||||
data: number[];
|
||||
icon: React.ReactNode;
|
||||
icon: ReactNode;
|
||||
variant?: 'line' | 'bar';
|
||||
date?: Date;
|
||||
}
|
||||
@@ -25,22 +28,22 @@ export default function ChartTile({
|
||||
data,
|
||||
icon,
|
||||
variant = 'line',
|
||||
date
|
||||
}: ChartTileProps) {
|
||||
date,
|
||||
}: ChartTileProps): ReactElement {
|
||||
const { contentCardStyle } = useTimelineStyles(date);
|
||||
|
||||
// Convert the data array to the format expected by recharts
|
||||
const chartData = data.map((value, index) => ({
|
||||
value,
|
||||
point: `P${index + 1}`
|
||||
point: `P${index + 1}`,
|
||||
}));
|
||||
|
||||
// Chart configuration with proper color variables
|
||||
const chartConfig = {
|
||||
value: {
|
||||
label: title,
|
||||
color: variant === 'line' ? 'var(--chart-2)' : 'var(--chart-1)'
|
||||
}
|
||||
color: variant === 'line' ? 'var(--chart-2)' : 'var(--chart-1)',
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
|
||||
return (
|
||||
@@ -59,15 +62,15 @@ export default function ChartTile({
|
||||
>
|
||||
{/* Header section with icon */}
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="w-6 h-6 text-text-default dark:text-white">
|
||||
{icon}
|
||||
</div>
|
||||
<div className="w-6 h-6 text-text-default dark:text-white">{icon}</div>
|
||||
|
||||
<div>
|
||||
<div className="text-text-muted dark:text-white/60 text-sm mb-1">{title}</div>
|
||||
<div className="text-text-default dark:text-white text-2xl font-semibold">
|
||||
{value}
|
||||
{trend && <span className="ml-1 text-sm text-text-muted dark:text-white/60">{trend}</span>}
|
||||
{trend && (
|
||||
<span className="ml-1 text-sm text-text-muted dark:text-white/60">{trend}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -96,9 +99,7 @@ export default function ChartTile({
|
||||
/>
|
||||
<ChartTooltip
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
className="border-border/50 bg-background-default text-text-default min-w-[180px] [&_.flex.flex-1]:gap-4 [&_.flex.flex-1>span]:whitespace-nowrap"
|
||||
/>
|
||||
<ChartTooltipContent className="border-border/50 bg-background-default text-text-default min-w-[180px] [&_.flex.flex-1]:gap-4 [&_.flex.flex-1>span]:whitespace-nowrap" />
|
||||
}
|
||||
/>
|
||||
<Line
|
||||
@@ -135,12 +136,7 @@ export default function ChartTile({
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="value"
|
||||
fill="var(--chart-1)"
|
||||
radius={4}
|
||||
maxBarSize={32}
|
||||
/>
|
||||
<Bar dataKey="value" fill="var(--chart-1)" radius={4} maxBarSize={32} />
|
||||
</BarChart>
|
||||
)}
|
||||
</ChartContainer>
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import { useState, useEffect, ReactElement } from 'react';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTimelineStyles } from '../../hooks/useTimelineStyles.ts';
|
||||
import waveBg from '../../assets/backgrounds/wave-bg.png';
|
||||
|
||||
// Use string path for the background image
|
||||
const waveBgUrl = '/src/assets/backgrounds/wave-bg.png';
|
||||
|
||||
interface ClockCardProps {
|
||||
date?: Date;
|
||||
}
|
||||
|
||||
export default function ClockTile({ date }: ClockCardProps) {
|
||||
export default function ClockTile({ date }: ClockCardProps): ReactElement | null {
|
||||
const { contentCardStyle, isPastDate } = useTimelineStyles(date);
|
||||
const [currentTime, setCurrentTime] = useState(new Date());
|
||||
|
||||
// Don't render for past dates
|
||||
if (isPastDate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update time every second for current day
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
@@ -25,6 +22,11 @@ export default function ClockTile({ date }: ClockCardProps) {
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
// Don't render for past dates
|
||||
if (isPastDate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Format hours (12-hour format)
|
||||
const hours = currentTime.getHours() % 12 || 12;
|
||||
const minutes = currentTime.getMinutes().toString().padStart(2, '0');
|
||||
@@ -51,15 +53,13 @@ export default function ClockTile({ date }: ClockCardProps) {
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center bg-no-repeat transition-opacity duration-500"
|
||||
style={{
|
||||
backgroundImage: `url(${waveBg})`,
|
||||
opacity: 0.8
|
||||
backgroundImage: `url(${waveBgUrl})`,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Gradient Overlay */}
|
||||
<div
|
||||
className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
|
||||
|
||||
{/* Time Display */}
|
||||
<div className="flex flex-col items-start mt-auto relative z-10">
|
||||
@@ -67,13 +67,9 @@ export default function ClockTile({ date }: ClockCardProps) {
|
||||
<span className="font-['Cash_Sans'] text-[48px] font-light text-white leading-none">
|
||||
{hours}:{minutes}
|
||||
</span>
|
||||
<span className="ml-1 font-['Cash_Sans'] text-xl font-light text-white">
|
||||
{period}
|
||||
</span>
|
||||
<span className="ml-1 font-['Cash_Sans'] text-xl font-light text-white">{period}</span>
|
||||
</div>
|
||||
<span className="text-sm text-white/80 mt-1">
|
||||
{dayName}
|
||||
</span>
|
||||
<span className="text-sm text-white/80 mt-1">{dayName}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import { ReactElement, ReactNode } from 'react';
|
||||
|
||||
import { useTimelineStyles } from '../../hooks/useTimelineStyles.ts';
|
||||
|
||||
interface HighlightTileProps {
|
||||
title: string;
|
||||
value: string;
|
||||
icon: React.ReactNode;
|
||||
icon: ReactNode;
|
||||
subtitle?: string;
|
||||
date?: Date;
|
||||
accentColor?: string;
|
||||
@@ -16,8 +17,8 @@ export default function HighlightTile({
|
||||
icon,
|
||||
subtitle,
|
||||
date,
|
||||
accentColor = '#00CAF7'
|
||||
}: HighlightTileProps) {
|
||||
accentColor = '#00CAF7',
|
||||
}: HighlightTileProps): ReactElement {
|
||||
const { contentCardStyle } = useTimelineStyles(date);
|
||||
|
||||
return (
|
||||
@@ -36,14 +37,14 @@ export default function HighlightTile({
|
||||
{/* Background accent */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-5"
|
||||
style={{ background: `radial-gradient(circle at top right, ${accentColor}, transparent 70%)` }}
|
||||
style={{
|
||||
background: `radial-gradient(circle at top right, ${accentColor}, transparent 70%)`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-4 h-full flex flex-col justify-between relative z-10">
|
||||
<div className="w-6 h-6 text-text-default dark:text-white">
|
||||
{icon}
|
||||
</div>
|
||||
<div className="w-6 h-6 text-text-default dark:text-white">{icon}</div>
|
||||
|
||||
<div>
|
||||
<div className="text-gray-600 dark:text-white/60 text-sm mb-1">{title}</div>
|
||||
@@ -54,9 +55,7 @@ export default function HighlightTile({
|
||||
{value}
|
||||
</div>
|
||||
{subtitle && (
|
||||
<div className="text-gray-500 dark:text-white/60 text-sm mt-1">
|
||||
{subtitle}
|
||||
</div>
|
||||
<div className="text-gray-500 dark:text-white/60 text-sm mt-1">{subtitle}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { ReactElement, ReactNode } from 'react';
|
||||
|
||||
import { useTimelineStyles } from '../../hooks/useTimelineStyles.ts';
|
||||
|
||||
interface ListItem {
|
||||
@@ -9,17 +10,12 @@ interface ListItem {
|
||||
|
||||
interface ListTileProps {
|
||||
title: string;
|
||||
icon: React.ReactNode;
|
||||
icon: ReactNode;
|
||||
items: ListItem[];
|
||||
date?: Date;
|
||||
}
|
||||
|
||||
export default function ListTile({
|
||||
title,
|
||||
icon,
|
||||
items,
|
||||
date
|
||||
}: ListTileProps) {
|
||||
export default function ListTile({ title, icon, items, date }: ListTileProps): ReactElement {
|
||||
const { contentCardStyle } = useTimelineStyles(date);
|
||||
|
||||
return (
|
||||
@@ -37,22 +33,15 @@ export default function ListTile({
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="p-4">
|
||||
<div className="w-6 h-6 mb-4 text-text-default dark:text-white">
|
||||
{icon}
|
||||
</div>
|
||||
<div className="text-gray-600 dark:text-white/60 text-sm mb-4">
|
||||
{title}
|
||||
</div>
|
||||
<div className="w-6 h-6 mb-4 text-text-default dark:text-white">{icon}</div>
|
||||
<div className="text-gray-600 dark:text-white/60 text-sm mb-4">{title}</div>
|
||||
</div>
|
||||
|
||||
{/* List */}
|
||||
<div className="flex-1 overflow-y-auto px-4 pb-4">
|
||||
<div className="space-y-3">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between"
|
||||
>
|
||||
<div key={index} className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
@@ -60,9 +49,7 @@ export default function ListTile({
|
||||
}`}
|
||||
style={item.color ? { backgroundColor: item.color } : {}}
|
||||
/>
|
||||
<span className="text-sm text-gray-600 dark:text-white/80">
|
||||
{item.text}
|
||||
</span>
|
||||
<span className="text-sm text-gray-600 dark:text-white/80">{item.text}</span>
|
||||
</div>
|
||||
{item.value && (
|
||||
<span
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTimelineStyles } from '../../hooks/useTimelineStyles';
|
||||
import { useState, ReactElement, ReactNode } from 'react';
|
||||
|
||||
import { PieChart, Pie, Cell, Sector } from 'recharts';
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { useTimelineStyles } from '../../hooks/useTimelineStyles';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface PieChartSegment {
|
||||
value: number;
|
||||
@@ -11,22 +14,33 @@ interface PieChartSegment {
|
||||
|
||||
interface PieChartTileProps {
|
||||
title: string;
|
||||
icon: React.ReactNode;
|
||||
icon: ReactNode;
|
||||
segments: PieChartSegment[];
|
||||
date?: Date;
|
||||
}
|
||||
|
||||
interface LabelProps {
|
||||
cx: number;
|
||||
cy: number;
|
||||
midAngle: number;
|
||||
innerRadius: number;
|
||||
outerRadius: number;
|
||||
percent: number;
|
||||
payload: { name: string };
|
||||
fill: string;
|
||||
}
|
||||
|
||||
// Custom label renderer with connecting lines
|
||||
const renderCustomizedLabel = ({
|
||||
cx,
|
||||
cy,
|
||||
midAngle,
|
||||
innerRadius,
|
||||
innerRadius: _innerRadius,
|
||||
outerRadius,
|
||||
percent,
|
||||
payload,
|
||||
fill,
|
||||
}: any) => {
|
||||
}: LabelProps) => {
|
||||
const RADIAN = Math.PI / 180;
|
||||
const sin = Math.sin(-RADIAN * midAngle);
|
||||
const cos = Math.cos(-RADIAN * midAngle);
|
||||
@@ -42,7 +56,7 @@ const renderCustomizedLabel = ({
|
||||
const ey = my;
|
||||
|
||||
// Text anchor based on which side of the pie we're on
|
||||
const textAnchor = cos >= 0 ? "start" : "end";
|
||||
const textAnchor = cos >= 0 ? 'start' : 'end';
|
||||
|
||||
// Calculate percentage
|
||||
const value = (percent * 100).toFixed(0);
|
||||
@@ -52,7 +66,7 @@ const renderCustomizedLabel = ({
|
||||
const yOffset = isTopHalf ? -2 : 2;
|
||||
|
||||
// Force specific adjustments for "In Progress" label if needed
|
||||
const isInProgress = payload.name === "In Progress";
|
||||
const isInProgress = payload.name === 'In Progress';
|
||||
const adjustedEx = isInProgress ? ex - 5 : ex;
|
||||
|
||||
return (
|
||||
@@ -82,8 +96,18 @@ const renderCustomizedLabel = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface ActiveShapeProps {
|
||||
cx: number;
|
||||
cy: number;
|
||||
innerRadius: number;
|
||||
outerRadius: number;
|
||||
startAngle: number;
|
||||
endAngle: number;
|
||||
fill: string;
|
||||
}
|
||||
|
||||
// Active shape renderer for hover effect
|
||||
const renderActiveShape = (props: any) => {
|
||||
const renderActiveShape = (props: ActiveShapeProps) => {
|
||||
const { cx, cy, innerRadius, outerRadius, startAngle, endAngle, fill } = props;
|
||||
|
||||
return (
|
||||
@@ -104,8 +128,8 @@ export default function PieChartTile({
|
||||
title,
|
||||
icon,
|
||||
segments,
|
||||
date
|
||||
}: PieChartTileProps) {
|
||||
date,
|
||||
}: PieChartTileProps): ReactElement {
|
||||
const { contentCardStyle } = useTimelineStyles(date);
|
||||
const [activeIndex, setActiveIndex] = useState<number>(0);
|
||||
|
||||
@@ -113,10 +137,10 @@ export default function PieChartTile({
|
||||
const chartData = segments.map((segment, index) => ({
|
||||
name: segment.label,
|
||||
value: segment.value,
|
||||
chartColor: `var(--chart-${index + 1})` // Use chart-1, chart-2, chart-3, etc.
|
||||
chartColor: `var(--chart-${index + 1})`, // Use chart-1, chart-2, chart-3, etc.
|
||||
}));
|
||||
|
||||
const onPieEnter = (_: any, index: number) => {
|
||||
const onPieEnter = (_: unknown, index: number): void => {
|
||||
setActiveIndex(index);
|
||||
};
|
||||
|
||||
@@ -136,41 +160,34 @@ export default function PieChartTile({
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="p-4">
|
||||
<div className="w-6 h-6 mb-4 text-text-default dark:text-white">
|
||||
{icon}
|
||||
</div>
|
||||
<div className="text-text-muted dark:text-white/60 text-sm">
|
||||
{title}
|
||||
</div>
|
||||
<div className="w-6 h-6 mb-4 text-text-default dark:text-white">{icon}</div>
|
||||
<div className="text-text-muted dark:text-white/60 text-sm">{title}</div>
|
||||
</div>
|
||||
|
||||
{/* Pie Chart */}
|
||||
<div className="flex-1 flex items-center justify-center p-4">
|
||||
<div
|
||||
className={cn(
|
||||
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground",
|
||||
'[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground',
|
||||
"[&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50",
|
||||
"[&_.recharts-curve.recharts-tooltip-cursor]:stroke-border",
|
||||
'[&_.recharts-curve.recharts-tooltip-cursor]:stroke-border',
|
||||
"[&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border",
|
||||
"[&_.recharts-radial-bar-background-sector]:fill-muted",
|
||||
"[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted",
|
||||
'[&_.recharts-radial-bar-background-sector]:fill-muted',
|
||||
'[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted',
|
||||
"[&_.recharts-reference-line_[stroke='#ccc']]:stroke-border",
|
||||
"flex justify-center text-xs",
|
||||
'flex justify-center text-xs',
|
||||
"[&_.recharts-dot[stroke='#fff']]:stroke-transparent",
|
||||
"[&_.recharts-layer]:outline-hidden",
|
||||
"[&_.recharts-sector]:outline-hidden",
|
||||
'[&_.recharts-layer]:outline-hidden',
|
||||
'[&_.recharts-sector]:outline-hidden',
|
||||
"[&_.recharts-sector[stroke='#fff']]:stroke-transparent",
|
||||
"[&_.recharts-surface]:outline-hidden"
|
||||
'[&_.recharts-surface]:outline-hidden'
|
||||
)}
|
||||
>
|
||||
<PieChart
|
||||
width={288}
|
||||
height={162}
|
||||
margin={{ top: 30, right: 40, bottom: 10, left: 40 }}
|
||||
>
|
||||
<PieChart width={288} height={162} margin={{ top: 30, right: 40, bottom: 10, left: 40 }}>
|
||||
<Pie
|
||||
activeIndex={activeIndex}
|
||||
activeShape={renderActiveShape}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
activeShape={renderActiveShape as any}
|
||||
data={chartData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import * as React from "react";
|
||||
import * as RechartsPrimitive from "recharts";
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import * as RechartsPrimitive from 'recharts';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||
const THEMES = { light: "", dark: ".dark" } as const;
|
||||
const THEMES = { light: '', dark: '.dark' } as const;
|
||||
|
||||
export type ChartConfig = {
|
||||
[k in string]: {
|
||||
@@ -28,7 +29,7 @@ function useChart() {
|
||||
const context = React.useContext(ChartContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useChart must be used within a <ChartContainer />");
|
||||
throw new Error('useChart must be used within a <ChartContainer />');
|
||||
}
|
||||
|
||||
return context;
|
||||
@@ -40,12 +41,12 @@ function ChartContainer({
|
||||
children,
|
||||
config,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & {
|
||||
}: React.ComponentProps<'div'> & {
|
||||
config: ChartConfig;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const uniqueId = React.useId();
|
||||
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
|
||||
const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`;
|
||||
|
||||
return (
|
||||
<ChartContext.Provider value={{ config }}>
|
||||
@@ -66,9 +67,7 @@ function ChartContainer({
|
||||
}
|
||||
|
||||
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
||||
const colorConfig = Object.entries(config).filter(
|
||||
([, config]) => config.theme || config.color
|
||||
);
|
||||
const colorConfig = Object.entries(config).filter(([, config]) => config.theme || config.color);
|
||||
|
||||
if (!colorConfig.length) {
|
||||
return null;
|
||||
@@ -83,16 +82,14 @@ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
||||
${prefix} [data-chart=${id}] {
|
||||
${colorConfig
|
||||
.map(([key, itemConfig]) => {
|
||||
const color =
|
||||
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
||||
itemConfig.color;
|
||||
const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || itemConfig.color;
|
||||
return color ? ` --color-${key}: ${color};` : null;
|
||||
})
|
||||
.join("\n")}
|
||||
.join('\n')}
|
||||
}
|
||||
`
|
||||
)
|
||||
.join("\n"),
|
||||
.join('\n'),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -104,7 +101,7 @@ function ChartTooltipContent({
|
||||
active,
|
||||
payload,
|
||||
className,
|
||||
indicator = "dot",
|
||||
indicator = 'dot',
|
||||
hideLabel = false,
|
||||
hideIndicator = false,
|
||||
label,
|
||||
@@ -115,10 +112,10 @@ function ChartTooltipContent({
|
||||
nameKey,
|
||||
labelKey,
|
||||
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
||||
React.ComponentProps<"div"> & {
|
||||
React.ComponentProps<'div'> & {
|
||||
hideLabel?: boolean;
|
||||
hideIndicator?: boolean;
|
||||
indicator?: "line" | "dot" | "dashed";
|
||||
indicator?: 'line' | 'dot' | 'dashed';
|
||||
nameKey?: string;
|
||||
labelKey?: string;
|
||||
}) {
|
||||
@@ -130,18 +127,16 @@ function ChartTooltipContent({
|
||||
}
|
||||
|
||||
const [item] = payload;
|
||||
const key = `${labelKey || item?.dataKey || item?.name || "value"}`;
|
||||
const key = `${labelKey || item?.dataKey || item?.name || 'value'}`;
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||
const value =
|
||||
!labelKey && typeof label === "string"
|
||||
!labelKey && typeof label === 'string'
|
||||
? config[label as keyof typeof config]?.label || label
|
||||
: itemConfig?.label;
|
||||
|
||||
if (labelFormatter) {
|
||||
return (
|
||||
<div className={cn("font-medium", labelClassName)}>
|
||||
{labelFormatter(value, payload)}
|
||||
</div>
|
||||
<div className={cn('font-medium', labelClassName)}>{labelFormatter(value, payload)}</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -149,34 +144,26 @@ function ChartTooltipContent({
|
||||
return null;
|
||||
}
|
||||
|
||||
return <div className={cn("font-medium", labelClassName)}>{value}</div>;
|
||||
}, [
|
||||
label,
|
||||
labelFormatter,
|
||||
payload,
|
||||
hideLabel,
|
||||
labelClassName,
|
||||
config,
|
||||
labelKey,
|
||||
]);
|
||||
return <div className={cn('font-medium', labelClassName)}>{value}</div>;
|
||||
}, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);
|
||||
|
||||
if (!active || !payload?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nestLabel = payload.length === 1 && indicator !== "dot";
|
||||
const nestLabel = payload.length === 1 && indicator !== 'dot';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"border-border/50 bg-background-default grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs",
|
||||
'border-border/50 bg-background-default grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{!nestLabel ? tooltipLabel : null}
|
||||
<div className="grid gap-1.5">
|
||||
{payload.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || "value"}`;
|
||||
const key = `${nameKey || item.name || item.dataKey || 'value'}`;
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||
const indicatorColor = color || item.payload.fill || item.color;
|
||||
|
||||
@@ -184,8 +171,8 @@ function ChartTooltipContent({
|
||||
<div
|
||||
key={item.dataKey}
|
||||
className={cn(
|
||||
"[&>svg]:text-text-muted flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
|
||||
indicator === "dot" && "items-center"
|
||||
'[&>svg]:text-text-muted flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5',
|
||||
indicator === 'dot' && 'items-center'
|
||||
)}
|
||||
>
|
||||
{formatter && item?.value !== undefined && item.name ? (
|
||||
@@ -198,19 +185,19 @@ function ChartTooltipContent({
|
||||
!hideIndicator && (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
|
||||
'shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)',
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||
indicator === "dashed",
|
||||
"my-0.5": nestLabel && indicator === "dashed",
|
||||
'h-2.5 w-2.5': indicator === 'dot',
|
||||
'w-1': indicator === 'line',
|
||||
'w-0 border-[1.5px] border-dashed bg-transparent':
|
||||
indicator === 'dashed',
|
||||
'my-0.5': nestLabel && indicator === 'dashed',
|
||||
}
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--color-bg": indicatorColor,
|
||||
"--color-border": indicatorColor,
|
||||
'--color-bg': indicatorColor,
|
||||
'--color-border': indicatorColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
@@ -218,15 +205,13 @@ function ChartTooltipContent({
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 justify-between leading-none",
|
||||
nestLabel ? "items-end" : "items-center"
|
||||
'flex flex-1 justify-between leading-none',
|
||||
nestLabel ? 'items-end' : 'items-center'
|
||||
)}
|
||||
>
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-text-muted">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
<span className="text-text-muted">{itemConfig?.label || item.name}</span>
|
||||
</div>
|
||||
{item.value && (
|
||||
<span className="text-text-default font-mono tabular-nums">
|
||||
@@ -250,10 +235,10 @@ function ChartLegendContent({
|
||||
className,
|
||||
hideIcon = false,
|
||||
payload,
|
||||
verticalAlign = "bottom",
|
||||
verticalAlign = 'bottom',
|
||||
nameKey,
|
||||
}: React.ComponentProps<"div"> &
|
||||
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
||||
}: React.ComponentProps<'div'> &
|
||||
Pick<RechartsPrimitive.LegendProps, 'payload' | 'verticalAlign'> & {
|
||||
hideIcon?: boolean;
|
||||
nameKey?: string;
|
||||
}) {
|
||||
@@ -266,20 +251,20 @@ function ChartLegendContent({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-4",
|
||||
verticalAlign === "top" ? "pb-3" : "pt-3",
|
||||
'flex items-center justify-center gap-4',
|
||||
verticalAlign === 'top' ? 'pb-3' : 'pt-3',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{payload.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || "value"}`;
|
||||
const key = `${nameKey || item.dataKey || 'value'}`;
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
className={cn(
|
||||
"[&>svg]:text-text-muted flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3"
|
||||
'[&>svg]:text-text-muted flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3'
|
||||
)}
|
||||
>
|
||||
{itemConfig?.icon && !hideIcon ? (
|
||||
@@ -301,42 +286,29 @@ function ChartLegendContent({
|
||||
}
|
||||
|
||||
// Helper to extract item config from a payload.
|
||||
function getPayloadConfigFromPayload(
|
||||
config: ChartConfig,
|
||||
payload: unknown,
|
||||
key: string
|
||||
) {
|
||||
if (typeof payload !== "object" || payload === null) {
|
||||
function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {
|
||||
if (typeof payload !== 'object' || payload === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const payloadPayload =
|
||||
"payload" in payload &&
|
||||
typeof payload.payload === "object" &&
|
||||
payload.payload !== null
|
||||
'payload' in payload && typeof payload.payload === 'object' && payload.payload !== null
|
||||
? payload.payload
|
||||
: undefined;
|
||||
|
||||
let configLabelKey: string = key;
|
||||
|
||||
if (
|
||||
key in payload &&
|
||||
typeof payload[key as keyof typeof payload] === "string"
|
||||
) {
|
||||
if (key in payload && typeof payload[key as keyof typeof payload] === 'string') {
|
||||
configLabelKey = payload[key as keyof typeof payload] as string;
|
||||
} else if (
|
||||
payloadPayload &&
|
||||
key in payloadPayload &&
|
||||
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
||||
typeof payloadPayload[key as keyof typeof payloadPayload] === 'string'
|
||||
) {
|
||||
configLabelKey = payloadPayload[
|
||||
key as keyof typeof payloadPayload
|
||||
] as string;
|
||||
configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;
|
||||
}
|
||||
|
||||
return configLabelKey in config
|
||||
? config[configLabelKey]
|
||||
: config[key as keyof typeof config];
|
||||
return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config];
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import * as React from "react";
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
function TooltipProvider({
|
||||
delayDuration = 0,
|
||||
@@ -18,9 +19,7 @@ function TooltipProvider({
|
||||
);
|
||||
}
|
||||
|
||||
function Tooltip({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
||||
function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||
@@ -28,9 +27,7 @@ function Tooltip({
|
||||
);
|
||||
}
|
||||
|
||||
function TooltipTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||
function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
||||
}
|
||||
|
||||
@@ -46,7 +43,7 @@ function TooltipContent({
|
||||
data-slot="tooltip-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-[#2E2E2E] text-[#FFFFFF] animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
||||
'bg-[#2E2E2E] text-[#FFFFFF] animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
import { createContext, useContext, useState, useCallback, ReactElement, ReactNode } from 'react';
|
||||
|
||||
interface TimelineContextType {
|
||||
currentDate: Date;
|
||||
setCurrentDate: (date: Date) => void;
|
||||
isCurrentDate: (date: Date) => boolean;
|
||||
}
|
||||
|
||||
const TimelineContext = createContext<TimelineContextType | undefined>(undefined);
|
||||
|
||||
export function TimelineProvider({ children }: { children: React.ReactNode }) {
|
||||
export function TimelineProvider({ children }: { children: ReactNode }): ReactElement {
|
||||
const [currentDate, setCurrentDate] = useState(new Date());
|
||||
|
||||
const isCurrentDate = useCallback((date: Date): boolean => {
|
||||
return date.toDateString() === new Date().toDateString();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<TimelineContext.Provider value={{ currentDate, setCurrentDate }}>
|
||||
<TimelineContext.Provider value={{ currentDate, setCurrentDate, isCurrentDate }}>
|
||||
{children}
|
||||
</TimelineContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useTimeline() {
|
||||
export function useTimeline(): TimelineContextType {
|
||||
const context = useContext(TimelineContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useTimeline must be used within a TimelineProvider');
|
||||
|
||||
@@ -11,25 +11,26 @@ interface TimelineStyles {
|
||||
|
||||
export function useTimelineStyles(date?: Date): TimelineStyles {
|
||||
const { isCurrentDate } = useTimeline();
|
||||
const isPastDate = date && date < new Date() && !isCurrentDate(date);
|
||||
const isPastDate = date ? date < new Date() && !isCurrentDate(date) : false;
|
||||
|
||||
// Content cards match the Tasks Completed tile styling
|
||||
const contentCardStyle = 'bg-white dark:bg-[#121212] shadow-[0_0_13.7px_rgba(0,0,0,0.04)] dark:shadow-[0_0_24px_rgba(255,255,255,0.02)]';
|
||||
const contentCardStyle =
|
||||
'bg-white dark:bg-[#121212] shadow-[0_0_13.7px_rgba(0,0,0,0.04)] dark:shadow-[0_0_24px_rgba(255,255,255,0.02)]';
|
||||
|
||||
// Greeting card styles based on date
|
||||
const greetingCardStyle = !isPastDate
|
||||
? {
|
||||
background: 'bg-textStandard', // Black background
|
||||
text: 'text-white' // White text
|
||||
text: 'text-white', // White text
|
||||
}
|
||||
: {
|
||||
background: 'bg-gray-100', // Light grey background
|
||||
text: 'text-gray-600' // Darker grey text
|
||||
text: 'text-gray-600', // Darker grey text
|
||||
};
|
||||
|
||||
return {
|
||||
isPastDate,
|
||||
greetingCardStyle,
|
||||
contentCardStyle
|
||||
contentCardStyle,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,120 +1,14 @@
|
||||
import React, { Suspense, useState } from 'react';
|
||||
import { Outlet } from '@tanstack/react-router';
|
||||
import { FloatingFilters } from '../components/filters/FloatingFilters';
|
||||
import SuspenseLoader from '../components/SuspenseLoader';
|
||||
import { FilterOption } from '../components/filters/types';
|
||||
import { DarkModeToggle } from '../components/DarkModeToggle';
|
||||
import { DateDisplay } from '../components/DateDisplay';
|
||||
import { TimelineProvider } from '../contexts/TimelineContext';
|
||||
import { FloatingChat } from '../components/chat/FloatingChat';
|
||||
import { ChatInput } from '../components/chat/ChatInput';
|
||||
import React from 'react';
|
||||
|
||||
const defaultFilters: FilterOption[] = [
|
||||
{ id: 'all', label: 'All', isActive: true },
|
||||
{ id: 'metrics', label: 'Metrics', isActive: false },
|
||||
{ id: 'tasks', label: 'Tasks', isActive: false },
|
||||
{ id: 'projects', label: 'Projects', isActive: false },
|
||||
{ id: 'automations', label: 'Automations', isActive: false },
|
||||
{ id: 'problems', label: 'Problems', isActive: false },
|
||||
];
|
||||
|
||||
const getFilterColor = (filterId: string): string => {
|
||||
switch (filterId) {
|
||||
case 'tasks':
|
||||
return '#05C168';
|
||||
case 'projects':
|
||||
return '#0066FF';
|
||||
case 'automations':
|
||||
return '#B18CFF';
|
||||
case 'problems':
|
||||
return '#FF2E6C';
|
||||
default:
|
||||
return 'transparent';
|
||||
}
|
||||
};
|
||||
|
||||
export const MainLayout: React.FC = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(event.currentTarget);
|
||||
const message = formData.get('message') as string;
|
||||
|
||||
if (!message?.trim()) return;
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// TODO: Implement your message handling logic here
|
||||
console.log('Sending message:', message);
|
||||
} catch (error) {
|
||||
console.error('Error sending message:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleStopGeneration = () => {
|
||||
// TODO: Implement your stop generation logic here
|
||||
setIsLoading(false);
|
||||
};
|
||||
interface MainLayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
|
||||
return (
|
||||
<TimelineProvider>
|
||||
<div className="min-h-screen bg-background-default dark:bg-zinc-800 transition-colors duration-200">
|
||||
<div className="titlebar-drag-region" />
|
||||
<DateDisplay />
|
||||
<div className="h-10 w-full" />
|
||||
|
||||
<FloatingFilters>
|
||||
<div filters={defaultFilters}>
|
||||
<div className="flex justify-center w-full px-4 pt-4">
|
||||
<div className="inline-flex gap-3 justify-center">
|
||||
{defaultFilters.map((filter) => (
|
||||
<button
|
||||
key={filter.id}
|
||||
className={`
|
||||
cursor-pointer
|
||||
px-4 py-2 rounded-full text-sm font-light transition-all
|
||||
shadow-[0_0_13.7px_rgba(0,0,0,0.04)]
|
||||
dark:shadow-[0_0_24px_rgba(255,255,255,0.08)]
|
||||
flex items-center gap-2
|
||||
${filter.isActive
|
||||
? 'bg-background-inverse text-text-inverse'
|
||||
: 'bg-background-default text-text-muted hover:text-text-default dark:text-white/60 dark:hover:text-white'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{filter.id !== 'all' && filter.id !== 'metrics' && (
|
||||
<div
|
||||
className="w-2 h-2 rounded-full"
|
||||
style={{ backgroundColor: getFilterColor(filter.id) }}
|
||||
/>
|
||||
)}
|
||||
{filter.label}
|
||||
</button>
|
||||
))}
|
||||
<div className="p-5 pt-10 max-w-3xl mx-auto">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FloatingFilters>
|
||||
|
||||
<main className="w-full pb-32">
|
||||
<Suspense fallback={<SuspenseLoader />}>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</main>
|
||||
|
||||
<FloatingChat>
|
||||
<ChatInput
|
||||
handleSubmit={handleSubmit}
|
||||
isLoading={isLoading}
|
||||
onStop={handleStopGeneration}
|
||||
/>
|
||||
</FloatingChat>
|
||||
|
||||
<DarkModeToggle />
|
||||
</div>
|
||||
</TimelineProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
export function cn(...inputs: ClassValue[]): string {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export const copyToClipboard = (text: string) => {
|
||||
export const copyToClipboard = (text: string): void => {
|
||||
if (window === undefined) return;
|
||||
window.navigator.clipboard.writeText(text);
|
||||
};
|
||||
|
||||
export function getComponentName(name: string) {
|
||||
export function getComponentName(name: string): string {
|
||||
// convert kebab-case to title case
|
||||
return name.replace(/-/g, " ");
|
||||
return name.replace(/-/g, ' ');
|
||||
}
|
||||
|
||||
export function getRandomIndex(array: any[]) {
|
||||
export function getRandomIndex<T>(array: T[]): number {
|
||||
return Math.floor(Math.random() * array.length);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
// Import Routes
|
||||
|
||||
import { Route as rootRoute } from './routes/__root'
|
||||
import { Route as IndexImport } from './routes/index'
|
||||
import { Route as rootRoute } from './routes/__root';
|
||||
import { Route as IndexImport } from './routes/index';
|
||||
|
||||
// Create/Update Routes
|
||||
|
||||
@@ -19,57 +19,57 @@ const IndexRoute = IndexImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
} as any);
|
||||
|
||||
// Populate the FileRoutesByPath interface
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof IndexImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
id: '/';
|
||||
path: '/';
|
||||
fullPath: '/';
|
||||
preLoaderRoute: typeof IndexImport;
|
||||
parentRoute: typeof rootRoute;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export the route tree
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/': typeof IndexRoute;
|
||||
}
|
||||
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/': typeof IndexRoute;
|
||||
}
|
||||
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRoute
|
||||
'/': typeof IndexRoute
|
||||
__root__: typeof rootRoute;
|
||||
'/': typeof IndexRoute;
|
||||
}
|
||||
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/'
|
||||
id: '__root__' | '/'
|
||||
fileRoutesById: FileRoutesById
|
||||
fileRoutesByFullPath: FileRoutesByFullPath;
|
||||
fullPaths: '/';
|
||||
fileRoutesByTo: FileRoutesByTo;
|
||||
to: '/';
|
||||
id: '__root__' | '/';
|
||||
fileRoutesById: FileRoutesById;
|
||||
}
|
||||
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
IndexRoute: typeof IndexRoute;
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
}
|
||||
};
|
||||
|
||||
export const routeTree = rootRoute
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
._addFileTypes<FileRouteTypes>();
|
||||
|
||||
/* ROUTE_MANIFEST_START
|
||||
{
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { createRouter } from '@tanstack/react-router';
|
||||
|
||||
import { rootRoute, timelineRoute } from './routes';
|
||||
|
||||
const routeTree = rootRoute.addChildren([timelineRoute]);
|
||||
import { routeTree } from './routeTree.gen';
|
||||
|
||||
export const router = createRouter({ routeTree });
|
||||
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import { createRootRoute, Link, Outlet } from '@tanstack/react-router';
|
||||
import { createRootRoute, Outlet } from '@tanstack/react-router';
|
||||
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';
|
||||
|
||||
import { MainLayout } from '../layout/MainLayout';
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: () => (
|
||||
<>
|
||||
<div className="p-2 flex gap-2">
|
||||
<Link to="/" className="[&.active]:font-bold">
|
||||
Timeline
|
||||
</Link>{' '}
|
||||
</div>
|
||||
<hr />
|
||||
<MainLayout>
|
||||
<Outlet />
|
||||
<TanStackRouterDevtools />
|
||||
</>
|
||||
</MainLayout>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -1,21 +1,7 @@
|
||||
import { createRootRoute, createRoute } from '@tanstack/react-router';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
|
||||
import App from '../App';
|
||||
import Timeline from '../components/Timeline';
|
||||
import { TimelineProvider } from '../components/TimelineContext';
|
||||
import Home from '../components/Home';
|
||||
|
||||
export const rootRoute = createRootRoute({
|
||||
component: App,
|
||||
});
|
||||
|
||||
export const timelineRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/',
|
||||
component: () => (
|
||||
<TimelineProvider>
|
||||
<div className="flex flex-col h-full">
|
||||
<Timeline />
|
||||
</div>
|
||||
</TimelineProvider>
|
||||
),
|
||||
export const Route = createFileRoute('/')({
|
||||
component: Home,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
@import url('tailwindcss');
|
||||
@import url('tw-animate-css');
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
--color-*: initial;
|
||||
|
||||
/* constants */
|
||||
--color-white: #ffffff;
|
||||
--color-black: #000000;
|
||||
--color-white: #fff;
|
||||
--color-black: #000;
|
||||
|
||||
/* slate */
|
||||
--color-slate-100: #f0f2f8;
|
||||
@@ -22,13 +22,10 @@
|
||||
/* utility */
|
||||
--color-red-100: #ff6b6b;
|
||||
--color-red-200: #f94b4b;
|
||||
|
||||
--color-blue-100: #7cacff;
|
||||
--color-blue-200: #5c98f9;
|
||||
|
||||
--color-green-100: #a3d795;
|
||||
--color-green-200: #91cb80;
|
||||
|
||||
--color-yellow-100: #ffd966;
|
||||
--color-yellow-200: #fbcd44;
|
||||
}
|
||||
@@ -53,7 +50,6 @@
|
||||
--background-success: var(--color-green-200);
|
||||
--background-info: var(--color-blue-200);
|
||||
--background-warning: var(--color-yellow-200);
|
||||
|
||||
--border-default: var(--color-slate-200);
|
||||
--border-input: var(--color-slate-200);
|
||||
--border-strong: var(--color-slate-300);
|
||||
@@ -62,7 +58,6 @@
|
||||
--border-success: var(--color-green-200);
|
||||
--border-warning: var(--color-yellow-200);
|
||||
--border-info: var(--color-blue-200);
|
||||
|
||||
--text-default: var(--color-slate-600);
|
||||
--text-muted: var(--color-slate-400);
|
||||
--text-inverse: var(--color-white);
|
||||
@@ -70,15 +65,12 @@
|
||||
--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);
|
||||
@@ -104,7 +96,6 @@
|
||||
--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);
|
||||
@@ -113,7 +104,6 @@
|
||||
--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);
|
||||
@@ -121,15 +111,12 @@
|
||||
--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);
|
||||
@@ -150,11 +137,9 @@
|
||||
--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);
|
||||
@@ -163,7 +148,6 @@
|
||||
--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);
|
||||
@@ -173,8 +157,8 @@
|
||||
--color-text-info: var(--text-info);
|
||||
|
||||
/* fonts */
|
||||
--font-sans: "Cash Sans", sans-serif;
|
||||
--font-mono: "Cash Sans Mono", monospace;
|
||||
--font-sans: 'Cash Sans', sans-serif;
|
||||
--font-mono: 'Cash Sans Mono', monospace;
|
||||
--font-serif: serif;
|
||||
|
||||
/* shape */
|
||||
@@ -182,15 +166,12 @@
|
||||
--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);
|
||||
@@ -202,45 +183,48 @@
|
||||
}
|
||||
|
||||
@font-face {
|
||||
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-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-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(../assets/fonts/CashSansMono-Light.woff2) format("woff2");
|
||||
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-family: 'Cash Sans Mono';
|
||||
src: url('../assets/fonts/CashSansMono-Regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@@ -249,6 +233,7 @@
|
||||
* {
|
||||
@apply border-border-default;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background-default text-text-default;
|
||||
}
|
||||
|
||||
32
ui-v2/src/test/types.d.ts
vendored
32
ui-v2/src/test/types.d.ts
vendored
@@ -1,6 +1,26 @@
|
||||
/// <reference types="vitest" />
|
||||
/// <reference types="@testing-library/jest-dom" />
|
||||
|
||||
declare module '*.png' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.jpg' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.jpeg' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.gif' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.svg' {
|
||||
import * as React from 'react';
|
||||
export const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
|
||||
@@ -8,17 +28,9 @@ declare module '*.svg' {
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.jpg' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.png' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.json' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"noImplicitReturns": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@platform": ["src/services/platform/electron"],
|
||||
"@platform/*": ["src/services/platform/electron/*"]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user