Compare commits
27 Commits
577d1eac8a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b65735994 | |||
| 298afc3cc8 | |||
| 72c9e666af | |||
| 3dfd35d8ba | |||
| eebdc619f2 | |||
| 188aa4fc74 | |||
| 2d73cb5def | |||
| 431e654154 | |||
| 6b7e254014 | |||
| 8006a65e88 | |||
| fe4783ae97 | |||
| d880b3fc26 | |||
| 299de302d4 | |||
| 96a3c13cc7 | |||
| 782ba7b911 | |||
| 201f133863 | |||
| d46fea9ea3 | |||
| 7811949cc4 | |||
| 9cdedf2bf9 | |||
| 5720571416 | |||
| 22e7a6903b | |||
| 22d4d65ad8 | |||
| 85b9cdb28f | |||
| 67d73ad308 | |||
| 03d556fd92 | |||
| 095c681401 | |||
| e9fcc24e65 |
@@ -13,7 +13,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
|
||||||
@@ -27,5 +27,5 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Run check
|
- name: Run lint
|
||||||
run: pnpm run check
|
run: pnpm run lint
|
||||||
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 105,
|
||||||
|
"plugins": ["prettier-plugin-tailwindcss"]
|
||||||
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { FlatCompat } from "@eslint/eslintrc";
|
|
||||||
import tseslint from "typescript-eslint";
|
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
|
||||||
baseDirectory: import.meta.dirname,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default tseslint.config(
|
|
||||||
{
|
|
||||||
ignores: [".next"],
|
|
||||||
},
|
|
||||||
...compat.extends("next/core-web-vitals"),
|
|
||||||
{
|
|
||||||
files: ["**/*.ts", "**/*.tsx"],
|
|
||||||
extends: [
|
|
||||||
...tseslint.configs.recommended,
|
|
||||||
...tseslint.configs.recommendedTypeChecked,
|
|
||||||
...tseslint.configs.stylisticTypeChecked,
|
|
||||||
],
|
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/array-type": "off",
|
|
||||||
"@typescript-eslint/consistent-type-definitions": "off",
|
|
||||||
"@typescript-eslint/consistent-type-imports": [
|
|
||||||
"warn",
|
|
||||||
{ prefer: "type-imports", fixStyle: "inline-type-imports" },
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-unused-vars": [
|
|
||||||
"warn",
|
|
||||||
{ argsIgnorePattern: "^_" },
|
|
||||||
],
|
|
||||||
"@typescript-eslint/require-await": "off",
|
|
||||||
"@typescript-eslint/no-misused-promises": [
|
|
||||||
"error",
|
|
||||||
{ checksVoidReturn: { attributes: false } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
linterOptions: {
|
|
||||||
reportUnusedDisableDirectives: true,
|
|
||||||
},
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
projectService: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
37
eslint.config.mjs
Normal file
37
eslint.config.mjs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// @ts-check
|
||||||
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
|
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||||
|
import nextTs from "eslint-config-next/typescript";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
const eslintConfig = defineConfig([
|
||||||
|
// Next.js core-web-vitals and TypeScript configs
|
||||||
|
...nextVitals,
|
||||||
|
...nextTs,
|
||||||
|
// Add strict TypeScript rules on top
|
||||||
|
...tseslint.configs.strictTypeChecked,
|
||||||
|
...tseslint.configs.stylisticTypeChecked,
|
||||||
|
// Configure TypeScript parser options
|
||||||
|
{
|
||||||
|
files: ["**/*.{ts,tsx}"],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
projectService: true,
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Override default ignores of eslint-config-next
|
||||||
|
globalIgnores([
|
||||||
|
// Default ignores of eslint-config-next:
|
||||||
|
".next/**",
|
||||||
|
"out/**",
|
||||||
|
"build/**",
|
||||||
|
"next-env.d.ts",
|
||||||
|
// Additional ignores:
|
||||||
|
"*.mjs",
|
||||||
|
"tailwind.config.ts",
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
|
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
|
||||||
* for Docker builds.
|
* for Docker builds.
|
||||||
*/
|
*/
|
||||||
import "./src/env.js";
|
import './src/env.ts';
|
||||||
|
|
||||||
/** @type {import("next").NextConfig} */
|
/** @type {import("next").NextConfig} */
|
||||||
const config = {};
|
const config = {};
|
||||||
37
package.json
37
package.json
@@ -9,7 +9,7 @@
|
|||||||
"dev": "next dev --turbopack",
|
"dev": "next dev --turbopack",
|
||||||
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||||
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||||
"lint:fix": "next lint --fix",
|
"lint": "next typegen && eslint . && npx tsc --noEmit",
|
||||||
"preview": "next build && next start",
|
"preview": "next build && next start",
|
||||||
"start": "next start"
|
"start": "next start"
|
||||||
},
|
},
|
||||||
@@ -23,35 +23,40 @@
|
|||||||
"@t3-oss/env-nextjs": "^0.13.0",
|
"@t3-oss/env-nextjs": "^0.13.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.552.0",
|
"lucide-react": "^0.554.0",
|
||||||
"next": "^15.4.1",
|
"next": "16.0.3",
|
||||||
"next-plausible": "^3.12.4",
|
"next-plausible": "^3.12.4",
|
||||||
"react": "^19.0.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "19.2.0",
|
||||||
"react-hook-form": "^7.56.1",
|
"react-hook-form": "^7.56.1",
|
||||||
"recharts": "^3.0.0",
|
"recharts": "^2.15.3",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"zod": "^4.0.0"
|
"zod": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "3.3.1",
|
"@tailwindcss/postcss": "4.1.17",
|
||||||
"@tailwindcss/postcss": "4.1.16",
|
"@types/node": "24.10.1",
|
||||||
"@types/node": "24.9.2",
|
"@types/react": "19.2.6",
|
||||||
"@types/react": "19.2.2",
|
"@types/react-dom": "19.2.3",
|
||||||
"@types/react-dom": "19.2.2",
|
|
||||||
"eslint": "9.39.1",
|
"eslint": "9.39.1",
|
||||||
"eslint-config-next": "15.5.6",
|
"eslint-config-next": "16.0.3",
|
||||||
"eslint-plugin-react-hooks": "5.2.0",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"prettier-plugin-tailwindcss": "0.7.1",
|
"prettier-plugin-tailwindcss": "0.7.1",
|
||||||
"tailwindcss": "4.1.16",
|
"tailwindcss": "4.1.17",
|
||||||
"tw-animate-css": "1.4.0",
|
"tw-animate-css": "1.4.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"typescript-eslint": "8.46.2"
|
"typescript-eslint": "8.47.0"
|
||||||
},
|
},
|
||||||
"ct3aMetadata": {
|
"ct3aMetadata": {
|
||||||
"initVersion": "7.39.3"
|
"initVersion": "7.39.3"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.20.0"
|
"packageManager": "pnpm@10.23.0",
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"@types/react": "19.2.6",
|
||||||
|
"@types/react-dom": "19.2.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1797
pnpm-lock.yaml
generated
1797
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
|||||||
/** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').PluginOptions} */
|
|
||||||
export default {
|
|
||||||
plugins: ["prettier-plugin-tailwindcss"],
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
import type React from "react";
|
||||||
import {
|
import {
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
HandCoins,
|
HandCoins,
|
||||||
@@ -112,46 +113,49 @@ export default function MultiIconPattern({ opacity = 0.2, spacing = 160 }) {
|
|||||||
Target,
|
Target,
|
||||||
];
|
];
|
||||||
|
|
||||||
const renderIcons = ({
|
const [icons, setIcons] = useState<React.ReactElement[]>([]);
|
||||||
rows,
|
|
||||||
columns,
|
useEffect(() => {
|
||||||
}: {
|
if (rows === 0 || columns === 0) {
|
||||||
rows: number;
|
setIcons([]);
|
||||||
columns: number;
|
return;
|
||||||
}) => {
|
}
|
||||||
const icons = [];
|
|
||||||
|
const iconElements: React.ReactElement[] = [];
|
||||||
for (let y = 0; y < rows; y++) {
|
for (let y = 0; y < rows; y++) {
|
||||||
for (let x = 0; x < columns; x++) {
|
for (let x = 0; x < columns; x++) {
|
||||||
// Pick a random icon component from the array
|
// Pick a random icon component from the array
|
||||||
const randomIndex = Math.floor(Math.random() * iconComponents.length);
|
const randomIndex = Math.floor(Math.random() * iconComponents.length);
|
||||||
const IconComponent = iconComponents[randomIndex]!;
|
const IconComponent = iconComponents[randomIndex];
|
||||||
|
|
||||||
// Slightly randomize size and position for more organic feel
|
// Slightly randomize size and position for more organic feel
|
||||||
const size = 28 + Math.floor(Math.random() * 8);
|
const size = 28 + Math.floor(Math.random() * 8);
|
||||||
const xOffset = Math.floor(Math.random() * (spacing / 1.618));
|
const xOffset = Math.floor(Math.random() * (spacing / 1.618));
|
||||||
const yOffset = Math.floor(Math.random() * (spacing / 1.618));
|
const yOffset = Math.floor(Math.random() * (spacing / 1.618));
|
||||||
|
const rotation = Math.round((Math.random() - 0.5) * 30);
|
||||||
|
|
||||||
icons.push(
|
iconElements.push(
|
||||||
<IconComponent
|
<IconComponent
|
||||||
key={`icon-${x}-${y}`}
|
key={`icon-${String(x)}-${String(y)}`}
|
||||||
size={size}
|
size={size}
|
||||||
className="text-primary fixed"
|
className="text-primary fixed"
|
||||||
style={{
|
style={{
|
||||||
left: `${x * spacing + xOffset}px`,
|
left: `${String(x * spacing + xOffset)}px`,
|
||||||
top: `${y * spacing + yOffset}px`,
|
top: `${String(y * spacing + yOffset)}px`,
|
||||||
opacity: opacity,
|
opacity: opacity,
|
||||||
transform: `rotate(${Math.round((Math.random() - 0.5) * 30)}deg)`,
|
transform: `rotate(${String(rotation)}deg)`,
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return icons;
|
setIcons(iconElements);
|
||||||
};
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [rows, columns, spacing, opacity]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute h-full w-full">
|
<div className="absolute h-full w-full">
|
||||||
{width > 0 && renderIcons({ rows, columns })}
|
{width > 0 && icons}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ export default function FireCalculatorForm() {
|
|||||||
return [0, 0];
|
return [0, 0];
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if (retirementIndex === -1 || !retirementData) {
|
if (retirementIndex === -1) {
|
||||||
setResult({
|
setResult({
|
||||||
fireNumber: null,
|
fireNumber: null,
|
||||||
fireNumber4percent: null,
|
fireNumber4percent: null,
|
||||||
@@ -246,7 +246,13 @@ export default function FireCalculatorForm() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
void form.handleSubmit(onSubmit)(e);
|
||||||
|
}}
|
||||||
|
className="space-y-8"
|
||||||
|
>
|
||||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -265,7 +271,8 @@ export default function FireCalculatorForm() {
|
|||||||
? undefined
|
? undefined
|
||||||
: Number(e.target.value),
|
: Number(e.target.value),
|
||||||
);
|
);
|
||||||
void form.handleSubmit(onSubmit)();
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
onBlur={field.onBlur}
|
onBlur={field.onBlur}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
@@ -293,7 +300,8 @@ export default function FireCalculatorForm() {
|
|||||||
? undefined
|
? undefined
|
||||||
: Number(e.target.value),
|
: Number(e.target.value),
|
||||||
);
|
);
|
||||||
void form.handleSubmit(onSubmit)();
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
onBlur={field.onBlur}
|
onBlur={field.onBlur}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
@@ -321,7 +329,8 @@ export default function FireCalculatorForm() {
|
|||||||
? undefined
|
? undefined
|
||||||
: Number(e.target.value),
|
: Number(e.target.value),
|
||||||
);
|
);
|
||||||
void form.handleSubmit(onSubmit)();
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
onBlur={field.onBlur}
|
onBlur={field.onBlur}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
@@ -349,7 +358,8 @@ export default function FireCalculatorForm() {
|
|||||||
? undefined
|
? undefined
|
||||||
: Number(e.target.value),
|
: Number(e.target.value),
|
||||||
);
|
);
|
||||||
void form.handleSubmit(onSubmit)();
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
onBlur={field.onBlur}
|
onBlur={field.onBlur}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
@@ -378,7 +388,8 @@ export default function FireCalculatorForm() {
|
|||||||
? undefined
|
? undefined
|
||||||
: Number(e.target.value),
|
: Number(e.target.value),
|
||||||
);
|
);
|
||||||
void form.handleSubmit(onSubmit)();
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
onBlur={field.onBlur}
|
onBlur={field.onBlur}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
@@ -407,7 +418,8 @@ export default function FireCalculatorForm() {
|
|||||||
? undefined
|
? undefined
|
||||||
: Number(e.target.value),
|
: Number(e.target.value),
|
||||||
);
|
);
|
||||||
void form.handleSubmit(onSubmit)();
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
onBlur={field.onBlur}
|
onBlur={field.onBlur}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
@@ -437,7 +449,8 @@ export default function FireCalculatorForm() {
|
|||||||
? undefined
|
? undefined
|
||||||
: Number(e.target.value),
|
: Number(e.target.value),
|
||||||
);
|
);
|
||||||
void form.handleSubmit(onSubmit)();
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
onBlur={field.onBlur}
|
onBlur={field.onBlur}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
@@ -467,7 +480,8 @@ export default function FireCalculatorForm() {
|
|||||||
step={1}
|
step={1}
|
||||||
onValueChange={(value: number[]) => {
|
onValueChange={(value: number[]) => {
|
||||||
field.onChange(value[0]);
|
field.onChange(value[0]);
|
||||||
void form.handleSubmit(onSubmit)();
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
form.handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
className="py-4"
|
className="py-4"
|
||||||
/>
|
/>
|
||||||
@@ -648,7 +662,7 @@ export default function FireCalculatorForm() {
|
|||||||
)}
|
)}
|
||||||
{result && (
|
{result && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setShowing4percent(!showing4percent)}
|
onClick={() => { setShowing4percent(!showing4percent); }}
|
||||||
variant={showing4percent ? "secondary" : "default"}
|
variant={showing4percent ? "secondary" : "default"}
|
||||||
size={"sm"}
|
size={"sm"}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export default function HomePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="text-primary-foreground to-destructive from-secondary flex min-h-screen flex-col items-center bg-gradient-to-b p-2">
|
<main className="text-primary-foreground to-destructive from-secondary flex min-h-screen flex-col items-center bg-linear-to-b p-2">
|
||||||
<BackgroundPattern />
|
<BackgroundPattern />
|
||||||
<div className="z-10 mx-auto flex flex-col items-center justify-center gap-4 text-center">
|
<div className="z-10 mx-auto flex flex-col items-center justify-center gap-4 text-center">
|
||||||
<div className="mt-8 flex flex-row flex-wrap items-center justify-center gap-4 align-middle">
|
<div className="mt-8 flex flex-row flex-wrap items-center justify-center gap-4 align-middle">
|
||||||
@@ -78,7 +78,7 @@ export default function HomePage() {
|
|||||||
width={100}
|
width={100}
|
||||||
height={100}
|
height={100}
|
||||||
/>
|
/>
|
||||||
<h1 className="from-primary via-primary-foreground to-primary bg-gradient-to-r bg-clip-text text-5xl font-extrabold tracking-tight text-transparent drop-shadow-md sm:text-[5rem]">
|
<h1 className="from-primary via-primary-foreground to-primary bg-linear-to-r bg-clip-text text-5xl font-extrabold tracking-tight text-transparent drop-shadow-md sm:text-[5rem]">
|
||||||
InvestingFIRE
|
InvestingFIRE
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ export type ChartConfig = Record<
|
|||||||
)
|
)
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type ChartContextProps = {
|
interface ChartContextProps {
|
||||||
config: ChartConfig;
|
config: ChartConfig;
|
||||||
};
|
}
|
||||||
|
|
||||||
const ChartContext = React.createContext<ChartContextProps | null>(null);
|
const ChartContext = React.createContext<ChartContextProps | null>(null);
|
||||||
|
|
||||||
@@ -135,12 +135,12 @@ function ChartTooltipContent({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [item] = payload;
|
const item = payload[0];
|
||||||
const key = `${labelKey ?? item?.dataKey ?? item?.name ?? "value"}`;
|
const key = labelKey ?? String(item.dataKey ?? item.name ?? "value");
|
||||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||||
const value =
|
const value =
|
||||||
!labelKey && typeof label === "string"
|
!labelKey && typeof label === "string"
|
||||||
? (config[label]?.label ?? label)
|
? (label in config && config[label].label ? config[label].label : undefined) ?? label
|
||||||
: itemConfig?.label;
|
: itemConfig?.label;
|
||||||
|
|
||||||
if (labelFormatter) {
|
if (labelFormatter) {
|
||||||
@@ -175,14 +175,14 @@ function ChartTooltipContent({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
|
"border-border/50 bg-background grid min-w-32 items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!nestLabel ? tooltipLabel : null}
|
{!nestLabel ? tooltipLabel : null}
|
||||||
<div className="grid gap-1.5">
|
<div className="grid gap-1.5">
|
||||||
{payload.map((item, index) => {
|
{payload.map((item, index) => {
|
||||||
const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}`;
|
const key = nameKey ?? String(item.name ?? item.dataKey ?? "value");
|
||||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||||
const indicatorColor: string | undefined =
|
const indicatorColor: string | undefined =
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
@@ -196,7 +196,7 @@ function ChartTooltipContent({
|
|||||||
indicator === "dot" && "items-center",
|
indicator === "dot" && "items-center",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{formatter && item?.value !== undefined && item.name ? (
|
{formatter && item.value !== undefined && item.name ? (
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
formatter(item.value, item.name, item, index, item.payload)
|
formatter(item.value, item.name, item, index, item.payload)
|
||||||
) : (
|
) : (
|
||||||
@@ -207,7 +207,7 @@ function ChartTooltipContent({
|
|||||||
!hideIndicator && (
|
!hideIndicator && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
|
"shrink-0 rounded-[2px] border-border bg-(--color-bg)",
|
||||||
{
|
{
|
||||||
"h-2.5 w-2.5": indicator === "dot",
|
"h-2.5 w-2.5": indicator === "dot",
|
||||||
"w-1": indicator === "line",
|
"w-1": indicator === "line",
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ import { Label } from "@/components/ui/label";
|
|||||||
|
|
||||||
const Form = FormProvider;
|
const Form = FormProvider;
|
||||||
|
|
||||||
type FormFieldContextValue<
|
interface FormFieldContextValue<
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||||
> = {
|
> {
|
||||||
name: TName;
|
name: TName;
|
||||||
};
|
}
|
||||||
|
|
||||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||||
{} as FormFieldContextValue,
|
{} as FormFieldContextValue,
|
||||||
@@ -61,9 +61,9 @@ const useFormField = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormItemContextValue = {
|
interface FormItemContextValue {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||||
{} as FormItemContextValue,
|
{} as FormItemContextValue,
|
||||||
@@ -110,7 +110,7 @@ function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
|||||||
id={formItemId}
|
id={formItemId}
|
||||||
aria-describedby={
|
aria-describedby={
|
||||||
!error
|
!error
|
||||||
? `${formDescriptionId}`
|
? formDescriptionId
|
||||||
: `${formDescriptionId} ${formMessageId}`
|
: `${formDescriptionId} ${formMessageId}`
|
||||||
}
|
}
|
||||||
aria-invalid={!!error}
|
aria-invalid={!!error}
|
||||||
@@ -134,7 +134,7 @@ function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|||||||
|
|
||||||
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
const { error, formMessageId } = useFormField();
|
const { error, formMessageId } = useFormField();
|
||||||
const body = error ? String(error?.message ?? "") : props.children;
|
const body = error ? (error.message ?? "") : props.children;
|
||||||
|
|
||||||
if (!body) {
|
if (!body) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function SelectTrigger({
|
|||||||
data-slot="select-trigger"
|
data-slot="select-trigger"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"border-input data-placeholder:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -61,7 +61,7 @@ function SelectContent({
|
|||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Content
|
||||||
data-slot="select-content"
|
data-slot="select-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 relative z-50 max-h-(--radix-select-content-available-height) min-w-32 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
className,
|
className,
|
||||||
@@ -74,7 +74,7 @@ function SelectContent({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"p-1",
|
"p-1",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
|
"h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width) scroll-my-1",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -107,7 +107,7 @@ function SelectItem({
|
|||||||
<SelectPrimitive.Item
|
<SelectPrimitive.Item
|
||||||
data-slot="select-item"
|
data-slot="select-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ function Slider({
|
|||||||
min={min}
|
min={min}
|
||||||
max={max}
|
max={max}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
|
"relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -1,42 +1,35 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
/* Base Options: */
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"esModuleInterop": true,
|
"allowJs": false,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"target": "es2022",
|
|
||||||
"allowJs": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"isolatedModules": true,
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
|
|
||||||
/* Strictness */
|
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUncheckedIndexedAccess": true,
|
|
||||||
"checkJs": true,
|
|
||||||
|
|
||||||
/* Bundled projects */
|
|
||||||
"lib": ["dom", "dom.iterable", "ES2022"],
|
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"module": "ESNext",
|
"esModuleInterop": true,
|
||||||
"moduleResolution": "Bundler",
|
"module": "esnext",
|
||||||
"jsx": "preserve",
|
"moduleResolution": "bundler",
|
||||||
"plugins": [{ "name": "next" }],
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
|
|
||||||
/* Path Aliases */
|
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
},
|
||||||
|
"target": "ES2022",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noUncheckedIndexedAccess": false,
|
||||||
|
"exactOptionalPropertyTypes": false,
|
||||||
|
"noImplicitReturns": false,
|
||||||
|
"plugins": [{ "name": "next" }]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
"**/*.cjs",
|
".next/types/**/*.ts",
|
||||||
"**/*.js",
|
".next/dev/types/**/*.ts"
|
||||||
".next/types/**/*.ts"
|
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user