diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..076b30810 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,5 @@ +# Commits that only reformat code. To have `git blame` skip them, run +# git config blame.ignoreRevsFile .git-blame-ignore-revs + +# Format the website and calculator with Prettier +e8d04db611dc231e6c27fe178893f55e3bfc0435 diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index fc1c1ab3d..5e5ec70e4 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -27,14 +27,30 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile - - name: Check formatting with Prettier + - name: Check formatting with Prettier (app) working-directory: ./app run: bun run prettier - - name: Run ESLint + - name: Run ESLint (app) working-directory: ./app run: bun run eslint + - name: Check formatting with Prettier (website) + working-directory: ./website + run: bun run format:check + + - name: Run ESLint (website) + working-directory: ./website + run: bun run lint + + - name: Check formatting with Prettier (calculator) + working-directory: ./calculator-app + run: bun run format:check + + - name: Run ESLint (calculator) + working-directory: ./calculator-app + run: bun run lint + typecheck: name: Type checking runs-on: ubuntu-latest diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 186d2a21a..cabe44e29 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -20,14 +20,30 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile - - name: Check formatting with Prettier + - name: Check formatting with Prettier (app) working-directory: ./app run: bun run prettier - - name: Run ESLint + - name: Run ESLint (app) working-directory: ./app run: bun run eslint + - name: Check formatting with Prettier (website) + working-directory: ./website + run: bun run format:check + + - name: Run ESLint (website) + working-directory: ./website + run: bun run lint + + - name: Check formatting with Prettier (calculator) + working-directory: ./calculator-app + run: bun run format:check + + - name: Run ESLint (calculator) + working-directory: ./calculator-app + run: bun run lint + typecheck: name: Type checking runs-on: ubuntu-latest @@ -42,10 +58,18 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile - - name: Run TypeScript type checks + - name: Run TypeScript type checks (app) working-directory: ./app run: bun run typecheck + - name: Run TypeScript type checks (website) + working-directory: ./website + run: bun run typecheck + + - name: Typecheck calculator + working-directory: ./calculator-app + run: bun run typecheck + test: name: Tests runs-on: ubuntu-latest @@ -64,6 +88,10 @@ jobs: working-directory: ./app run: bun run test + - name: Run website tests + working-directory: ./website + run: bun run test + check-embeds: name: Check embedded site URLs runs-on: ubuntu-latest diff --git a/Makefile b/Makefile index 395940fda..7cfd0a8fd 100644 --- a/Makefile +++ b/Makefile @@ -32,9 +32,13 @@ test: lint: cd app && bun run lint + cd website && bun run lint + cd calculator-app && bun run lint format: cd app && bun run prettier:write + cd website && bun run format + cd calculator-app && bun run format clean: rm -rf website/.next calculator-app/.next app/node_modules node_modules packages/*/node_modules diff --git a/app/.prettierrc.mjs b/app/.prettierrc.mjs index 9a3ddde31..f2da58a96 100644 --- a/app/.prettierrc.mjs +++ b/app/.prettierrc.mjs @@ -1,5 +1,8 @@ /** @type {import("@ianvs/prettier-plugin-sort-imports").PrettierConfig} */ const config = { + // Set explicitly so an .editorconfig in a parent folder can't change them. + useTabs: false, + tabWidth: 2, endOfLine: 'lf', printWidth: 100, singleQuote: true, diff --git a/app/package.json b/app/package.json index 6b95a83aa..adcbe10a0 100644 --- a/app/package.json +++ b/app/package.json @@ -9,7 +9,7 @@ "build-parameter-dependencies": "python3 scripts/build-parameter-dependency-map.py", "typecheck": "tsc --noEmit", "lint": "bun run eslint && bun run stylelint", - "eslint": "eslint . --cache", + "eslint": "eslint . --cache --max-warnings 0", "stylelint": "stylelint '**/*.css' --cache", "prettier": "prettier --check \"**/*.{ts,tsx}\"", "prettier:write": "prettier --write \"**/*.{ts,tsx}\"", diff --git a/bun.lock b/bun.lock index 7da0225ac..abb341c13 100644 --- a/bun.lock +++ b/bun.lock @@ -122,13 +122,17 @@ "tailwindcss": "^4.2.0", }, "devDependencies": { + "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4.1.8", "@types/node": "^22.15.0", "@types/react": "^19.1.0", "@types/react-dom": "^19.1.0", "@types/react-plotly.js": "^2.6.3", "@types/react-simple-maps": "^3.0.6", + "eslint": "^9.29.0", + "eslint-config-next": "^15.3.3", "postcss": "^8.5.4", + "prettier": "^3.5.3", "typescript": "^5.8.0", }, }, @@ -155,6 +159,7 @@ "tailwind-merge": "^3.5.0", }, "devDependencies": { + "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4.1.8", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", @@ -169,6 +174,7 @@ "fast-check": "^4.10.2", "jsdom": "^26.1.0", "postcss": "^8.5.4", + "prettier": "^3.5.3", "tailwindcss": "^4.1.8", "typescript": "^5.8.0", "vite-tsconfig-paths": "^4.3.2", diff --git a/calculator-app/.prettierignore b/calculator-app/.prettierignore new file mode 100644 index 000000000..a0241161d --- /dev/null +++ b/calculator-app/.prettierignore @@ -0,0 +1,2 @@ +# Symlinks into app/public, which holds built assets. +public/ diff --git a/calculator-app/eslint.config.mjs b/calculator-app/eslint.config.mjs new file mode 100644 index 000000000..db99e8084 --- /dev/null +++ b/calculator-app/eslint.config.mjs @@ -0,0 +1,58 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { FlatCompat } from "@eslint/eslintrc"; +import { defineConfig, globalIgnores } from "eslint/config"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +// @next/eslint-plugin-next reads settings.next.rootDir as a glob. Brackets +// make (, ), {, }, |, + and [ literal, so a parent folder named like +// "Projects (1)" doesn't stop the path matching. When rootDir matches +// nothing, @next/next/no-html-link-for-pages switches itself off without +// failing lint. +const rootDir = __dirname.replace(/[(){}|+[]/g, "[$&]"); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +// The Next.js rules the website uses. ESLint lints only this folder; the +// shared code it imports from ../app/src is linted by app/'s own config. +export default defineConfig([ + ...compat.config({ + extends: ["next/core-web-vitals", "next/typescript"], + settings: { + next: { + // Absolute, so the rule finds src/app whichever folder ESLint runs + // from. + rootDir, + }, + }, + }), + { + // The calculator's routes are written in app/'s TypeScript style, so + // they follow app/'s rules for `any` and unused variables. + rules: { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + args: "all", + argsIgnorePattern: "^_", + caughtErrors: "none", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + ignoreRestSiblings: true, + }, + ], + }, + }, + globalIgnores([ + ".next/**", + "out/**", + "build/**", + // A link to app/public, which holds built assets. + "public/**", + "next-env.d.ts", + ]), +]); diff --git a/calculator-app/next.config.ts b/calculator-app/next.config.ts index 6a6c7ad82..e0f20f38f 100644 --- a/calculator-app/next.config.ts +++ b/calculator-app/next.config.ts @@ -39,10 +39,9 @@ const nextConfig: NextConfig = { ]; }, - webpack: (config, { dev }) => { + webpack: (config, { dev, webpack }) => { // Polyfill import.meta.env for shared code written for Vite. // These are replaced at build time — no runtime cost. - const webpack = require("webpack"); config.plugins.push( new webpack.DefinePlugin({ "import.meta.env.DEV": JSON.stringify(dev), diff --git a/calculator-app/package.json b/calculator-app/package.json index 4265aa079..6a531f392 100644 --- a/calculator-app/package.json +++ b/calculator-app/package.json @@ -6,6 +6,10 @@ "dev": "sh -c 'next dev --port ${PORT:-3001}'", "build": "next build", "start": "next start", + "lint": "eslint . --max-warnings 0", + "lint:fix": "eslint . --fix", + "format": "prettier --write \"**/*.{ts,tsx,mjs,css}\"", + "format:check": "prettier --check \"**/*.{ts,tsx,mjs,css}\"", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -38,13 +42,17 @@ "tailwindcss": "^4.2.0" }, "devDependencies": { + "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4.1.8", "@types/node": "^22.15.0", "@types/react": "^19.1.0", "@types/react-dom": "^19.1.0", "@types/react-plotly.js": "^2.6.3", "@types/react-simple-maps": "^3.0.6", + "eslint": "^9.29.0", + "eslint-config-next": "^15.3.3", "postcss": "^8.5.4", + "prettier": "^3.5.3", "typescript": "^5.8.0" } } diff --git a/calculator-app/postcss.config.mjs b/calculator-app/postcss.config.mjs index c2ddf7482..7059fe95a 100644 --- a/calculator-app/postcss.config.mjs +++ b/calculator-app/postcss.config.mjs @@ -1,5 +1,6 @@ -export default { +const config = { plugins: { "@tailwindcss/postcss": {}, }, }; +export default config; diff --git a/calculator-app/prettier.config.mjs b/calculator-app/prettier.config.mjs new file mode 100644 index 000000000..89d239f1c --- /dev/null +++ b/calculator-app/prettier.config.mjs @@ -0,0 +1,15 @@ +// The calculator uses Prettier's default style; app/ has its own config. +// Having a config file here stops Prettier looking for one in parent +// folders. The options below are Prettier's defaults, written out because +// an .editorconfig in a parent folder (one outside this repo, such as a +// home folder) can otherwise change them. +/** @type {import("prettier").Config} */ +const config = { + useTabs: false, + tabWidth: 2, + printWidth: 80, + singleQuote: false, + endOfLine: "lf", +}; + +export default config; diff --git a/calculator-app/src/app/[countryId]/(calculator)/error.tsx b/calculator-app/src/app/[countryId]/(calculator)/error.tsx index f91446d05..e33e75f1d 100644 --- a/calculator-app/src/app/[countryId]/(calculator)/error.tsx +++ b/calculator-app/src/app/[countryId]/(calculator)/error.tsx @@ -33,10 +33,16 @@ export default function CountryError({ > Try again + {/* A full page load rather than a client-side navigation, so the + client state that may have caused the error is discarded. */} + {/* eslint-disable-next-line @next/next/no-html-link-for-pages */} Go to reports diff --git a/calculator-app/src/app/[countryId]/(calculator)/loading.tsx b/calculator-app/src/app/[countryId]/(calculator)/loading.tsx index 42f177637..ad8ee7d82 100644 --- a/calculator-app/src/app/[countryId]/(calculator)/loading.tsx +++ b/calculator-app/src/app/[countryId]/(calculator)/loading.tsx @@ -1,6 +1,13 @@ export default function CountryLoading() { return ( -
+
import("@/pages/flagship/Report.page"), { - ssr: false, -}); +const FlagshipReportPage = dynamic( + () => import("@/pages/flagship/Report.page"), + { + ssr: false, + }, +); export default function FlagshipReportRoute({ params, diff --git a/calculator-app/src/app/error.tsx b/calculator-app/src/app/error.tsx index 97516b976..9e911421b 100644 --- a/calculator-app/src/app/error.tsx +++ b/calculator-app/src/app/error.tsx @@ -33,10 +33,16 @@ export default function GlobalError({ > Try again + {/* A full page load rather than a client-side navigation, so the + client state that may have caused the error is discarded. */} + {/* eslint-disable-next-line @next/next/no-html-link-for-pages */} Go to reports diff --git a/docs/engineering/skills/repository-guidance.md b/docs/engineering/skills/repository-guidance.md index ccf333947..5b4077899 100644 --- a/docs/engineering/skills/repository-guidance.md +++ b/docs/engineering/skills/repository-guidance.md @@ -123,6 +123,6 @@ No route embeds a GitHub Pages site as a whole page any more: the 2025 year in r ## Before committing -1. Run `cd app && bun run prettier -- --write .` to format -2. Run `bun run lint` to check for errors -3. CI uses `--max-warnings 0` so fix all warnings +1. Run `make format` to format `app/`, `website/` and `calculator-app/` with Prettier. `app/` has its own Prettier config; `website/` and `calculator-app/` use Prettier's defaults. +2. Run `make lint` to run ESLint (and Stylelint in `app/`) in all three. +3. CI fails on ESLint warnings as well as errors (`--max-warnings 0`), and on files Prettier would reformat, so fix all of them. diff --git a/website/.prettierignore b/website/.prettierignore new file mode 100644 index 000000000..a0241161d --- /dev/null +++ b/website/.prettierignore @@ -0,0 +1,2 @@ +# Symlinks into app/public, which holds built assets. +public/ diff --git a/website/eslint.config.mjs b/website/eslint.config.mjs index dfd8f685e..6be06c078 100644 --- a/website/eslint.config.mjs +++ b/website/eslint.config.mjs @@ -5,6 +5,13 @@ import { defineConfig, globalIgnores } from "eslint/config"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); +// @next/eslint-plugin-next reads settings.next.rootDir as a glob. Brackets +// make (, ), {, }, |, + and [ literal, so a parent folder named like +// "Projects (1)" doesn't stop the path matching. When rootDir matches +// nothing, @next/next/no-html-link-for-pages switches itself off without +// failing lint. +const rootDir = __dirname.replace(/[(){}|+[]/g, "[$&]"); + const compat = new FlatCompat({ baseDirectory: __dirname, }); @@ -14,15 +21,14 @@ export default defineConfig([ extends: ["next/core-web-vitals", "next/typescript"], settings: { next: { - rootDir: "website/", + // Absolute, so the rule finds src/app whichever folder ESLint runs + // from. A relative "website/" matched nothing when run from website/. + rootDir, }, }, }), { - files: [ - "src/app/layout.tsx", - "src/components/ui/OptimisedImage.tsx", - ], + files: ["src/app/layout.tsx", "src/components/ui/OptimisedImage.tsx"], rules: { "@next/next/no-img-element": "off", "@next/next/no-page-custom-font": "off", @@ -33,6 +39,9 @@ export default defineConfig([ "out/**", "build/**", "coverage/**", + // A link to app/public; scripts/build.sh swaps in a copy of its built + // assets during a build. + "public/**", "next-env.d.ts", ]), ]); diff --git a/website/package.json b/website/package.json index 5c3dcabf1..65fa09062 100644 --- a/website/package.json +++ b/website/package.json @@ -7,8 +7,10 @@ "build": "bash scripts/build.sh", "build:next": "next build", "start": "next start", - "lint": "eslint .", + "lint": "eslint . --max-warnings 0", "lint:fix": "eslint . --fix", + "format": "prettier --write \"**/*.{ts,tsx,mjs,css}\"", + "format:check": "prettier --check \"**/*.{ts,tsx,mjs,css}\"", "typecheck": "tsc --noEmit", "test": "vitest run", "test:watch": "vitest" @@ -33,6 +35,7 @@ "tailwind-merge": "^3.5.0" }, "devDependencies": { + "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4.1.8", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", @@ -47,6 +50,7 @@ "fast-check": "^4.10.2", "jsdom": "^26.1.0", "postcss": "^8.5.4", + "prettier": "^3.5.3", "tailwindcss": "^4.1.8", "typescript": "^5.8.0", "vite-tsconfig-paths": "^4.3.2", diff --git a/website/prettier.config.mjs b/website/prettier.config.mjs new file mode 100644 index 000000000..e8f3eccc7 --- /dev/null +++ b/website/prettier.config.mjs @@ -0,0 +1,15 @@ +// The website uses Prettier's default style; app/ has its own config. +// Having a config file here stops Prettier looking for one in parent +// folders. The options below are Prettier's defaults, written out because +// an .editorconfig in a parent folder (one outside this repo, such as a +// home folder) can otherwise change them. +/** @type {import("prettier").Config} */ +const config = { + useTabs: false, + tabWidth: 2, + printWidth: 80, + singleQuote: false, + endOfLine: "lf", +}; + +export default config; diff --git a/website/src/__tests__/app/api/geolocate/route.test.ts b/website/src/__tests__/app/api/geolocate/route.test.ts index df347933d..3ef988fb2 100644 --- a/website/src/__tests__/app/api/geolocate/route.test.ts +++ b/website/src/__tests__/app/api/geolocate/route.test.ts @@ -99,9 +99,11 @@ describe("GET /api/geolocate", () => { test("given upstream 5xx then 502 with empty body (no key leak)", async () => { // Given process.env.IPAPI_CO_KEY = "secret-test-key"; - global.fetch = vi.fn().mockResolvedValue( - new Response("Internal Server Error", { status: 500 }), - ); + global.fetch = vi + .fn() + .mockResolvedValue( + new Response("Internal Server Error", { status: 500 }), + ); // When const response = await GET(); diff --git a/website/src/__tests__/components/header.test.tsx b/website/src/__tests__/components/header.test.tsx index 39e6f9ca4..659b837b0 100644 --- a/website/src/__tests__/components/header.test.tsx +++ b/website/src/__tests__/components/header.test.tsx @@ -23,7 +23,9 @@ describe("Header", () => { test("does not force desktop navigation to display via inline styles", () => { render(
); - expect(screen.getByTestId("desktop-nav")).not.toHaveStyle({ display: "flex" }); + expect(screen.getByTestId("desktop-nav")).not.toHaveStyle({ + display: "flex", + }); expect(screen.getByTestId("mobile-controls")).toBeInTheDocument(); }); diff --git a/website/src/__tests__/config/eslint-config.test.ts b/website/src/__tests__/config/eslint-config.test.ts new file mode 100644 index 000000000..1e06d1f96 --- /dev/null +++ b/website/src/__tests__/config/eslint-config.test.ts @@ -0,0 +1,21 @@ +// @vitest-environment node +import { fileURLToPath } from "node:url"; +import { ESLint } from "eslint"; +import { describe, expect, test } from "vitest"; + +const websiteDir = fileURLToPath(new URL("../../..", import.meta.url)); + +// @next/next/no-html-link-for-pages switches itself off, without failing +// lint, when settings.next.rootDir in eslint.config.mjs matches no folder. +describe("website ESLint config", () => { + test("reports a plain link to a website page", async () => { + const eslint = new ESLint({ cwd: websiteDir }); + const [result] = await eslint.lintText( + 'export default function Probe() {\n return Research;\n}\n', + { filePath: `${websiteDir}src/app/probe.tsx` }, + ); + expect(result.messages.map((message) => message.ruleId)).toContain( + "@next/next/no-html-link-for-pages", + ); + }, 60_000); +}); diff --git a/website/src/app/[countryId]/brand/assets/page.tsx b/website/src/app/[countryId]/brand/assets/page.tsx index fcc41dd36..140ac5944 100644 --- a/website/src/app/[countryId]/brand/assets/page.tsx +++ b/website/src/app/[countryId]/brand/assets/page.tsx @@ -2,11 +2,7 @@ import type { Metadata } from "next"; import Link from "next/link"; import { IconCheck, IconX } from "@tabler/icons-react"; import { Button, Text, Title } from "@/components/ui"; -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; export const metadata: Metadata = { title: "Assets", @@ -81,13 +77,7 @@ function LogoCard({ ); } -function UsageCard({ - type, - items, -}: { - type: "do" | "dont"; - items: string[]; -}) { +function UsageCard({ type, items }: { type: "do" | "dont"; items: string[] }) { const isDo = type === "do"; return (
- - - - - {copied ? "Copied!" : "Click to copy"} - + + + + + {copied ? "Copied!" : "Click to copy"} + ); } @@ -296,9 +297,7 @@ export default function BrandDesignClient() { Tabler Icons . Import from{" "} - - @tabler/icons-react - + @tabler/icons-react .

diff --git a/website/src/app/[countryId]/brand/page.tsx b/website/src/app/[countryId]/brand/page.tsx index b8253fe6d..bab6667d6 100644 --- a/website/src/app/[countryId]/brand/page.tsx +++ b/website/src/app/[countryId]/brand/page.tsx @@ -2,11 +2,7 @@ import type { Metadata } from "next"; import Link from "next/link"; import HeroSection from "@/components/static/HeroSection"; import { Text, Title } from "@/components/ui"; -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; export const metadata: Metadata = { title: "Brand", diff --git a/website/src/app/[countryId]/brand/writing/page.tsx b/website/src/app/[countryId]/brand/writing/page.tsx index 4a080ef27..fbf978d74 100644 --- a/website/src/app/[countryId]/brand/writing/page.tsx +++ b/website/src/app/[countryId]/brand/writing/page.tsx @@ -2,11 +2,7 @@ import type { Metadata } from "next"; import Link from "next/link"; import { IconCheck, IconX } from "@tabler/icons-react"; import { Text, Title } from "@/components/ui"; -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; export const metadata: Metadata = { title: "Writing guide", @@ -74,9 +70,7 @@ function ExampleBox({
- How PolicyEngine communicates. Voice, tone, and content guidelines - for research-oriented writing. + How PolicyEngine communicates. Voice, tone, and content guidelines for + research-oriented writing.
@@ -255,16 +243,16 @@ export default function BrandWritingPage() { lineHeight: typography.lineHeight.relaxed, }} > - Always use sentence case for headings, not title case. This - follows the modern standard used by Apple, Google, Slack, Notion, - and GOV.UK. + Always use sentence case for headings, not title case. This follows + the modern standard used by Apple, Google, Slack, Notion, and + GOV.UK.
The rule - Capitalize only the first word of a heading and any proper - nouns. Everything else stays lowercase. + Capitalize only the first word of a heading and any proper nouns. + Everything else stays lowercase.
@@ -295,8 +283,7 @@ export default function BrandWritingPage() {
Proper nouns stay capitalized - Brand names, acronyms, and proper nouns keep their - capitalization. + Brand names, acronyms, and proper nouns keep their capitalization.
@@ -342,9 +329,7 @@ export default function BrandWritingPage() {
  • Navigation labels
  • Button text
  • Form labels
  • -
  • - Documentation titles -
  • +
  • Documentation titles
  • Blog post titles
  • Error messages
  • @@ -371,8 +356,8 @@ export default function BrandWritingPage() {
    Use active voice - Write in active voice. The subject should perform the action, - not receive it. + Write in active voice. The subject should perform the action, not + receive it.
    @@ -438,9 +423,9 @@ export default function BrandWritingPage() {
    Present numbers dispassionately - When presenting results from PolicyEngine, let the data speak - for itself. Avoid adjectives or adverbs that aren't backed - by the numbers themselves. + When presenting results from PolicyEngine, let the data speak for + itself. Avoid adjectives or adverbs that aren't backed by the + numbers themselves.
    @@ -454,13 +439,11 @@ export default function BrandWritingPage() {
    - The policy dramatically slashes poverty by an impressive - 15%. + The policy dramatically slashes poverty by an impressive 15%.
    Benefits skyrocket by a remarkable $2,400/year.
    - The reform has a surprisingly modest cost of just $80 - billion. + The reform has a surprisingly modest cost of just $80 billion.
    diff --git a/website/src/app/[countryId]/dev-tools/api-status/ApiStatusClient.tsx b/website/src/app/[countryId]/dev-tools/api-status/ApiStatusClient.tsx index 2a8ab5526..4dd0ca02b 100644 --- a/website/src/app/[countryId]/dev-tools/api-status/ApiStatusClient.tsx +++ b/website/src/app/[countryId]/dev-tools/api-status/ApiStatusClient.tsx @@ -6,7 +6,13 @@ import { IconCircleCheck, IconCircleX, } from "@tabler/icons-react"; -import { Alert, AlertDescription, AlertTitle, Spinner, Text } from "@/components/ui"; +import { + Alert, + AlertDescription, + AlertTitle, + Spinner, + Text, +} from "@/components/ui"; import { colors, spacing, typography } from "@/designTokens"; import type { DayRecord, @@ -149,9 +155,7 @@ function formatDowntime(minutes: number): string { const hours = Math.floor(minutes / 60); const remainingMinutes = Math.round(minutes % 60); - return remainingMinutes > 0 - ? `${hours}h ${remainingMinutes}m` - : `${hours}h`; + return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`; } function formatDate(dateStr: string): string { @@ -175,9 +179,10 @@ function formatLastUpdated(timestamp: string): string { }); } -function formatMonitorStatus( - status: MonitorStatus, -): { label: string; color: string } { +function formatMonitorStatus(status: MonitorStatus): { + label: string; + color: string; +} { if (status === "up") { return { label: "Operational", color: colors.primary[500] }; } @@ -457,7 +462,9 @@ function useStatusData(): { const response = await fetch( `/api/betterstack?monitors=${MONITOR_IDS.join(",")}`, ); - const data = (await response.json()) as StatusPageData | { error: string }; + const data = (await response.json()) as + | StatusPageData + | { error: string }; if (!response.ok) { throw new Error( diff --git a/website/src/app/[countryId]/dev-tools/api-status/page.tsx b/website/src/app/[countryId]/dev-tools/api-status/page.tsx index c84b89dfa..b1b1b4d3b 100644 --- a/website/src/app/[countryId]/dev-tools/api-status/page.tsx +++ b/website/src/app/[countryId]/dev-tools/api-status/page.tsx @@ -5,7 +5,8 @@ import ApiStatusClient from "./ApiStatusClient"; export const metadata: Metadata = { title: "API status", - description: "Monitor the current status and availability of PolicyEngine API services.", + description: + "Monitor the current status and availability of PolicyEngine API services.", }; export default function ApiStatusPage() { diff --git a/website/src/app/[countryId]/events/iariw-2026/WorkshopContent.tsx b/website/src/app/[countryId]/events/iariw-2026/WorkshopContent.tsx index 7850586bd..a9c642d4a 100644 --- a/website/src/app/[countryId]/events/iariw-2026/WorkshopContent.tsx +++ b/website/src/app/[countryId]/events/iariw-2026/WorkshopContent.tsx @@ -121,9 +121,10 @@ export default function WorkshopContent() { description={ <> A free half-day workshop on open microsimulation, simulation-ready - microdata, and executable law, co-organized by PolicyEngine, the Axiom - Foundation, CAPE, and BEAMM at UCLouvain Saint-Louis, alongside the 39th IARIW General - Conference. Thursday 27 August 2026, 13:00–17:00, Brussels. + microdata, and executable law, co-organized by PolicyEngine, the + Axiom Foundation, CAPE, and BEAMM at UCLouvain Saint-Louis, + alongside the 39th IARIW General Conference. Thursday 27 August + 2026, 13:00–17:00, Brussels. } /> diff --git a/website/src/app/[countryId]/research/ResearchClient.tsx b/website/src/app/[countryId]/research/ResearchClient.tsx index ba1d4ce4e..25274a49d 100644 --- a/website/src/app/[countryId]/research/ResearchClient.tsx +++ b/website/src/app/[countryId]/research/ResearchClient.tsx @@ -32,11 +32,7 @@ import { IconChevronDown, IconSearch, } from "@tabler/icons-react"; -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; import { Button, Checkbox, @@ -150,170 +146,178 @@ export function BlogPostCard({ // to force a full server request instead of client-side routing. const cardClassName = "tw:no-underline tw:text-inherit tw:group"; const cardContent = ( -
    - {/* Image */} -
    - {item.image && ( - { - e.currentTarget.style.display = "none"; - }} - /> - )} - {/* Gradient overlay at bottom of image for depth */} -
    + {/* Image */} +
    + {item.image && ( + { + e.currentTarget.style.display = "none"; }} /> -
    + )} + {/* Gradient overlay at bottom of image for depth */} +
    +
    - {/* Content */} + {/* Content */} +
    + {/* Tags + Date row */}
    - {/* Tags + Date row */} -
    -
    - {displayTags.map((tag, i) => ( - - {tag} - {i < displayTags.length - 1 && ( - - ● - - )} - - ))} -
    - {formattedDate} +
    + {displayTags.map((tag, i) => ( + + {tag} + {i < displayTags.length - 1 && ( + + ● + + )} + + ))}
    + {formattedDate} +
    - {/* Title */} -

    - {item.title} -

    + {/* Title */} +

    + {item.title} +

    - {/* Description or body-search excerpt */} - - {item.searchExcerpt ? ( - <> - “ - {item.searchExcerpt.text.slice( - 0, - item.searchExcerpt.highlightStart, - )} - - {item.searchExcerpt.highlightedText} - - {item.searchExcerpt.text.slice(item.searchExcerpt.highlightEnd)}” - - ) : ( - item.description - )} - + {/* Description or body-search excerpt */} + + {item.searchExcerpt ? ( + <> + “ + {item.searchExcerpt.text.slice( + 0, + item.searchExcerpt.highlightStart, + )} + + {item.searchExcerpt.highlightedText} + + {item.searchExcerpt.text.slice(item.searchExcerpt.highlightEnd)}” + + ) : ( + item.description + )} + - {/* CTA */} -
    - {item.isApp ? "Open" : "Read more"} - -
    + {/* CTA */} +
    + {item.isApp ? "Open" : "Read more"} +
    +
    ); if (item.isApp) { - return
    {cardContent}; + return ( + + {cardContent} + + ); } - return {cardContent}; + return ( + + {cardContent} + + ); } /* ─── BlogPostGrid ─── */ @@ -475,9 +479,7 @@ function FilterSection({ fw={typography.fontWeight.semibold} size="sm" style={{ - color: isExpanded - ? colors.primary[700] - : colors.secondary[800], + color: isExpanded ? colors.primary[700] : colors.secondary[800], }} > {label} @@ -517,9 +519,7 @@ function FilterSection({ style={{ flex: isExpanded ? "1 1 auto" : "0 1 auto", minHeight: 0, - maxHeight: isExpanded - ? `${Math.min(height, maxHeight)}px` - : "0px", + maxHeight: isExpanded ? `${Math.min(height, maxHeight)}px` : "0px", opacity: isExpanded ? 1 : 0, transition: "max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease", @@ -555,7 +555,12 @@ function CheckboxRow({ role="button" tabIndex={0} onClick={onChange} - onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onChange(); } }} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + onChange(); + } + }} style={{ display: "flex", alignItems: "center", @@ -635,8 +640,7 @@ export function ResearchFilters({ availableAuthors, countryId = "us", }: ResearchFiltersProps) { - const [expandedSection, setExpandedSection] = - useState(null); + const [expandedSection, setExpandedSection] = useState(null); const [usStatesExpanded, setUsStatesExpanded] = useState(false); const [availableHeight, setAvailableHeight] = useState(400); const filterContainerRef = useRef(null); @@ -833,8 +837,7 @@ export function ResearchFilters({ colors.primary[50]; }} onMouseLeave={(e) => { - e.currentTarget.style.backgroundColor = - "transparent"; + e.currentTarget.style.backgroundColor = "transparent"; }} > {usStatesExpanded ? "Hide states" : "Show states"} @@ -885,11 +888,7 @@ export function ResearchFilters({ /* ─── Main Client Component ─── */ -export default function ResearchClient({ - countryId, -}: { - countryId: string; -}) { +export default function ResearchClient({ countryId }: { countryId: string }) { const searchParams = useSearchParams(); const router = useRouter(); const pathname = usePathname(); @@ -899,10 +898,7 @@ export default function ResearchClient({ const allItems = useMemo(() => getResearchItems(), []); // Default locations based on country - const defaultLocations = useMemo( - () => [countryId, "global"], - [countryId], - ); + const defaultLocations = useMemo(() => [countryId, "global"], [countryId]); // Filter state - initialize from URL params const [searchQuery, setSearchQuery] = useState( @@ -1081,25 +1077,19 @@ export default function ResearchClient({
    {/* Sidebar Filters */}
    ) : filteredItems.length > 0 ? ( <> - + {/* Sentinel element for infinite scroll */} {hasMore && ( diff --git a/website/src/app/[countryId]/research/[slug]/ArticleClient.tsx b/website/src/app/[countryId]/research/[slug]/ArticleClient.tsx index 84d100d1a..fd8f68221 100644 --- a/website/src/app/[countryId]/research/[slug]/ArticleClient.tsx +++ b/website/src/app/[countryId]/research/[slug]/ArticleClient.tsx @@ -27,11 +27,7 @@ import { import { extractMarkdownFromNotebook } from "@/lib/notebookUtils"; import { formatPostDate } from "@/lib/postDate"; import authorsData from "@/data/posts/authors.json"; -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; const authors = authorsData as AuthorsCollection; @@ -229,11 +225,7 @@ function PostHeading({ > {post.title} - + {post.description}
    ; diff --git a/website/src/components/Footer.tsx b/website/src/components/Footer.tsx index 195dd3ccf..3274c1b9d 100644 --- a/website/src/components/Footer.tsx +++ b/website/src/components/Footer.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from 'react'; +import { useState } from "react"; import { IconBrandFacebook, IconBrandGithub, @@ -9,13 +9,13 @@ import { IconBrandTwitter, IconBrandYoutube, IconMail, -} from '@tabler/icons-react'; -import jsonp from 'jsonp'; -import OptimisedImage from '@/components/ui/OptimisedImage'; -import { Button, Container, Input, Spinner, Stack } from '@/components/ui'; -import { colors, spacing, typography } from '@/designTokens'; -import { cn } from '@/lib/utils'; -import { useCountryId } from '@/hooks/useCountryId'; +} from "@tabler/icons-react"; +import jsonp from "jsonp"; +import OptimisedImage from "@/components/ui/OptimisedImage"; +import { Button, Container, Input, Spinner, Stack } from "@/components/ui"; +import { colors, spacing, typography } from "@/designTokens"; +import { cn } from "@/lib/utils"; +import { useCountryId } from "@/hooks/useCountryId"; /* ── GA4 helpers (inlined; no app-only deps) ── */ @@ -26,17 +26,17 @@ declare global { } function trackEvent(eventName: string, params?: Record) { - if (typeof window !== 'undefined' && window.gtag) { - window.gtag('event', eventName, params); + if (typeof window !== "undefined" && window.gtag) { + window.gtag("event", eventName, params); } } function trackContactClicked() { - trackEvent('contact_clicked'); + trackEvent("contact_clicked"); } function trackNewsletterSignup() { - trackEvent('newsletter_signup'); + trackEvent("newsletter_signup"); } /* ── Mailchimp helper (inlined; uses jsonp from deps) ── */ @@ -47,33 +47,39 @@ interface SubscriptionResult { } const MAILCHIMP_URL = - 'https://policyengine.us5.list-manage.com/subscribe/post-json?u=e5ad35332666289a0f48013c5&id=71ed1f89d8&f_id=00f173e6f0'; + "https://policyengine.us5.list-manage.com/subscribe/post-json?u=e5ad35332666289a0f48013c5&id=71ed1f89d8&f_id=00f173e6f0"; function submitToMailchimp(email: string): Promise { return new Promise((resolve, reject) => { const encodedEmail = encodeURIComponent(email); - jsonp(`${MAILCHIMP_URL}&EMAIL=${encodedEmail}`, { param: 'c' }, (error, data) => { - if (error) { - reject( - new Error('There was an issue processing your subscription; please try again later.') - ); - return; - } + jsonp( + `${MAILCHIMP_URL}&EMAIL=${encodedEmail}`, + { param: "c" }, + (error, data) => { + if (error) { + reject( + new Error( + "There was an issue processing your subscription; please try again later.", + ), + ); + return; + } - if (data) { - const { msg } = data; - resolve({ - isSuccessful: data.result !== 'error', - message: msg, - }); - } - }); + if (data) { + const { msg } = data; + resolve({ + isSuccessful: data.result !== "error", + message: msg, + }); + } + }, + ); }); } /* ── Constants ── */ -const PolicyEngineLogo = '/assets/logos/policyengine/white.svg'; +const PolicyEngineLogo = "/assets/logos/policyengine/white.svg"; const getContactLinks = (countryId: string) => ({ about: `/${countryId}/team`, @@ -84,57 +90,75 @@ const getContactLinks = (countryId: string) => ({ }); const SOCIAL_LINKS = [ - { icon: IconMail, href: 'mailto:hello@policyengine.org', label: 'Email' }, - { icon: IconBrandTwitter, href: 'https://twitter.com/ThePolicyEngine', label: 'Twitter' }, - { icon: IconBrandFacebook, href: 'https://www.facebook.com/PolicyEngine', label: 'Facebook' }, + { icon: IconMail, href: "mailto:hello@policyengine.org", label: "Email" }, + { + icon: IconBrandTwitter, + href: "https://twitter.com/ThePolicyEngine", + label: "Twitter", + }, + { + icon: IconBrandFacebook, + href: "https://www.facebook.com/PolicyEngine", + label: "Facebook", + }, { icon: IconBrandLinkedin, - href: 'https://www.linkedin.com/company/thepolicyengine', - label: 'LinkedIn', + href: "https://www.linkedin.com/company/thepolicyengine", + label: "LinkedIn", + }, + { + icon: IconBrandYoutube, + href: "https://www.youtube.com/@policyengine", + label: "YouTube", }, - { icon: IconBrandYoutube, href: 'https://www.youtube.com/@policyengine', label: 'YouTube' }, { icon: IconBrandInstagram, - href: 'https://www.instagram.com/PolicyEngine/', - label: 'Instagram', + href: "https://www.instagram.com/PolicyEngine/", + label: "Instagram", + }, + { + icon: IconBrandGithub, + href: "https://github.com/PolicyEngine", + label: "GitHub", }, - { icon: IconBrandGithub, href: 'https://github.com/PolicyEngine', label: 'GitHub' }, ]; /* ── FooterSubscribe ── */ function FooterSubscribe() { - const [email, setEmail] = useState(''); - const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle'); - const [message, setMessage] = useState(''); + const [email, setEmail] = useState(""); + const [status, setStatus] = useState< + "idle" | "loading" | "success" | "error" + >("idle"); + const [message, setMessage] = useState(""); const handleSubscribe = async () => { if (!email) { - setStatus('error'); - setMessage('Please enter a valid email address.'); + setStatus("error"); + setMessage("Please enter a valid email address."); return; } - setStatus('loading'); - setMessage(''); + setStatus("loading"); + setMessage(""); try { const result = await submitToMailchimp(email); if (result.isSuccessful) { trackNewsletterSignup(); - setStatus('success'); + setStatus("success"); setMessage(result.message); - setEmail(''); + setEmail(""); } else { - setStatus('error'); + setStatus("error"); setMessage(result.message); } } catch (error) { - setStatus('error'); + setStatus("error"); setMessage( error instanceof Error ? error.message - : 'There was an issue processing your subscription; please try again later.' + : "There was an issue processing your subscription; please try again later.", ); } }; @@ -143,37 +167,37 @@ function FooterSubscribe() {

    Subscribe to PolicyEngine

    Get the latest posts delivered right to your inbox.

    - + setEmail(event.currentTarget.value)} - disabled={status === 'loading'} + disabled={status === "loading"} /> {message && (

    {message} @@ -194,7 +218,7 @@ export default function Footer() { data-testid="site-footer" className="tw:w-full" style={{ - padding: `${spacing['4xl']} ${spacing['5xl']}`, + padding: `${spacing["4xl"]} ${spacing["5xl"]}`, background: `linear-gradient(to right, ${colors.primary[800]}, ${colors.primary[600]})`, }} > @@ -206,16 +230,16 @@ export default function Footer() { />

    {[ - { href: CONTACT_LINKS.about, text: 'About us' }, - { href: CONTACT_LINKS.donate, text: 'Donate' }, - { href: CONTACT_LINKS.devTools, text: 'Developer tools' }, - { href: CONTACT_LINKS.privacy, text: 'Privacy policy' }, - { href: CONTACT_LINKS.terms, text: 'Terms and conditions' }, + { href: CONTACT_LINKS.about, text: "About us" }, + { href: CONTACT_LINKS.donate, text: "Donate" }, + { href: CONTACT_LINKS.devTools, text: "Developer tools" }, + { href: CONTACT_LINKS.privacy, text: "Privacy policy" }, + { href: CONTACT_LINKS.terms, text: "Terms and conditions" }, ].map(({ href, text }) => ( ))}

    - © {new Date().getFullYear()} PolicyEngine. All rights reserved. + © {new Date().getFullYear()} PolicyEngine. All rights + reserved.

    diff --git a/website/src/components/blog/blogStyles.ts b/website/src/components/blog/blogStyles.ts index 3b3b702a5..1b4aff346 100644 --- a/website/src/components/blog/blogStyles.ts +++ b/website/src/components/blog/blogStyles.ts @@ -4,11 +4,7 @@ * for markdown rendering in blog posts. */ -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; export const blogColors = { primary: colors.primary[600], diff --git a/website/src/components/home/FeaturedResearchBanner.tsx b/website/src/components/home/FeaturedResearchBanner.tsx index 2c600697f..300eac522 100644 --- a/website/src/components/home/FeaturedResearchBanner.tsx +++ b/website/src/components/home/FeaturedResearchBanner.tsx @@ -2,11 +2,7 @@ import { useState } from "react"; import { IconArrowRight, IconX } from "@tabler/icons-react"; -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; const BANNER_DISMISSED_KEY = "featured-research-banner-dismissed"; diff --git a/website/src/components/home/HeroCTA.tsx b/website/src/components/home/HeroCTA.tsx index b79475e84..b77cda8ff 100644 --- a/website/src/components/home/HeroCTA.tsx +++ b/website/src/components/home/HeroCTA.tsx @@ -3,11 +3,7 @@ import { motion } from "framer-motion"; const CALCULATOR_URL = process.env.NEXT_PUBLIC_CALCULATOR_URL || "https://app.policyengine.org"; -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; const ctaVariant = { hidden: { opacity: 0, y: 20 }, diff --git a/website/src/components/home/HeroSection.tsx b/website/src/components/home/HeroSection.tsx index fb56d7b40..4f1ba53ba 100644 --- a/website/src/components/home/HeroSection.tsx +++ b/website/src/components/home/HeroSection.tsx @@ -2,11 +2,7 @@ import { useCallback, useMemo, useRef, useState } from "react"; import { motion, useReducedMotion } from "framer-motion"; -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; import HeroCTA from "./HeroCTA"; import HouseholdGraph, { generateGraph, diff --git a/website/src/components/home/HomeTrackerPreview.tsx b/website/src/components/home/HomeTrackerPreview.tsx index 030042d37..e63c90158 100644 --- a/website/src/components/home/HomeTrackerPreview.tsx +++ b/website/src/components/home/HomeTrackerPreview.tsx @@ -1,11 +1,7 @@ "use client"; import Link from "next/link"; -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; export default function HomeTrackerPreview({ countryId, diff --git a/website/src/components/home/OrgLogos.tsx b/website/src/components/home/OrgLogos.tsx index a1b0c7c5b..61657bfcd 100644 --- a/website/src/components/home/OrgLogos.tsx +++ b/website/src/components/home/OrgLogos.tsx @@ -8,11 +8,7 @@ import { getOrgsForCountry, Organization, } from "@/data/organizations"; -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; const LOGO_WIDTH = 140; const LOGO_GAP = 64; diff --git a/website/src/components/home/PE84Banner.tsx b/website/src/components/home/PE84Banner.tsx deleted file mode 100644 index 760d508d8..000000000 --- a/website/src/components/home/PE84Banner.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { colors, typography } from "@/designTokens"; - -export default function PE84Banner() { - return ( - - Introducing the PE-84 — The graphing microsimulator.{" "} - - Learn more → - - - ); -} diff --git a/website/src/components/home/TypewriterPrompt.tsx b/website/src/components/home/TypewriterPrompt.tsx index acb71e7bb..731bb705e 100644 --- a/website/src/components/home/TypewriterPrompt.tsx +++ b/website/src/components/home/TypewriterPrompt.tsx @@ -1,11 +1,7 @@ "use client"; import { useCallback, useEffect, useRef, useState } from "react"; -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; export interface PromptData { text: string; diff --git a/website/src/components/static/ActionButton.tsx b/website/src/components/static/ActionButton.tsx index edb8f026c..e4e79313f 100644 --- a/website/src/components/static/ActionButton.tsx +++ b/website/src/components/static/ActionButton.tsx @@ -2,11 +2,7 @@ import { useEffect, useRef, useState } from "react"; import { Text } from "@/components/ui"; -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; export interface ActionButtonProps { text: string; diff --git a/website/src/components/static/BulletList.tsx b/website/src/components/static/BulletList.tsx index a73251ad9..851322dd0 100644 --- a/website/src/components/static/BulletList.tsx +++ b/website/src/components/static/BulletList.tsx @@ -1,9 +1,5 @@ import { Text } from "@/components/ui"; -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; export interface BulletItem { title: string; diff --git a/website/src/components/static/CTASection.tsx b/website/src/components/static/CTASection.tsx index a4f3e87fb..c5385d3ea 100644 --- a/website/src/components/static/CTASection.tsx +++ b/website/src/components/static/CTASection.tsx @@ -1,10 +1,6 @@ import { ReactNode } from "react"; import { Container, Title } from "@/components/ui"; -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; import ActionButton, { ActionButtonProps } from "./ActionButton"; export interface CTASectionProps { @@ -63,9 +59,7 @@ export default function CTASection({ )}
    -
    - {content} -
    +
    {content}
    {title} {sections.map((section, index) => ( -
    +
    {project.title} @@ -73,8 +73,8 @@ export default function SupportedProject({ project }: SupportedProjectProps) { fontFamily: typography.fontFamily.body, }} > - <strong>Award:</strong> {project.currency === 'GBP' ? '\u00a3' : '$'} - {project.amount.toLocaleString('en-US')} + <strong>Award:</strong> {project.currency === "GBP" ? "\u00a3" : "$"} + {project.amount.toLocaleString("en-US")} </span> <span style={{ diff --git a/website/src/components/static/SupporterCard.tsx b/website/src/components/static/SupporterCard.tsx index 8926e5df4..db2c371cc 100644 --- a/website/src/components/static/SupporterCard.tsx +++ b/website/src/components/static/SupporterCard.tsx @@ -1,5 +1,7 @@ -import OptimisedImage from '@/components/ui/OptimisedImage'; -import SupportedProject, { SupportedProject as SupportedProjectType } from './SupportedProject'; +import OptimisedImage from "@/components/ui/OptimisedImage"; +import SupportedProject, { + SupportedProject as SupportedProjectType, +} from "./SupportedProject"; export interface Supporter { id: string; @@ -14,7 +16,10 @@ export interface SupporterCardProps { projects: SupportedProjectType[]; } -export default function SupporterCard({ supporter, projects }: SupporterCardProps) { +export default function SupporterCard({ + supporter, + projects, +}: SupporterCardProps) { return ( <div className="tw:mb-4xl tw:p-2xl tw:border tw:border-border-light tw:rounded-element tw:shadow-[0_2px_4px_rgba(0,0,0,0.05)]"> <div className="tw:flex tw:flex-col tw:sm:flex-row tw:sm:items-center tw:gap-lg tw:mb-2xl"> @@ -34,7 +39,10 @@ export default function SupporterCard({ supporter, projects }: SupporterCardProp </a> ) : ( // Keep the text column aligned with cards that have a logo. - <div aria-hidden="true" className="tw:hidden tw:sm:block tw:shrink-0 tw:w-[200px]" /> + <div + aria-hidden="true" + className="tw:hidden tw:sm:block tw:shrink-0 tw:w-[200px]" + /> )} <p className="tw:flex-1 tw:min-w-0 tw:text-base tw:leading-relaxed"> {supporter.websiteUrl ? ( @@ -48,7 +56,7 @@ export default function SupporterCard({ supporter, projects }: SupporterCardProp </a> ) : ( <span className="tw:font-bold">{supporter.name}</span> - )}{' '} + )}{" "} — {supporter.description} </p> </div> diff --git a/website/src/components/static/TeamMemberCard.tsx b/website/src/components/static/TeamMemberCard.tsx index c54edf1b8..573791a6f 100644 --- a/website/src/components/static/TeamMemberCard.tsx +++ b/website/src/components/static/TeamMemberCard.tsx @@ -1,5 +1,5 @@ -import OptimisedImage from '@/components/ui/OptimisedImage'; -import { cn } from '@/lib/utils'; +import OptimisedImage from "@/components/ui/OptimisedImage"; +import { cn } from "@/lib/utils"; export interface TeamMember { name: string; @@ -9,11 +9,14 @@ export interface TeamMember { export interface TeamMemberCardProps { member: TeamMember; - variant?: 'default' | 'inverted'; + variant?: "default" | "inverted"; } -export default function TeamMemberCard({ member, variant = 'default' }: TeamMemberCardProps) { - const isInverted = variant === 'inverted'; +export default function TeamMemberCard({ + member, + variant = "default", +}: TeamMemberCardProps) { + const isInverted = variant === "inverted"; return ( <div className="tw:grid tw:grid-cols-1 tw:sm:grid-cols-[auto_1fr] tw:items-stretch tw:gap-[5vw] tw:mt-[50px]"> @@ -27,19 +30,22 @@ export default function TeamMemberCard({ member, variant = 'default' }: TeamMemb <div className={cn( - 'tw:h-full tw:pb-[50px] tw:border-b', - isInverted ? 'tw:border-white' : 'tw:border-black' + "tw:h-full tw:pb-[50px] tw:border-b", + isInverted ? "tw:border-white" : "tw:border-black", )} > <p className={cn( - 'tw:text-base tw:leading-relaxed', - isInverted ? 'tw:text-text-inverse' : 'tw:text-text-primary' + "tw:text-base tw:leading-relaxed", + isInverted ? "tw:text-text-inverse" : "tw:text-text-primary", )} > - <span className="tw:font-semibold tw:uppercase" style={{ letterSpacing: '0.05em' }}> + <span + className="tw:font-semibold tw:uppercase" + style={{ letterSpacing: "0.05em" }} + > {member.name} - </span>{' '} + </span>{" "} {member.bio} </p> </div> diff --git a/website/src/components/static/TeamSection.tsx b/website/src/components/static/TeamSection.tsx index a75147257..6a9e1d9e6 100644 --- a/website/src/components/static/TeamSection.tsx +++ b/website/src/components/static/TeamSection.tsx @@ -1,9 +1,5 @@ import { Container, Title } from "@/components/ui"; -import { - colors, - spacing, - typography, -} from "@/designTokens"; +import { colors, spacing, typography } from "@/designTokens"; import TeamMemberCard, { TeamMember } from "./TeamMemberCard"; export interface TeamSectionProps { diff --git a/website/src/components/ui/Container.tsx b/website/src/components/ui/Container.tsx index 9118de63f..a65c20fb4 100644 --- a/website/src/components/ui/Container.tsx +++ b/website/src/components/ui/Container.tsx @@ -1,28 +1,28 @@ -import React from 'react'; -import { spacing } from '@/designTokens'; -import { cn } from '@/lib/utils'; +import React from "react"; +import { spacing } from "@/designTokens"; +import { cn } from "@/lib/utils"; const sizeMap = { - xs: 'tw:max-w-screen-xs', - sm: 'tw:max-w-screen-sm', - md: 'tw:max-w-screen-md', - lg: 'tw:max-w-screen-lg', - xl: 'tw:max-w-screen-xl', - '2xl': 'tw:max-w-screen-2xl', + xs: "tw:max-w-screen-xs", + sm: "tw:max-w-screen-sm", + md: "tw:max-w-screen-md", + lg: "tw:max-w-screen-lg", + xl: "tw:max-w-screen-xl", + "2xl": "tw:max-w-screen-2xl", } as const; interface ContainerProps extends React.HTMLAttributes<HTMLDivElement> { size?: keyof typeof sizeMap; - variant?: 'guttered'; + variant?: "guttered"; } const Container = React.forwardRef<HTMLDivElement, ContainerProps>( ({ className, size, variant, children, style, ...props }, ref) => { const gutteredStyle = - variant === 'guttered' + variant === "guttered" ? { - paddingLeft: spacing.container['2xl'], - paddingRight: spacing.container['2xl'], + paddingLeft: spacing.container["2xl"], + paddingRight: spacing.container["2xl"], paddingTop: spacing.container.lg, paddingBottom: spacing.container.lg, } @@ -32,10 +32,10 @@ const Container = React.forwardRef<HTMLDivElement, ContainerProps>( <div ref={ref} className={cn( - 'tw:mx-auto tw:w-full', - variant !== 'guttered' && 'tw:px-lg', - size ? sizeMap[size] : 'tw:max-w-[976px]', - className + "tw:mx-auto tw:w-full", + variant !== "guttered" && "tw:px-lg", + size ? sizeMap[size] : "tw:max-w-[976px]", + className, )} style={{ ...gutteredStyle, ...style }} {...props} @@ -43,9 +43,9 @@ const Container = React.forwardRef<HTMLDivElement, ContainerProps>( {children} </div> ); - } + }, ); -Container.displayName = 'Container'; +Container.displayName = "Container"; export { Container }; export type { ContainerProps }; diff --git a/website/src/components/ui/Group.tsx b/website/src/components/ui/Group.tsx index 94770f9df..b6e36c1b8 100644 --- a/website/src/components/ui/Group.tsx +++ b/website/src/components/ui/Group.tsx @@ -1,67 +1,76 @@ -import React from 'react'; -import { cn } from '@/lib/utils'; +import React from "react"; +import { cn } from "@/lib/utils"; const gapMap = { - xs: 'tw:gap-1', - sm: 'tw:gap-2', - md: 'tw:gap-3', - lg: 'tw:gap-4', - xl: 'tw:gap-5', - '2xl': 'tw:gap-6', - '3xl': 'tw:gap-8', - '4xl': 'tw:gap-12', - '5xl': 'tw:gap-16', + xs: "tw:gap-1", + sm: "tw:gap-2", + md: "tw:gap-3", + lg: "tw:gap-4", + xl: "tw:gap-5", + "2xl": "tw:gap-6", + "3xl": "tw:gap-8", + "4xl": "tw:gap-12", + "5xl": "tw:gap-16", } as const; const justifyMap = { - start: 'tw:justify-start', - center: 'tw:justify-center', - end: 'tw:justify-end', - 'space-between': 'tw:justify-between', - apart: 'tw:justify-between', + start: "tw:justify-start", + center: "tw:justify-center", + end: "tw:justify-end", + "space-between": "tw:justify-between", + apart: "tw:justify-between", } as const; const alignMap = { - start: 'tw:items-start', - center: 'tw:items-center', - end: 'tw:items-end', - stretch: 'tw:items-stretch', - baseline: 'tw:items-baseline', + start: "tw:items-start", + center: "tw:items-center", + end: "tw:items-end", + stretch: "tw:items-stretch", + baseline: "tw:items-baseline", } as const; interface GroupProps extends React.HTMLAttributes<HTMLDivElement> { gap?: keyof typeof gapMap; justify?: keyof typeof justifyMap; align?: keyof typeof alignMap; - wrap?: 'wrap' | 'nowrap'; + wrap?: "wrap" | "nowrap"; grow?: boolean; } const Group = React.forwardRef<HTMLDivElement, GroupProps>( ( - { className, gap = 'md', justify, align = 'center', wrap = 'wrap', grow, children, ...props }, - ref + { + className, + gap = "md", + justify, + align = "center", + wrap = "wrap", + grow, + children, + ...props + }, + ref, ) => { return ( <div ref={ref} className={cn( - 'tw:flex tw:flex-row', + "tw:flex tw:flex-row", gapMap[gap], - justifyMap[justify ?? 'start'], + justifyMap[justify ?? "start"], alignMap[align], - wrap === 'wrap' ? 'tw:flex-wrap' : 'tw:flex-nowrap', - grow && 'tw:[&>*]:flex-1', - className + wrap === "wrap" ? "tw:flex-wrap" : "tw:flex-nowrap", + grow && "tw:[&>*]:flex-1", + className, )} {...props} > {children} </div> ); - } + }, ); -Group.displayName = 'Group'; +Group.displayName = "Group"; export { Group }; export type { GroupProps }; diff --git a/website/src/components/ui/Spinner.tsx b/website/src/components/ui/Spinner.tsx index a07d57466..dfefaff8b 100644 --- a/website/src/components/ui/Spinner.tsx +++ b/website/src/components/ui/Spinner.tsx @@ -1,10 +1,10 @@ -import React from 'react'; -import { cn } from '@/lib/utils'; +import React from "react"; +import { cn } from "@/lib/utils"; const sizeMap = { - sm: 'tw:h-4 tw:w-4', - md: 'tw:h-8 tw:w-8', - lg: 'tw:h-12 tw:w-12', + sm: "tw:h-4 tw:w-4", + md: "tw:h-8 tw:w-8", + lg: "tw:h-12 tw:w-12", } as const; interface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> { @@ -12,24 +12,24 @@ interface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> { } const Spinner = React.forwardRef<HTMLDivElement, SpinnerProps>( - ({ className, size = 'md', ...props }, ref) => { + ({ className, size = "md", ...props }, ref) => { return ( <div ref={ref} role="status" className={cn( - 'tw:animate-spin tw:rounded-full tw:border-2 tw:border-current tw:border-t-transparent tw:text-primary-500', + "tw:animate-spin tw:rounded-full tw:border-2 tw:border-current tw:border-t-transparent tw:text-primary-500", sizeMap[size], - className + className, )} {...props} > <span className="tw:sr-only">Loading...</span> </div> ); - } + }, ); -Spinner.displayName = 'Spinner'; +Spinner.displayName = "Spinner"; export { Spinner }; export type { SpinnerProps }; diff --git a/website/src/components/ui/Stack.tsx b/website/src/components/ui/Stack.tsx index bf8b5f013..47b75d17d 100644 --- a/website/src/components/ui/Stack.tsx +++ b/website/src/components/ui/Stack.tsx @@ -1,31 +1,31 @@ -import React from 'react'; -import { cn } from '@/lib/utils'; +import React from "react"; +import { cn } from "@/lib/utils"; const gapMap = { - xs: 'tw:gap-1', - sm: 'tw:gap-2', - md: 'tw:gap-3', - lg: 'tw:gap-4', - xl: 'tw:gap-5', - '2xl': 'tw:gap-6', - '3xl': 'tw:gap-8', - '4xl': 'tw:gap-12', - '5xl': 'tw:gap-16', + xs: "tw:gap-1", + sm: "tw:gap-2", + md: "tw:gap-3", + lg: "tw:gap-4", + xl: "tw:gap-5", + "2xl": "tw:gap-6", + "3xl": "tw:gap-8", + "4xl": "tw:gap-12", + "5xl": "tw:gap-16", } as const; const alignMap = { - start: 'tw:items-start', - center: 'tw:items-center', - end: 'tw:items-end', - stretch: 'tw:items-stretch', + start: "tw:items-start", + center: "tw:items-center", + end: "tw:items-end", + stretch: "tw:items-stretch", } as const; const justifyMap = { - start: 'tw:justify-start', - center: 'tw:justify-center', - end: 'tw:justify-end', - 'space-between': 'tw:justify-between', - 'space-around': 'tw:justify-around', + start: "tw:justify-start", + center: "tw:justify-center", + end: "tw:justify-end", + "space-between": "tw:justify-between", + "space-around": "tw:justify-around", } as const; interface StackProps extends React.HTMLAttributes<HTMLDivElement> { @@ -35,25 +35,25 @@ interface StackProps extends React.HTMLAttributes<HTMLDivElement> { } const Stack = React.forwardRef<HTMLDivElement, StackProps>( - ({ className, gap = 'md', align, justify, children, ...props }, ref) => { + ({ className, gap = "md", align, justify, children, ...props }, ref) => { return ( <div ref={ref} className={cn( - 'tw:flex tw:flex-col', + "tw:flex tw:flex-col", gapMap[gap], align && alignMap[align], justify && justifyMap[justify], - className + className, )} {...props} > {children} </div> ); - } + }, ); -Stack.displayName = 'Stack'; +Stack.displayName = "Stack"; export { Stack }; export type { StackProps }; diff --git a/website/src/components/ui/Text.tsx b/website/src/components/ui/Text.tsx index 60b2ca88d..382d82ee9 100644 --- a/website/src/components/ui/Text.tsx +++ b/website/src/components/ui/Text.tsx @@ -1,34 +1,35 @@ -import React from 'react'; -import { cva, type VariantProps } from 'class-variance-authority'; -import { cn } from '@/lib/utils'; +import React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "@/lib/utils"; // Mantine color aliases — "dimmed" was the most common const COLOR_ALIASES: Record<string, string> = { - dimmed: '#9CA3AF', + dimmed: "#9CA3AF", }; -const textVariants = cva('', { +const textVariants = cva("", { variants: { size: { - xs: 'tw:text-xs', - sm: 'tw:text-sm', - md: 'tw:text-base', - lg: 'tw:text-lg', - xl: 'tw:text-xl', + xs: "tw:text-xs", + sm: "tw:text-sm", + md: "tw:text-base", + lg: "tw:text-lg", + xl: "tw:text-xl", }, weight: { - normal: 'tw:font-normal', - medium: 'tw:font-medium', - semibold: 'tw:font-semibold', - bold: 'tw:font-bold', + normal: "tw:font-normal", + medium: "tw:font-medium", + semibold: "tw:font-semibold", + bold: "tw:font-bold", }, }, defaultVariants: { - size: 'md', + size: "md", }, }); -interface TextProps extends React.HTMLAttributes<HTMLElement>, VariantProps<typeof textVariants> { +interface TextProps + extends React.HTMLAttributes<HTMLElement>, VariantProps<typeof textVariants> { span?: boolean; fw?: number; c?: string; @@ -36,8 +37,22 @@ interface TextProps extends React.HTMLAttributes<HTMLElement>, VariantProps<type } const Text = React.forwardRef<HTMLElement, TextProps>( - ({ className, size, weight, span, fw, c, component, style, children, ...props }, ref) => { - const Component = component || (span ? 'span' : 'p'); + ( + { + className, + size, + weight, + span, + fw, + c, + component, + style, + children, + ...props + }, + ref, + ) => { + const Component = component || (span ? "span" : "p"); const fontWeightStyle = fw ? { fontWeight: fw } : undefined; const colorStyle = c ? { color: COLOR_ALIASES[c] || c } : undefined; @@ -51,9 +66,9 @@ const Text = React.forwardRef<HTMLElement, TextProps>( {children} </Component> ); - } + }, ); -Text.displayName = 'Text'; +Text.displayName = "Text"; export { Text, textVariants }; export type { TextProps }; diff --git a/website/src/components/ui/Title.tsx b/website/src/components/ui/Title.tsx index 93ef0a43f..60bdbd37e 100644 --- a/website/src/components/ui/Title.tsx +++ b/website/src/components/ui/Title.tsx @@ -1,13 +1,13 @@ -import React from 'react'; -import { cn } from '@/lib/utils'; +import React from "react"; +import { cn } from "@/lib/utils"; const orderStyles = { - 1: 'tw:text-4xl tw:font-bold', - 2: 'tw:text-3xl tw:font-bold', - 3: 'tw:text-2xl tw:font-semibold', - 4: 'tw:text-xl tw:font-semibold', - 5: 'tw:text-lg tw:font-medium', - 6: 'tw:text-base tw:font-medium', + 1: "tw:text-4xl tw:font-bold", + 2: "tw:text-3xl tw:font-bold", + 3: "tw:text-2xl tw:font-semibold", + 4: "tw:text-xl tw:font-semibold", + 5: "tw:text-lg tw:font-medium", + 6: "tw:text-base tw:font-medium", } as const; interface TitleProps extends React.HTMLAttributes<HTMLHeadingElement> { @@ -27,9 +27,9 @@ const Title = React.forwardRef<HTMLHeadingElement, TitleProps>( {children} </Component> ); - } + }, ); -Title.displayName = 'Title'; +Title.displayName = "Title"; export { Title }; export type { TitleProps }; diff --git a/website/src/components/ui/accordion.tsx b/website/src/components/ui/accordion.tsx index a7211fa51..674fca83a 100644 --- a/website/src/components/ui/accordion.tsx +++ b/website/src/components/ui/accordion.tsx @@ -1,11 +1,13 @@ -'use client'; +"use client"; -import * as React from 'react'; -import { ChevronDownIcon } from 'lucide-react'; -import { Accordion as AccordionPrimitive } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { ChevronDownIcon } from "lucide-react"; +import { Accordion as AccordionPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; -function Accordion({ ...props }: React.ComponentProps<typeof AccordionPrimitive.Root>) { +function Accordion({ + ...props +}: React.ComponentProps<typeof AccordionPrimitive.Root>) { return <AccordionPrimitive.Root data-slot="accordion" {...props} />; } @@ -16,7 +18,7 @@ function AccordionItem({ return ( <AccordionPrimitive.Item data-slot="accordion-item" - className={cn('tw:border-b tw:last:border-b-0', className)} + className={cn("tw:border-b tw:last:border-b-0", className)} {...props} /> ); @@ -32,8 +34,8 @@ function AccordionTrigger({ <AccordionPrimitive.Trigger data-slot="accordion-trigger" className={cn( - 'tw:bg-transparent tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:flex tw:flex-1 tw:items-start tw:justify-between tw:gap-4 tw:rounded-md tw:py-4 tw:text-left tw:text-sm tw:font-medium tw:transition-all tw:outline-none tw:hover:underline tw:focus-visible:ring-[3px] tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:[&[data-state=open]>svg]:rotate-180', - className + "tw:bg-transparent tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:flex tw:flex-1 tw:items-start tw:justify-between tw:gap-4 tw:rounded-md tw:py-4 tw:text-left tw:text-sm tw:font-medium tw:transition-all tw:outline-none tw:hover:underline tw:focus-visible:ring-[3px] tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:[&[data-state=open]>svg]:rotate-180", + className, )} {...props} > @@ -55,7 +57,7 @@ function AccordionContent({ className="tw:data-[state=closed]:animate-accordion-up tw:data-[state=open]:animate-accordion-down tw:overflow-hidden tw:text-sm" {...props} > - <div className={cn('tw:pt-0 tw:pb-4', className)}>{children}</div> + <div className={cn("tw:pt-0 tw:pb-4", className)}>{children}</div> </AccordionPrimitive.Content> ); } diff --git a/website/src/components/ui/alert.tsx b/website/src/components/ui/alert.tsx index 426f5968b..0ffafa51a 100644 --- a/website/src/components/ui/alert.tsx +++ b/website/src/components/ui/alert.tsx @@ -1,28 +1,28 @@ -import * as React from 'react'; -import { cva, type VariantProps } from 'class-variance-authority'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "@/lib/utils"; const alertVariants = cva( - 'tw:relative tw:w-full tw:rounded-lg tw:border tw:px-4 tw:py-3 tw:text-sm tw:grid tw:has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] tw:grid-cols-[0_1fr] tw:has-[>svg]:gap-x-3 tw:gap-y-0.5 tw:items-start tw:[&>svg]:size-4 tw:[&>svg]:translate-y-0.5 tw:[&>svg]:text-current', + "tw:relative tw:w-full tw:rounded-lg tw:border tw:px-4 tw:py-3 tw:text-sm tw:grid tw:has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] tw:grid-cols-[0_1fr] tw:has-[>svg]:gap-x-3 tw:gap-y-0.5 tw:items-start tw:[&>svg]:size-4 tw:[&>svg]:translate-y-0.5 tw:[&>svg]:text-current", { variants: { variant: { - default: 'tw:bg-card tw:text-card-foreground', + default: "tw:bg-card tw:text-card-foreground", destructive: - 'tw:text-destructive tw:bg-card tw:[&>svg]:text-current tw:*:data-[slot=alert-description]:text-destructive/90', + "tw:text-destructive tw:bg-card tw:[&>svg]:text-current tw:*:data-[slot=alert-description]:text-destructive/90", }, }, defaultVariants: { - variant: 'default', + variant: "default", }, - } + }, ); function Alert({ className, variant, ...props -}: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) { +}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) { return ( <div data-slot="alert" @@ -33,26 +33,29 @@ function Alert({ ); } -function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) { +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="alert-title" className={cn( - 'tw:col-start-2 tw:line-clamp-1 tw:min-h-4 tw:font-medium tw:tracking-tight', - className + "tw:col-start-2 tw:line-clamp-1 tw:min-h-4 tw:font-medium tw:tracking-tight", + className, )} {...props} /> ); } -function AlertDescription({ className, ...props }: React.ComponentProps<'div'>) { +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { return ( <div data-slot="alert-description" className={cn( - 'tw:text-muted-foreground tw:col-start-2 tw:grid tw:justify-items-start tw:gap-1 tw:text-sm tw:[&_p]:leading-relaxed', - className + "tw:text-muted-foreground tw:col-start-2 tw:grid tw:justify-items-start tw:gap-1 tw:text-sm tw:[&_p]:leading-relaxed", + className, )} {...props} /> diff --git a/website/src/components/ui/badge.tsx b/website/src/components/ui/badge.tsx index c40a3b7c0..c75ed981f 100644 --- a/website/src/components/ui/badge.tsx +++ b/website/src/components/ui/badge.tsx @@ -1,37 +1,40 @@ -import * as React from 'react'; -import { cva, type VariantProps } from 'class-variance-authority'; -import { Slot } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { Slot } from "radix-ui"; +import { cn } from "@/lib/utils"; const badgeVariants = cva( - 'tw:inline-flex tw:items-center tw:justify-center tw:rounded-full tw:border tw:border-transparent tw:px-2 tw:py-0.5 tw:text-xs tw:font-medium tw:w-fit tw:whitespace-nowrap tw:shrink-0 tw:[&>svg]:size-3 tw:gap-1 tw:[&>svg]:pointer-events-none tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:focus-visible:ring-[3px] tw:aria-invalid:ring-destructive/20 tw:aria-invalid:border-destructive tw:transition-[color,box-shadow] tw:overflow-hidden', + "tw:inline-flex tw:items-center tw:justify-center tw:rounded-full tw:border tw:border-transparent tw:px-2 tw:py-0.5 tw:text-xs tw:font-medium tw:w-fit tw:whitespace-nowrap tw:shrink-0 tw:[&>svg]:size-3 tw:gap-1 tw:[&>svg]:pointer-events-none tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:focus-visible:ring-[3px] tw:aria-invalid:ring-destructive/20 tw:aria-invalid:border-destructive tw:transition-[color,box-shadow] tw:overflow-hidden", { variants: { variant: { - default: 'tw:bg-primary tw:text-primary-foreground tw:[a&]:hover:bg-primary/90', - secondary: 'tw:bg-secondary tw:text-secondary-foreground tw:[a&]:hover:bg-secondary/90', + default: + "tw:bg-primary tw:text-primary-foreground tw:[a&]:hover:bg-primary/90", + secondary: + "tw:bg-secondary tw:text-secondary-foreground tw:[a&]:hover:bg-secondary/90", destructive: - 'tw:bg-destructive tw:text-white tw:[a&]:hover:bg-destructive/90 tw:focus-visible:ring-destructive/20', + "tw:bg-destructive tw:text-white tw:[a&]:hover:bg-destructive/90 tw:focus-visible:ring-destructive/20", outline: - 'tw:border-border tw:text-foreground tw:[a&]:hover:bg-accent tw:[a&]:hover:text-accent-foreground', - ghost: 'tw:[a&]:hover:bg-accent tw:[a&]:hover:text-accent-foreground', - link: 'tw:text-primary tw:underline-offset-4 tw:[a&]:hover:underline', + "tw:border-border tw:text-foreground tw:[a&]:hover:bg-accent tw:[a&]:hover:text-accent-foreground", + ghost: "tw:[a&]:hover:bg-accent tw:[a&]:hover:text-accent-foreground", + link: "tw:text-primary tw:underline-offset-4 tw:[a&]:hover:underline", }, }, defaultVariants: { - variant: 'default', + variant: "default", }, - } + }, ); function Badge({ className, - variant = 'default', + variant = "default", asChild = false, ...props -}: React.ComponentProps<'span'> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) { +}: React.ComponentProps<"span"> & + VariantProps<typeof badgeVariants> & { asChild?: boolean }) { // Cast needed: dual csstype versions in monorepo cause SlotProps style mismatch - const Comp = (asChild ? Slot.Root : 'span') as React.ElementType; + const Comp = (asChild ? Slot.Root : "span") as React.ElementType; return ( <Comp diff --git a/website/src/components/ui/button.tsx b/website/src/components/ui/button.tsx index 0c41f7795..437fc2d00 100644 --- a/website/src/components/ui/button.tsx +++ b/website/src/components/ui/button.tsx @@ -1,52 +1,56 @@ -import * as React from 'react'; -import { cva, type VariantProps } from 'class-variance-authority'; -import { Slot } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { Slot } from "radix-ui"; +import { cn } from "@/lib/utils"; const buttonVariants = cva( - 'tw:inline-flex tw:items-center tw:justify-center tw:gap-2 tw:whitespace-nowrap tw:rounded-md tw:text-sm tw:font-medium tw:cursor-pointer tw:transition-all tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg:not([class*=size-])]:size-4 tw:shrink-0 tw:[&_svg]:shrink-0 tw:outline-none tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:focus-visible:ring-[3px] tw:aria-invalid:ring-destructive/20 tw:aria-invalid:border-destructive', + "tw:inline-flex tw:items-center tw:justify-center tw:gap-2 tw:whitespace-nowrap tw:rounded-md tw:text-sm tw:font-medium tw:cursor-pointer tw:transition-all tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg:not([class*=size-])]:size-4 tw:shrink-0 tw:[&_svg]:shrink-0 tw:outline-none tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:focus-visible:ring-[3px] tw:aria-invalid:ring-destructive/20 tw:aria-invalid:border-destructive", { variants: { variant: { - default: 'tw:bg-primary tw:text-primary-foreground tw:hover:bg-primary/90', + default: + "tw:bg-primary tw:text-primary-foreground tw:hover:bg-primary/90", destructive: - 'tw:bg-destructive tw:text-white tw:hover:bg-destructive/90 tw:focus-visible:ring-destructive/20', + "tw:bg-destructive tw:text-white tw:hover:bg-destructive/90 tw:focus-visible:ring-destructive/20", outline: - 'tw:border tw:bg-background tw:shadow-xs tw:hover:bg-accent tw:hover:text-accent-foreground', - secondary: 'tw:bg-secondary tw:text-secondary-foreground tw:hover:bg-secondary/80', - ghost: 'tw:bg-transparent tw:hover:bg-accent tw:hover:text-accent-foreground', - link: 'tw:bg-transparent tw:text-primary tw:underline-offset-4 tw:hover:underline', + "tw:border tw:bg-background tw:shadow-xs tw:hover:bg-accent tw:hover:text-accent-foreground", + secondary: + "tw:bg-secondary tw:text-secondary-foreground tw:hover:bg-secondary/80", + ghost: + "tw:bg-transparent tw:hover:bg-accent tw:hover:text-accent-foreground", + link: "tw:bg-transparent tw:text-primary tw:underline-offset-4 tw:hover:underline", }, size: { - default: 'tw:h-9 tw:px-4 tw:py-2 tw:has-[>svg]:px-3', - xs: 'tw:h-6 tw:gap-1 tw:rounded-md tw:px-2 tw:text-xs tw:has-[>svg]:px-1.5 tw:[&_svg:not([class*=size-])]:size-3', - sm: 'tw:h-8 tw:rounded-md tw:gap-1.5 tw:px-3 tw:has-[>svg]:px-2.5', - lg: 'tw:h-10 tw:rounded-md tw:px-6 tw:has-[>svg]:px-4', - icon: 'tw:size-9', - 'icon-xs': 'tw:size-6 tw:rounded-md tw:[&_svg:not([class*=size-])]:size-3', - 'icon-sm': 'tw:size-8', - 'icon-lg': 'tw:size-10', + default: "tw:h-9 tw:px-4 tw:py-2 tw:has-[>svg]:px-3", + xs: "tw:h-6 tw:gap-1 tw:rounded-md tw:px-2 tw:text-xs tw:has-[>svg]:px-1.5 tw:[&_svg:not([class*=size-])]:size-3", + sm: "tw:h-8 tw:rounded-md tw:gap-1.5 tw:px-3 tw:has-[>svg]:px-2.5", + lg: "tw:h-10 tw:rounded-md tw:px-6 tw:has-[>svg]:px-4", + icon: "tw:size-9", + "icon-xs": + "tw:size-6 tw:rounded-md tw:[&_svg:not([class*=size-])]:size-3", + "icon-sm": "tw:size-8", + "icon-lg": "tw:size-10", }, }, defaultVariants: { - variant: 'default', - size: 'default', + variant: "default", + size: "default", }, - } + }, ); function Button({ className, - variant = 'default', - size = 'default', + variant = "default", + size = "default", asChild = false, ...props -}: React.ComponentProps<'button'> & +}: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean; }) { // Cast needed: dual csstype versions in monorepo cause SlotProps style mismatch - const Comp = (asChild ? Slot.Root : 'button') as React.ElementType; + const Comp = (asChild ? Slot.Root : "button") as React.ElementType; return ( <Comp diff --git a/website/src/components/ui/card.tsx b/website/src/components/ui/card.tsx index e4ba307ea..8c0c64928 100644 --- a/website/src/components/ui/card.tsx +++ b/website/src/components/ui/card.tsx @@ -1,77 +1,94 @@ -import * as React from 'react'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { cn } from "@/lib/utils"; -function Card({ className, ...props }: React.ComponentProps<'div'>) { +function Card({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="card" className={cn( - 'tw:bg-card tw:text-card-foreground tw:flex tw:flex-col tw:gap-6 tw:rounded-xl tw:border tw:py-6 tw:shadow-sm', - className + "tw:bg-card tw:text-card-foreground tw:flex tw:flex-col tw:gap-6 tw:rounded-xl tw:border tw:py-6 tw:shadow-sm", + className, )} {...props} /> ); } -function CardHeader({ className, ...props }: React.ComponentProps<'div'>) { +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="card-header" className={cn( - 'tw:@container/card-header tw:grid tw:auto-rows-min tw:grid-rows-[auto_auto] tw:items-start tw:gap-2 tw:px-6 tw:has-data-[slot=card-action]:grid-cols-[1fr_auto] tw:[.border-b]:pb-6', - className + "tw:@container/card-header tw:grid tw:auto-rows-min tw:grid-rows-[auto_auto] tw:items-start tw:gap-2 tw:px-6 tw:has-data-[slot=card-action]:grid-cols-[1fr_auto] tw:[.border-b]:pb-6", + className, )} {...props} /> ); } -function CardTitle({ className, ...props }: React.ComponentProps<'div'>) { +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="card-title" - className={cn('tw:leading-none tw:font-semibold', className)} + className={cn("tw:leading-none tw:font-semibold", className)} {...props} /> ); } -function CardDescription({ className, ...props }: React.ComponentProps<'div'>) { +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="card-description" - className={cn('tw:text-muted-foreground tw:text-sm', className)} + className={cn("tw:text-muted-foreground tw:text-sm", className)} {...props} /> ); } -function CardAction({ className, ...props }: React.ComponentProps<'div'>) { +function CardAction({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="card-action" className={cn( - 'tw:col-start-2 tw:row-span-2 tw:row-start-1 tw:self-start tw:justify-self-end', - className + "tw:col-start-2 tw:row-span-2 tw:row-start-1 tw:self-start tw:justify-self-end", + className, )} {...props} /> ); } -function CardContent({ className, ...props }: React.ComponentProps<'div'>) { - return <div data-slot="card-content" className={cn('tw:px-6', className)} {...props} />; +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + data-slot="card-content" + className={cn("tw:px-6", className)} + {...props} + /> + ); } -function CardFooter({ className, ...props }: React.ComponentProps<'div'>) { +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="card-footer" - className={cn('tw:flex tw:items-center tw:px-6 tw:[.border-t]:pt-6', className)} + className={cn( + "tw:flex tw:items-center tw:px-6 tw:[.border-t]:pt-6", + className, + )} {...props} /> ); } -export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent }; +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +}; diff --git a/website/src/components/ui/checkbox.tsx b/website/src/components/ui/checkbox.tsx index 20452f814..8c8d318da 100644 --- a/website/src/components/ui/checkbox.tsx +++ b/website/src/components/ui/checkbox.tsx @@ -1,15 +1,18 @@ -import * as React from 'react'; -import { CheckIcon } from 'lucide-react'; -import { Checkbox as CheckboxPrimitive } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { CheckIcon } from "lucide-react"; +import { Checkbox as CheckboxPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; -function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) { +function Checkbox({ + className, + ...props +}: React.ComponentProps<typeof CheckboxPrimitive.Root>) { return ( <CheckboxPrimitive.Root data-slot="checkbox" className={cn( - 'tw:peer tw:border-input tw:data-[state=checked]:bg-primary tw:data-[state=checked]:text-primary-foreground tw:data-[state=checked]:border-primary tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:aria-invalid:ring-destructive/20 tw:aria-invalid:border-destructive tw:size-4 tw:shrink-0 tw:rounded-[4px] tw:border tw:shadow-xs tw:transition-shadow tw:outline-none tw:focus-visible:ring-[3px] tw:disabled:cursor-not-allowed tw:disabled:opacity-50', - className + "tw:peer tw:border-input tw:data-[state=checked]:bg-primary tw:data-[state=checked]:text-primary-foreground tw:data-[state=checked]:border-primary tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:aria-invalid:ring-destructive/20 tw:aria-invalid:border-destructive tw:size-4 tw:shrink-0 tw:rounded-[4px] tw:border tw:shadow-xs tw:transition-shadow tw:outline-none tw:focus-visible:ring-[3px] tw:disabled:cursor-not-allowed tw:disabled:opacity-50", + className, )} {...props} > diff --git a/website/src/components/ui/collapsible.tsx b/website/src/components/ui/collapsible.tsx index f68af59ff..d0d49dc48 100644 --- a/website/src/components/ui/collapsible.tsx +++ b/website/src/components/ui/collapsible.tsx @@ -1,19 +1,25 @@ -import { Collapsible as CollapsiblePrimitive } from 'radix-ui'; +import { Collapsible as CollapsiblePrimitive } from "radix-ui"; -function Collapsible({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) { +function Collapsible({ + ...props +}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) { return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />; } function CollapsibleTrigger({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.Trigger>) { - return <CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />; + return ( + <CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} /> + ); } function CollapsibleContent({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.Content>) { - return <CollapsiblePrimitive.Content data-slot="collapsible-content" {...props} />; + return ( + <CollapsiblePrimitive.Content data-slot="collapsible-content" {...props} /> + ); } export { Collapsible, CollapsibleTrigger, CollapsibleContent }; diff --git a/website/src/components/ui/command.tsx b/website/src/components/ui/command.tsx index 5233a3cb1..33be61b2e 100644 --- a/website/src/components/ui/command.tsx +++ b/website/src/components/ui/command.tsx @@ -1,22 +1,25 @@ -import * as React from 'react'; -import { Command as CommandPrimitive } from 'cmdk'; -import { SearchIcon } from 'lucide-react'; +import * as React from "react"; +import { Command as CommandPrimitive } from "cmdk"; +import { SearchIcon } from "lucide-react"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, -} from '@/components/ui/dialog'; -import { cn } from '@/lib/utils'; +} from "@/components/ui/dialog"; +import { cn } from "@/lib/utils"; -function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) { +function Command({ + className, + ...props +}: React.ComponentProps<typeof CommandPrimitive>) { return ( <CommandPrimitive data-slot="command" className={cn( - 'tw:bg-popover tw:text-popover-foreground tw:flex tw:h-full tw:w-full tw:flex-col tw:overflow-hidden tw:rounded-md', - className + "tw:bg-popover tw:text-popover-foreground tw:flex tw:h-full tw:w-full tw:flex-col tw:overflow-hidden tw:rounded-md", + className, )} {...props} /> @@ -24,8 +27,8 @@ function Command({ className, ...props }: React.ComponentProps<typeof CommandPri } function CommandDialog({ - title = 'Command palette', - description = 'Search for a command to run...', + title = "Command palette", + description = "Search for a command to run...", children, className, showCloseButton = true, @@ -43,7 +46,7 @@ function CommandDialog({ <DialogDescription>{description}</DialogDescription> </DialogHeader> <DialogContent - className={cn('tw:overflow-hidden tw:p-0', className)} + className={cn("tw:overflow-hidden tw:p-0", className)} showCloseButton={showCloseButton} > <Command className="tw:[&_[cmdk-group-heading]]:text-muted-foreground tw:**:data-[slot=command-input-wrapper]:h-12 tw:[&_[cmdk-group-heading]]:px-2 tw:[&_[cmdk-group-heading]]:font-medium tw:[&_[cmdk-group]]:px-2 tw:[&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 tw:[&_[cmdk-input-wrapper]_svg]:h-5 tw:[&_[cmdk-input-wrapper]_svg]:w-5 tw:[&_[cmdk-input]]:h-12 tw:[&_[cmdk-item]]:px-2 tw:[&_[cmdk-item]]:py-3 tw:[&_[cmdk-item]_svg]:h-5 tw:[&_[cmdk-item]_svg]:w-5"> @@ -67,8 +70,8 @@ function CommandInput({ <CommandPrimitive.Input data-slot="command-input" className={cn( - 'tw:placeholder:text-muted-foreground tw:flex tw:h-10 tw:w-full tw:rounded-md tw:bg-transparent tw:py-3 tw:text-sm tw:outline-hidden tw:disabled:cursor-not-allowed tw:disabled:opacity-50', - className + "tw:placeholder:text-muted-foreground tw:flex tw:h-10 tw:w-full tw:rounded-md tw:bg-transparent tw:py-3 tw:text-sm tw:outline-hidden tw:disabled:cursor-not-allowed tw:disabled:opacity-50", + className, )} {...props} /> @@ -76,20 +79,25 @@ function CommandInput({ ); } -function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) { +function CommandList({ + className, + ...props +}: React.ComponentProps<typeof CommandPrimitive.List>) { return ( <CommandPrimitive.List data-slot="command-list" className={cn( - 'tw:max-h-[300px] tw:scroll-py-1 tw:overflow-x-hidden tw:overflow-y-auto', - className + "tw:max-h-[300px] tw:scroll-py-1 tw:overflow-x-hidden tw:overflow-y-auto", + className, )} {...props} /> ); } -function CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) { +function CommandEmpty({ + ...props +}: React.ComponentProps<typeof CommandPrimitive.Empty>) { return ( <CommandPrimitive.Empty data-slot="command-empty" @@ -107,8 +115,8 @@ function CommandGroup({ <CommandPrimitive.Group data-slot="command-group" className={cn( - 'tw:text-foreground tw:[&_[cmdk-group-heading]]:text-muted-foreground tw:overflow-hidden tw:p-1 tw:[&_[cmdk-group-heading]]:px-2 tw:[&_[cmdk-group-heading]]:py-1.5 tw:[&_[cmdk-group-heading]]:text-xs tw:[&_[cmdk-group-heading]]:font-medium', - className + "tw:text-foreground tw:[&_[cmdk-group-heading]]:text-muted-foreground tw:overflow-hidden tw:p-1 tw:[&_[cmdk-group-heading]]:px-2 tw:[&_[cmdk-group-heading]]:py-1.5 tw:[&_[cmdk-group-heading]]:text-xs tw:[&_[cmdk-group-heading]]:font-medium", + className, )} {...props} /> @@ -122,30 +130,39 @@ function CommandSeparator({ return ( <CommandPrimitive.Separator data-slot="command-separator" - className={cn('tw:bg-border tw:-mx-1 tw:h-px', className)} + className={cn("tw:bg-border tw:-mx-1 tw:h-px", className)} {...props} /> ); } -function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) { +function CommandItem({ + className, + ...props +}: React.ComponentProps<typeof CommandPrimitive.Item>) { return ( <CommandPrimitive.Item data-slot="command-item" className={cn( - 'tw:data-[selected=true]:bg-accent tw:data-[selected=true]:text-accent-foreground tw:[&_svg:not([class*=text-])]:text-muted-foreground tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-2 tw:rounded-sm tw:px-2 tw:py-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:data-[disabled=true]:pointer-events-none tw:data-[disabled=true]:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4', - className + "tw:data-[selected=true]:bg-accent tw:data-[selected=true]:text-accent-foreground tw:[&_svg:not([class*=text-])]:text-muted-foreground tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-2 tw:rounded-sm tw:px-2 tw:py-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:data-[disabled=true]:pointer-events-none tw:data-[disabled=true]:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4", + className, )} {...props} /> ); } -function CommandShortcut({ className, ...props }: React.ComponentProps<'span'>) { +function CommandShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { return ( <span data-slot="command-shortcut" - className={cn('tw:text-muted-foreground tw:ml-auto tw:text-xs tw:tracking-widest', className)} + className={cn( + "tw:text-muted-foreground tw:ml-auto tw:text-xs tw:tracking-widest", + className, + )} {...props} /> ); diff --git a/website/src/components/ui/date-picker.story.tsx b/website/src/components/ui/date-picker.story.tsx index 1d7cf06e8..4dc6564d7 100644 --- a/website/src/components/ui/date-picker.story.tsx +++ b/website/src/components/ui/date-picker.story.tsx @@ -1,10 +1,10 @@ "use client"; -import { useState } from 'react'; -import type { Meta, StoryObj } from '@storybook/react'; -import { DatePicker } from './date-picker'; +import { useState } from "react"; +import type { Meta, StoryObj } from "@storybook/react"; +import { DatePicker } from "./date-picker"; const meta: Meta<typeof DatePicker> = { - title: 'UI/DatePicker', + title: "UI/DatePicker", component: DatePicker, decorators: [ (Story) => ( diff --git a/website/src/components/ui/date-picker.tsx b/website/src/components/ui/date-picker.tsx index 1805644e9..14a380652 100644 --- a/website/src/components/ui/date-picker.tsx +++ b/website/src/components/ui/date-picker.tsx @@ -1,11 +1,11 @@ "use client"; -import dayjs from 'dayjs'; -import * as React from 'react'; -import { useState } from 'react'; -import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react'; -import { cn } from '@/lib/utils'; -import { Button } from './button'; -import { Popover, PopoverContent, PopoverTrigger } from './popover'; +import dayjs from "dayjs"; +import * as React from "react"; +import { useState } from "react"; +import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react"; +import { cn } from "@/lib/utils"; +import { Button } from "./button"; +import { Popover, PopoverContent, PopoverTrigger } from "./popover"; interface DatePickerProps { value: Date | null | undefined; @@ -17,40 +17,40 @@ interface DatePickerProps { className?: string; } -const DAYS_OF_WEEK = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']; +const DAYS_OF_WEEK = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]; function DatePicker({ value, onChange, minDate, maxDate, - placeholder = 'Select date', - displayFormat = 'MMM. D, YYYY', + placeholder = "Select date", + displayFormat = "MMM. D, YYYY", className, }: DatePickerProps) { const [open, setOpen] = useState(false); const initialMonth = value ? dayjs(value) : dayjs(); - const [viewMonth, setViewMonth] = useState(initialMonth.startOf('month')); + const [viewMonth, setViewMonth] = useState(initialMonth.startOf("month")); const displayText = value ? dayjs(value).format(displayFormat) : placeholder; // Generate calendar days for the current view month - const startOfMonth = viewMonth.startOf('month'); - const endOfMonth = viewMonth.endOf('month'); + const startOfMonth = viewMonth.startOf("month"); + const endOfMonth = viewMonth.endOf("month"); const startDay = startOfMonth.day(); // 0 = Sunday const daysInMonth = viewMonth.daysInMonth(); // Build 6 rows x 7 cols grid const calendarDays: (dayjs.Dayjs | null)[] = []; for (let i = 0; i < startDay; i++) { - calendarDays.push(startOfMonth.subtract(startDay - i, 'day')); + calendarDays.push(startOfMonth.subtract(startDay - i, "day")); } for (let i = 1; i <= daysInMonth; i++) { calendarDays.push(viewMonth.date(i)); } const remaining = 42 - calendarDays.length; for (let i = 1; i <= remaining; i++) { - calendarDays.push(endOfMonth.add(i, 'day')); + calendarDays.push(endOfMonth.add(i, "day")); } const handleSelect = (day: dayjs.Dayjs) => { @@ -59,10 +59,10 @@ function DatePicker({ }; const isDateDisabled = (day: dayjs.Dayjs) => { - if (minDate && day.isBefore(dayjs(minDate), 'day')) { + if (minDate && day.isBefore(dayjs(minDate), "day")) { return true; } - if (maxDate && day.isAfter(dayjs(maxDate), 'day')) { + if (maxDate && day.isAfter(dayjs(maxDate), "day")) { return true; } return false; @@ -74,9 +74,9 @@ function DatePicker({ <button type="button" className={cn( - 'tw:border-input tw:flex tw:h-9 tw:w-full tw:items-center tw:justify-between tw:rounded-md tw:border tw:px-3 tw:py-2 tw:text-sm tw:shadow-xs tw:transition-[color,box-shadow] tw:outline-none', - !value && 'tw:text-muted-foreground', - className + "tw:border-input tw:flex tw:h-9 tw:w-full tw:items-center tw:justify-between tw:rounded-md tw:border tw:px-3 tw:py-2 tw:text-sm tw:shadow-xs tw:transition-[color,box-shadow] tw:outline-none", + !value && "tw:text-muted-foreground", + className, )} > {displayText} @@ -89,15 +89,17 @@ function DatePicker({ <Button variant="ghost" size="icon-xs" - onClick={() => setViewMonth((m) => m.subtract(1, 'month'))} + onClick={() => setViewMonth((m) => m.subtract(1, "month"))} > <IconChevronLeft size={14} /> </Button> - <span className="tw:text-sm tw:font-medium">{viewMonth.format('MMMM YYYY')}</span> + <span className="tw:text-sm tw:font-medium"> + {viewMonth.format("MMMM YYYY")} + </span> <Button variant="ghost" size="icon-xs" - onClick={() => setViewMonth((m) => m.add(1, 'month'))} + onClick={() => setViewMonth((m) => m.add(1, "month"))} > <IconChevronRight size={14} /> </Button> @@ -120,9 +122,9 @@ function DatePicker({ return <div key={i} />; } const isCurrentMonth = day.month() === viewMonth.month(); - const isSelected = value && day.isSame(dayjs(value), 'day'); + const isSelected = value && day.isSame(dayjs(value), "day"); const isDisabled = isDateDisabled(day); - const isToday = day.isSame(dayjs(), 'day'); + const isToday = day.isSame(dayjs(), "day"); return ( <button @@ -131,13 +133,13 @@ function DatePicker({ disabled={isDisabled} onClick={() => handleSelect(day)} className={cn( - 'tw:h-8 tw:w-full tw:rounded-md tw:text-sm tw:transition-colors', + "tw:h-8 tw:w-full tw:rounded-md tw:text-sm tw:transition-colors", isSelected - ? 'tw:bg-primary tw:text-primary-foreground tw:font-medium' - : 'tw:hover:bg-accent tw:hover:text-accent-foreground', - !isCurrentMonth && !isSelected && 'tw:text-muted-foreground', - isToday && !isSelected && 'tw:font-semibold', - isDisabled && 'tw:opacity-30 tw:pointer-events-none' + ? "tw:bg-primary tw:text-primary-foreground tw:font-medium" + : "tw:hover:bg-accent tw:hover:text-accent-foreground", + !isCurrentMonth && !isSelected && "tw:text-muted-foreground", + isToday && !isSelected && "tw:font-semibold", + isDisabled && "tw:opacity-30 tw:pointer-events-none", )} > {day.date()} diff --git a/website/src/components/ui/dialog.tsx b/website/src/components/ui/dialog.tsx index 530f074fb..1f8b9163d 100644 --- a/website/src/components/ui/dialog.tsx +++ b/website/src/components/ui/dialog.tsx @@ -1,24 +1,32 @@ -'use client'; +"use client"; -import * as React from 'react'; -import { XIcon } from 'lucide-react'; -import { Dialog as DialogPrimitive } from 'radix-ui'; -import { Button } from '@/components/ui/button'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { XIcon } from "lucide-react"; +import { Dialog as DialogPrimitive } from "radix-ui"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; -function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) { +function Dialog({ + ...props +}: React.ComponentProps<typeof DialogPrimitive.Root>) { return <DialogPrimitive.Root data-slot="dialog" {...props} />; } -function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) { +function DialogTrigger({ + ...props +}: React.ComponentProps<typeof DialogPrimitive.Trigger>) { return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />; } -function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) { +function DialogPortal({ + ...props +}: React.ComponentProps<typeof DialogPrimitive.Portal>) { return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />; } -function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) { +function DialogClose({ + ...props +}: React.ComponentProps<typeof DialogPrimitive.Close>) { return <DialogPrimitive.Close data-slot="dialog-close" {...props} />; } @@ -30,8 +38,8 @@ function DialogOverlay({ <DialogPrimitive.Overlay data-slot="dialog-overlay" className={cn( - 'tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=open]:fade-in-0 tw:fixed tw:inset-0 tw:z-50 tw:bg-black/50', - className + "tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=open]:fade-in-0 tw:fixed tw:inset-0 tw:z-50 tw:bg-black/50", + className, )} {...props} /> @@ -52,8 +60,8 @@ function DialogContent({ <DialogPrimitive.Content data-slot="dialog-content" className={cn( - 'tw:bg-background tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=open]:fade-in-0 tw:data-[state=closed]:zoom-out-95 tw:data-[state=open]:zoom-in-95 tw:fixed tw:top-[50%] tw:left-[50%] tw:z-50 tw:grid tw:w-full tw:max-w-[calc(100%-2rem)] tw:translate-x-[-50%] tw:translate-y-[-50%] tw:gap-4 tw:rounded-lg tw:border tw:p-6 tw:shadow-lg tw:duration-200 tw:outline-none tw:sm:max-w-[32rem]', - className + "tw:bg-background tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=open]:fade-in-0 tw:data-[state=closed]:zoom-out-95 tw:data-[state=open]:zoom-in-95 tw:fixed tw:top-[50%] tw:left-[50%] tw:z-50 tw:grid tw:w-full tw:max-w-[calc(100%-2rem)] tw:translate-x-[-50%] tw:translate-y-[-50%] tw:gap-4 tw:rounded-lg tw:border tw:p-6 tw:shadow-lg tw:duration-200 tw:outline-none tw:sm:max-w-[32rem]", + className, )} {...props} > @@ -72,11 +80,14 @@ function DialogContent({ ); } -function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) { +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="dialog-header" - className={cn('tw:flex tw:flex-col tw:gap-2 tw:text-center tw:sm:text-left', className)} + className={cn( + "tw:flex tw:flex-col tw:gap-2 tw:text-center tw:sm:text-left", + className, + )} {...props} /> ); @@ -87,15 +98,15 @@ function DialogFooter({ showCloseButton = false, children, ...props -}: React.ComponentProps<'div'> & { +}: React.ComponentProps<"div"> & { showCloseButton?: boolean; }) { return ( <div data-slot="dialog-footer" className={cn( - 'tw:flex tw:flex-col-reverse tw:gap-2 tw:sm:flex-row tw:sm:justify-end', - className + "tw:flex tw:flex-col-reverse tw:gap-2 tw:sm:flex-row tw:sm:justify-end", + className, )} {...props} > @@ -109,11 +120,14 @@ function DialogFooter({ ); } -function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) { +function DialogTitle({ + className, + ...props +}: React.ComponentProps<typeof DialogPrimitive.Title>) { return ( <DialogPrimitive.Title data-slot="dialog-title" - className={cn('tw:text-lg tw:leading-none tw:font-semibold', className)} + className={cn("tw:text-lg tw:leading-none tw:font-semibold", className)} {...props} /> ); @@ -126,7 +140,7 @@ function DialogDescription({ return ( <DialogPrimitive.Description data-slot="dialog-description" - className={cn('tw:text-muted-foreground tw:text-sm', className)} + className={cn("tw:text-muted-foreground tw:text-sm", className)} {...props} /> ); diff --git a/website/src/components/ui/dropdown-menu.tsx b/website/src/components/ui/dropdown-menu.tsx index 49e1713f4..061e79e22 100644 --- a/website/src/components/ui/dropdown-menu.tsx +++ b/website/src/components/ui/dropdown-menu.tsx @@ -1,22 +1,31 @@ -import * as React from 'react'; -import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react'; -import { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"; +import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; -function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) { +function DropdownMenu({ + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) { return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />; } function DropdownMenuPortal({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) { - return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />; + return ( + <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} /> + ); } function DropdownMenuTrigger({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) { - return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />; + return ( + <DropdownMenuPrimitive.Trigger + data-slot="dropdown-menu-trigger" + {...props} + /> + ); } function DropdownMenuContent({ @@ -30,8 +39,8 @@ function DropdownMenuContent({ data-slot="dropdown-menu-content" sideOffset={sideOffset} className={cn( - 'tw:bg-popover tw:text-popover-foreground tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=open]:fade-in-0 tw:data-[state=closed]:zoom-out-95 tw:data-[state=open]:zoom-in-95 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:z-50 tw:max-h-(--radix-dropdown-menu-content-available-height) tw:min-w-[8rem] tw:origin-(--radix-dropdown-menu-content-transform-origin) tw:overflow-x-hidden tw:overflow-y-auto tw:rounded-md tw:border tw:p-1 tw:shadow-md', - className + "tw:bg-popover tw:text-popover-foreground tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=open]:fade-in-0 tw:data-[state=closed]:zoom-out-95 tw:data-[state=open]:zoom-in-95 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:z-50 tw:max-h-(--radix-dropdown-menu-content-available-height) tw:min-w-[8rem] tw:origin-(--radix-dropdown-menu-content-transform-origin) tw:overflow-x-hidden tw:overflow-y-auto tw:rounded-md tw:border tw:p-1 tw:shadow-md", + className, )} {...props} /> @@ -39,18 +48,22 @@ function DropdownMenuContent({ ); } -function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) { - return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />; +function DropdownMenuGroup({ + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) { + return ( + <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} /> + ); } function DropdownMenuItem({ className, inset, - variant = 'default', + variant = "default", ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & { inset?: boolean; - variant?: 'default' | 'destructive'; + variant?: "default" | "destructive"; }) { return ( <DropdownMenuPrimitive.Item @@ -58,8 +71,8 @@ function DropdownMenuItem({ data-inset={inset} data-variant={variant} className={cn( - 'tw:focus:bg-accent tw:focus:text-accent-foreground tw:data-[variant=destructive]:text-destructive tw:data-[variant=destructive]:focus:bg-destructive/10 tw:data-[variant=destructive]:focus:text-destructive tw:data-[variant=destructive]:*:[svg]:!text-destructive tw:[&_svg:not([class*=text-])]:text-muted-foreground tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-2 tw:rounded-sm tw:px-2 tw:py-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:data-[disabled]:pointer-events-none tw:data-[disabled]:opacity-50 tw:data-[inset]:pl-8 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4', - className + "tw:focus:bg-accent tw:focus:text-accent-foreground tw:data-[variant=destructive]:text-destructive tw:data-[variant=destructive]:focus:bg-destructive/10 tw:data-[variant=destructive]:focus:text-destructive tw:data-[variant=destructive]:*:[svg]:!text-destructive tw:[&_svg:not([class*=text-])]:text-muted-foreground tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-2 tw:rounded-sm tw:px-2 tw:py-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:data-[disabled]:pointer-events-none tw:data-[disabled]:opacity-50 tw:data-[inset]:pl-8 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4", + className, )} {...props} /> @@ -76,8 +89,8 @@ function DropdownMenuCheckboxItem({ <DropdownMenuPrimitive.CheckboxItem data-slot="dropdown-menu-checkbox-item" className={cn( - 'tw:focus:bg-accent tw:focus:text-accent-foreground tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-2 tw:rounded-sm tw:py-1.5 tw:pr-2 tw:pl-8 tw:text-sm tw:outline-hidden tw:select-none tw:data-[disabled]:pointer-events-none tw:data-[disabled]:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4', - className + "tw:focus:bg-accent tw:focus:text-accent-foreground tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-2 tw:rounded-sm tw:py-1.5 tw:pr-2 tw:pl-8 tw:text-sm tw:outline-hidden tw:select-none tw:data-[disabled]:pointer-events-none tw:data-[disabled]:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4", + className, )} checked={checked} {...props} @@ -95,7 +108,12 @@ function DropdownMenuCheckboxItem({ function DropdownMenuRadioGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) { - return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />; + return ( + <DropdownMenuPrimitive.RadioGroup + data-slot="dropdown-menu-radio-group" + {...props} + /> + ); } function DropdownMenuRadioItem({ @@ -107,8 +125,8 @@ function DropdownMenuRadioItem({ <DropdownMenuPrimitive.RadioItem data-slot="dropdown-menu-radio-item" className={cn( - 'tw:focus:bg-accent tw:focus:text-accent-foreground tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-2 tw:rounded-sm tw:py-1.5 tw:pr-2 tw:pl-8 tw:text-sm tw:outline-hidden tw:select-none tw:data-[disabled]:pointer-events-none tw:data-[disabled]:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4', - className + "tw:focus:bg-accent tw:focus:text-accent-foreground tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-2 tw:rounded-sm tw:py-1.5 tw:pr-2 tw:pl-8 tw:text-sm tw:outline-hidden tw:select-none tw:data-[disabled]:pointer-events-none tw:data-[disabled]:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4", + className, )} {...props} > @@ -133,7 +151,10 @@ function DropdownMenuLabel({ <DropdownMenuPrimitive.Label data-slot="dropdown-menu-label" data-inset={inset} - className={cn('tw:px-2 tw:py-1.5 tw:text-sm tw:font-medium tw:data-[inset]:pl-8', className)} + className={cn( + "tw:px-2 tw:py-1.5 tw:text-sm tw:font-medium tw:data-[inset]:pl-8", + className, + )} {...props} /> ); @@ -146,23 +167,31 @@ function DropdownMenuSeparator({ return ( <DropdownMenuPrimitive.Separator data-slot="dropdown-menu-separator" - className={cn('tw:bg-border tw:-mx-1 tw:my-1 tw:h-px', className)} + className={cn("tw:bg-border tw:-mx-1 tw:my-1 tw:h-px", className)} {...props} /> ); } -function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) { +function DropdownMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { return ( <span data-slot="dropdown-menu-shortcut" - className={cn('tw:text-muted-foreground tw:ml-auto tw:text-xs tw:tracking-widest', className)} + className={cn( + "tw:text-muted-foreground tw:ml-auto tw:text-xs tw:tracking-widest", + className, + )} {...props} /> ); } -function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) { +function DropdownMenuSub({ + ...props +}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) { return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />; } @@ -179,8 +208,8 @@ function DropdownMenuSubTrigger({ data-slot="dropdown-menu-sub-trigger" data-inset={inset} className={cn( - 'tw:focus:bg-accent tw:focus:text-accent-foreground tw:data-[state=open]:bg-accent tw:data-[state=open]:text-accent-foreground tw:[&_svg:not([class*=text-])]:text-muted-foreground tw:flex tw:cursor-default tw:items-center tw:gap-2 tw:rounded-sm tw:px-2 tw:py-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:data-[inset]:pl-8 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4', - className + "tw:focus:bg-accent tw:focus:text-accent-foreground tw:data-[state=open]:bg-accent tw:data-[state=open]:text-accent-foreground tw:[&_svg:not([class*=text-])]:text-muted-foreground tw:flex tw:cursor-default tw:items-center tw:gap-2 tw:rounded-sm tw:px-2 tw:py-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:data-[inset]:pl-8 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4", + className, )} {...props} > @@ -198,8 +227,8 @@ function DropdownMenuSubContent({ <DropdownMenuPrimitive.SubContent data-slot="dropdown-menu-sub-content" className={cn( - 'tw:bg-popover tw:text-popover-foreground tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=open]:fade-in-0 tw:data-[state=closed]:zoom-out-95 tw:data-[state=open]:zoom-in-95 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:z-50 tw:min-w-[8rem] tw:origin-(--radix-dropdown-menu-content-transform-origin) tw:overflow-hidden tw:rounded-md tw:border tw:p-1 tw:shadow-lg', - className + "tw:bg-popover tw:text-popover-foreground tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=open]:fade-in-0 tw:data-[state=closed]:zoom-out-95 tw:data-[state=open]:zoom-in-95 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:z-50 tw:min-w-[8rem] tw:origin-(--radix-dropdown-menu-content-transform-origin) tw:overflow-hidden tw:rounded-md tw:border tw:p-1 tw:shadow-lg", + className, )} {...props} /> diff --git a/website/src/components/ui/index.ts b/website/src/components/ui/index.ts index 41507b0d1..bfc8776e3 100644 --- a/website/src/components/ui/index.ts +++ b/website/src/components/ui/index.ts @@ -1,21 +1,21 @@ // Custom PE components -export { Container } from './Container'; -export type { ContainerProps } from './Container'; +export { Container } from "./Container"; +export type { ContainerProps } from "./Container"; -export { Group } from './Group'; -export type { GroupProps } from './Group'; +export { Group } from "./Group"; +export type { GroupProps } from "./Group"; -export { Spinner } from './Spinner'; -export type { SpinnerProps } from './Spinner'; +export { Spinner } from "./Spinner"; +export type { SpinnerProps } from "./Spinner"; -export { Stack } from './Stack'; -export type { StackProps } from './Stack'; +export { Stack } from "./Stack"; +export type { StackProps } from "./Stack"; -export { Text, textVariants } from './Text'; -export type { TextProps } from './Text'; +export { Text, textVariants } from "./Text"; +export type { TextProps } from "./Text"; -export { Title } from './Title'; -export type { TitleProps } from './Title'; +export { Title } from "./Title"; +export type { TitleProps } from "./Title"; // shadcn/ui components export { @@ -34,13 +34,25 @@ export { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, -} from './dropdown-menu'; -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from './accordion'; -export { Alert, AlertTitle, AlertDescription } from './alert'; -export { Badge, badgeVariants } from './badge'; -export { Button, buttonVariants } from './button'; -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from './card'; -export { Checkbox } from './checkbox'; +} from "./dropdown-menu"; +export { + Accordion, + AccordionItem, + AccordionTrigger, + AccordionContent, +} from "./accordion"; +export { Alert, AlertTitle, AlertDescription } from "./alert"; +export { Badge, badgeVariants } from "./badge"; +export { Button, buttonVariants } from "./button"; +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +} from "./card"; +export { Checkbox } from "./checkbox"; export { Dialog, DialogPortal, @@ -52,12 +64,12 @@ export { DialogFooter, DialogTitle, DialogDescription, -} from './dialog'; -export { Input } from './input'; -export { Label } from './label'; -export { Progress } from './progress'; -export { RadioGroup, RadioGroupItem } from './radio-group'; -export { ScrollArea, ScrollBar } from './scroll-area'; +} from "./dialog"; +export { Input } from "./input"; +export { Label } from "./label"; +export { Progress } from "./progress"; +export { RadioGroup, RadioGroupItem } from "./radio-group"; +export { ScrollArea, ScrollBar } from "./scroll-area"; export { Select, SelectGroup, @@ -69,8 +81,8 @@ export { SelectSeparator, SelectScrollUpButton, SelectScrollDownButton, -} from './select'; -export { Separator } from './separator'; +} from "./select"; +export { Separator } from "./separator"; export { Sheet, SheetPortal, @@ -82,9 +94,9 @@ export { SheetFooter, SheetTitle, SheetDescription, -} from './sheet'; -export { Skeleton } from './skeleton'; -export { Switch } from './switch'; +} from "./sheet"; +export { Skeleton } from "./skeleton"; +export { Switch } from "./switch"; export { Table as ShadcnTable, TableHeader, @@ -94,13 +106,25 @@ export { TableRow, TableCell, TableCaption, -} from './table'; -export { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs'; -export { Textarea } from './textarea'; -export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './tooltip'; -export { Collapsible, CollapsibleTrigger, CollapsibleContent } from './collapsible'; -export { SegmentedControl } from './segmented-control'; -export type { SegmentedControlOption, SegmentedControlProps } from './segmented-control'; +} from "./table"; +export { Tabs, TabsList, TabsTrigger, TabsContent } from "./tabs"; +export { Textarea } from "./textarea"; +export { + Tooltip, + TooltipTrigger, + TooltipContent, + TooltipProvider, +} from "./tooltip"; +export { + Collapsible, + CollapsibleTrigger, + CollapsibleContent, +} from "./collapsible"; +export { SegmentedControl } from "./segmented-control"; +export type { + SegmentedControlOption, + SegmentedControlProps, +} from "./segmented-control"; export { Popover, PopoverTrigger, @@ -109,7 +133,7 @@ export { PopoverHeader, PopoverTitle, PopoverDescription, -} from './popover'; +} from "./popover"; export { Command, CommandDialog, @@ -120,8 +144,8 @@ export { CommandItem, CommandShortcut, CommandSeparator, -} from './command'; -export { YearPicker } from './year-picker'; -export type { YearPickerProps } from './year-picker'; -export { DatePicker } from './date-picker'; -export type { DatePickerProps } from './date-picker'; +} from "./command"; +export { YearPicker } from "./year-picker"; +export type { YearPickerProps } from "./year-picker"; +export { DatePicker } from "./date-picker"; +export type { DatePickerProps } from "./date-picker"; diff --git a/website/src/components/ui/input.tsx b/website/src/components/ui/input.tsx index d99d6e1f6..ba674d227 100644 --- a/website/src/components/ui/input.tsx +++ b/website/src/components/ui/input.tsx @@ -1,16 +1,16 @@ -import * as React from 'react'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { cn } from "@/lib/utils"; -function Input({ className, type, ...props }: React.ComponentProps<'input'>) { +function Input({ className, type, ...props }: React.ComponentProps<"input">) { return ( <input type={type} data-slot="input" className={cn( - 'tw:file:text-foreground tw:placeholder:text-muted-foreground tw:selection:bg-primary tw:selection:text-primary-foreground tw:border-input tw:h-9 tw:w-full tw:min-w-0 tw:rounded-md tw:border tw:px-3 tw:py-1 tw:text-base tw:shadow-xs tw:transition-[color,box-shadow] tw:outline-none tw:file:inline-flex tw:file:h-7 tw:file:border-0 tw:file:bg-transparent tw:file:text-sm tw:file:font-medium tw:disabled:pointer-events-none tw:disabled:cursor-not-allowed tw:disabled:opacity-50 tw:md:text-sm', - 'tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:focus-visible:ring-[3px]', - 'tw:aria-invalid:ring-destructive/20 tw:aria-invalid:border-destructive', - className + "tw:file:text-foreground tw:placeholder:text-muted-foreground tw:selection:bg-primary tw:selection:text-primary-foreground tw:border-input tw:h-9 tw:w-full tw:min-w-0 tw:rounded-md tw:border tw:px-3 tw:py-1 tw:text-base tw:shadow-xs tw:transition-[color,box-shadow] tw:outline-none tw:file:inline-flex tw:file:h-7 tw:file:border-0 tw:file:bg-transparent tw:file:text-sm tw:file:font-medium tw:disabled:pointer-events-none tw:disabled:cursor-not-allowed tw:disabled:opacity-50 tw:md:text-sm", + "tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:focus-visible:ring-[3px]", + "tw:aria-invalid:ring-destructive/20 tw:aria-invalid:border-destructive", + className, )} {...props} /> diff --git a/website/src/components/ui/label.tsx b/website/src/components/ui/label.tsx index e281ad07b..e73cf929b 100644 --- a/website/src/components/ui/label.tsx +++ b/website/src/components/ui/label.tsx @@ -1,16 +1,19 @@ -'use client'; +"use client"; -import * as React from 'react'; -import { Label as LabelPrimitive } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { Label as LabelPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; -function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) { +function Label({ + className, + ...props +}: React.ComponentProps<typeof LabelPrimitive.Root>) { return ( <LabelPrimitive.Root data-slot="label" className={cn( - 'tw:flex tw:items-center tw:gap-2 tw:text-sm tw:leading-none tw:font-normal tw:select-none tw:group-data-[disabled=true]:pointer-events-none tw:group-data-[disabled=true]:opacity-50 tw:peer-disabled:cursor-not-allowed tw:peer-disabled:opacity-50', - className + "tw:flex tw:items-center tw:gap-2 tw:text-sm tw:leading-none tw:font-normal tw:select-none tw:group-data-[disabled=true]:pointer-events-none tw:group-data-[disabled=true]:opacity-50 tw:peer-disabled:cursor-not-allowed tw:peer-disabled:opacity-50", + className, )} {...props} /> diff --git a/website/src/components/ui/popover.tsx b/website/src/components/ui/popover.tsx index 3db0f3b42..8fc8818be 100644 --- a/website/src/components/ui/popover.tsx +++ b/website/src/components/ui/popover.tsx @@ -1,18 +1,22 @@ -import * as React from 'react'; -import { Popover as PopoverPrimitive } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { Popover as PopoverPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; -function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) { +function Popover({ + ...props +}: React.ComponentProps<typeof PopoverPrimitive.Root>) { return <PopoverPrimitive.Root data-slot="popover" {...props} />; } -function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) { +function PopoverTrigger({ + ...props +}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) { return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />; } function PopoverContent({ className, - align = 'center', + align = "center", sideOffset = 4, ...props }: React.ComponentProps<typeof PopoverPrimitive.Content>) { @@ -23,8 +27,8 @@ function PopoverContent({ align={align} sideOffset={sideOffset} className={cn( - 'tw:bg-popover tw:text-popover-foreground tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=open]:fade-in-0 tw:data-[state=closed]:zoom-out-95 tw:data-[state=open]:zoom-in-95 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:z-50 tw:w-72 tw:origin-(--radix-popover-content-transform-origin) tw:rounded-md tw:border tw:p-4 tw:shadow-md tw:outline-hidden', - className + "tw:bg-popover tw:text-popover-foreground tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=open]:fade-in-0 tw:data-[state=closed]:zoom-out-95 tw:data-[state=open]:zoom-in-95 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:z-50 tw:w-72 tw:origin-(--radix-popover-content-transform-origin) tw:rounded-md tw:border tw:p-4 tw:shadow-md tw:outline-hidden", + className, )} {...props} /> @@ -32,29 +36,40 @@ function PopoverContent({ ); } -function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) { +function PopoverAnchor({ + ...props +}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) { return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />; } -function PopoverHeader({ className, ...props }: React.ComponentProps<'div'>) { +function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="popover-header" - className={cn('tw:flex tw:flex-col tw:gap-1 tw:text-sm', className)} + className={cn("tw:flex tw:flex-col tw:gap-1 tw:text-sm", className)} {...props} /> ); } -function PopoverTitle({ className, ...props }: React.ComponentProps<'h2'>) { - return <div data-slot="popover-title" className={cn('tw:font-medium', className)} {...props} />; +function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">) { + return ( + <div + data-slot="popover-title" + className={cn("tw:font-medium", className)} + {...props} + /> + ); } -function PopoverDescription({ className, ...props }: React.ComponentProps<'p'>) { +function PopoverDescription({ + className, + ...props +}: React.ComponentProps<"p">) { return ( <p data-slot="popover-description" - className={cn('tw:text-muted-foreground', className)} + className={cn("tw:text-muted-foreground", className)} {...props} /> ); diff --git a/website/src/components/ui/progress.tsx b/website/src/components/ui/progress.tsx index 9b0d01c78..a503d223e 100644 --- a/website/src/components/ui/progress.tsx +++ b/website/src/components/ui/progress.tsx @@ -1,8 +1,8 @@ -'use client'; +"use client"; -import * as React from 'react'; -import { Progress as ProgressPrimitive } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { Progress as ProgressPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; function Progress({ className, @@ -13,8 +13,8 @@ function Progress({ <ProgressPrimitive.Root data-slot="progress" className={cn( - 'tw:bg-primary/20 tw:relative tw:h-2 tw:w-full tw:overflow-hidden tw:rounded-full', - className + "tw:bg-primary/20 tw:relative tw:h-2 tw:w-full tw:overflow-hidden tw:rounded-full", + className, )} {...props} > diff --git a/website/src/components/ui/radio-group.tsx b/website/src/components/ui/radio-group.tsx index 2fbbd367b..27a5eabf2 100644 --- a/website/src/components/ui/radio-group.tsx +++ b/website/src/components/ui/radio-group.tsx @@ -1,7 +1,7 @@ -import * as React from 'react'; -import { CircleIcon } from 'lucide-react'; -import { RadioGroup as RadioGroupPrimitive } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { CircleIcon } from "lucide-react"; +import { RadioGroup as RadioGroupPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; function RadioGroup({ className, @@ -10,7 +10,7 @@ function RadioGroup({ return ( <RadioGroupPrimitive.Root data-slot="radio-group" - className={cn('tw:grid tw:gap-3', className)} + className={cn("tw:grid tw:gap-3", className)} {...props} /> ); @@ -24,8 +24,8 @@ function RadioGroupItem({ <RadioGroupPrimitive.Item data-slot="radio-group-item" className={cn( - 'tw:border-input tw:text-primary tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:aria-invalid:ring-destructive/20 tw:aria-invalid:border-destructive tw:aspect-square tw:size-4 tw:shrink-0 tw:rounded-full tw:border tw:shadow-xs tw:transition-[color,box-shadow] tw:outline-none tw:focus-visible:ring-[3px] tw:disabled:cursor-not-allowed tw:disabled:opacity-50', - className + "tw:border-input tw:text-primary tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:aria-invalid:ring-destructive/20 tw:aria-invalid:border-destructive tw:aspect-square tw:size-4 tw:shrink-0 tw:rounded-full tw:border tw:shadow-xs tw:transition-[color,box-shadow] tw:outline-none tw:focus-visible:ring-[3px] tw:disabled:cursor-not-allowed tw:disabled:opacity-50", + className, )} {...props} > diff --git a/website/src/components/ui/scroll-area.tsx b/website/src/components/ui/scroll-area.tsx index f1ceedfd5..550f70f7a 100644 --- a/website/src/components/ui/scroll-area.tsx +++ b/website/src/components/ui/scroll-area.tsx @@ -1,6 +1,6 @@ -import * as React from 'react'; -import { ScrollArea as ScrollAreaPrimitive } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; function ScrollArea({ className, @@ -10,7 +10,7 @@ function ScrollArea({ return ( <ScrollAreaPrimitive.Root data-slot="scroll-area" - className={cn('tw:relative', className)} + className={cn("tw:relative", className)} {...props} > <ScrollAreaPrimitive.Viewport @@ -27,7 +27,7 @@ function ScrollArea({ function ScrollBar({ className, - orientation = 'vertical', + orientation = "vertical", ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) { return ( @@ -35,10 +35,12 @@ function ScrollBar({ data-slot="scroll-area-scrollbar" orientation={orientation} className={cn( - 'tw:flex tw:touch-none tw:p-px tw:transition-colors tw:select-none', - orientation === 'vertical' && 'tw:h-full tw:w-2.5 tw:border-l tw:border-l-transparent', - orientation === 'horizontal' && 'tw:h-2.5 tw:flex-col tw:border-t tw:border-t-transparent', - className + "tw:flex tw:touch-none tw:p-px tw:transition-colors tw:select-none", + orientation === "vertical" && + "tw:h-full tw:w-2.5 tw:border-l tw:border-l-transparent", + orientation === "horizontal" && + "tw:h-2.5 tw:flex-col tw:border-t tw:border-t-transparent", + className, )} {...props} > diff --git a/website/src/components/ui/segmented-control.story.tsx b/website/src/components/ui/segmented-control.story.tsx index 7db3de434..af33ff286 100644 --- a/website/src/components/ui/segmented-control.story.tsx +++ b/website/src/components/ui/segmented-control.story.tsx @@ -1,10 +1,10 @@ "use client"; -import { useState } from 'react'; -import type { Meta, StoryObj } from '@storybook/react'; -import { SegmentedControl } from './segmented-control'; +import { useState } from "react"; +import type { Meta, StoryObj } from "@storybook/react"; +import { SegmentedControl } from "./segmented-control"; const meta: Meta<typeof SegmentedControl> = { - title: 'UI/SegmentedControl', + title: "UI/SegmentedControl", component: SegmentedControl, }; @@ -16,20 +16,27 @@ function ControlledSegmented({ options, defaultValue, }: { - size?: 'xs' | 'sm'; + size?: "xs" | "sm"; options: { label: string; value: string; disabled?: boolean }[]; defaultValue: string; }) { const [value, setValue] = useState(defaultValue); - return <SegmentedControl value={value} onValueChange={setValue} options={options} size={size} />; + return ( + <SegmentedControl + value={value} + onValueChange={setValue} + options={options} + size={size} + /> + ); } export const Default: Story = { render: () => ( <ControlledSegmented options={[ - { label: 'Absolute', value: 'absolute' }, - { label: 'Relative', value: 'relative' }, + { label: "Absolute", value: "absolute" }, + { label: "Relative", value: "relative" }, ]} defaultValue="absolute" /> @@ -41,8 +48,8 @@ export const ExtraSmall: Story = { <ControlledSegmented size="xs" options={[ - { label: 'Geographic', value: 'geographic' }, - { label: 'Hex grid', value: 'hex' }, + { label: "Geographic", value: "geographic" }, + { label: "Hex grid", value: "hex" }, ]} defaultValue="geographic" /> @@ -53,9 +60,9 @@ export const ThreeOptions: Story = { render: () => ( <ControlledSegmented options={[ - { label: 'Income', value: 'income' }, - { label: 'Wealth', value: 'wealth' }, - { label: 'Consumption', value: 'consumption' }, + { label: "Income", value: "income" }, + { label: "Wealth", value: "wealth" }, + { label: "Consumption", value: "consumption" }, ]} defaultValue="income" /> diff --git a/website/src/components/ui/segmented-control.tsx b/website/src/components/ui/segmented-control.tsx index d1297bd16..d7132b40e 100644 --- a/website/src/components/ui/segmented-control.tsx +++ b/website/src/components/ui/segmented-control.tsx @@ -1,6 +1,6 @@ -import * as React from 'react'; -import { Tabs as TabsPrimitive } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { Tabs as TabsPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; interface SegmentedControlOption { label: string; @@ -12,7 +12,7 @@ interface SegmentedControlProps { value: string; onValueChange: (value: string) => void; options: SegmentedControlOption[]; - size?: 'xs' | 'sm'; + size?: "xs" | "sm"; className?: string; } @@ -20,7 +20,7 @@ function SegmentedControl({ value, onValueChange, options, - size = 'sm', + size = "sm", className, }: SegmentedControlProps) { return ( @@ -28,9 +28,9 @@ function SegmentedControl({ <TabsPrimitive.List data-slot="segmented-control" className={cn( - 'tw:inline-flex tw:items-center tw:gap-1 tw:rounded-lg tw:bg-muted', - size === 'xs' ? 'tw:h-7 tw:p-1' : 'tw:h-9 tw:p-1', - className + "tw:inline-flex tw:items-center tw:gap-1 tw:rounded-lg tw:bg-muted", + size === "xs" ? "tw:h-7 tw:p-1" : "tw:h-9 tw:p-1", + className, )} > {options.map((option) => ( @@ -39,10 +39,10 @@ function SegmentedControl({ value={option.value} disabled={option.disabled} className={cn( - 'tw:inline-flex tw:h-full tw:items-center tw:justify-center tw:rounded-md tw:px-2 tw:font-medium tw:whitespace-nowrap tw:transition-all', - 'tw:text-muted-foreground tw:data-[state=active]:bg-background tw:data-[state=active]:text-foreground tw:data-[state=active]:shadow-sm', - 'tw:disabled:pointer-events-none tw:disabled:opacity-50', - size === 'xs' ? 'tw:text-xs' : 'tw:text-sm' + "tw:inline-flex tw:h-full tw:items-center tw:justify-center tw:rounded-md tw:px-2 tw:font-medium tw:whitespace-nowrap tw:transition-all", + "tw:text-muted-foreground tw:data-[state=active]:bg-background tw:data-[state=active]:text-foreground tw:data-[state=active]:shadow-sm", + "tw:disabled:pointer-events-none tw:disabled:opacity-50", + size === "xs" ? "tw:text-xs" : "tw:text-sm", )} > {option.label} diff --git a/website/src/components/ui/select.tsx b/website/src/components/ui/select.tsx index 0f519d18d..a1b2443f9 100644 --- a/website/src/components/ui/select.tsx +++ b/website/src/components/ui/select.tsx @@ -1,35 +1,41 @@ -import * as React from 'react'; -import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react'; -import { Select as SelectPrimitive } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"; +import { Select as SelectPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; -function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) { +function Select({ + ...props +}: React.ComponentProps<typeof SelectPrimitive.Root>) { return <SelectPrimitive.Root data-slot="select" {...props} />; } -function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) { +function SelectGroup({ + ...props +}: React.ComponentProps<typeof SelectPrimitive.Group>) { return <SelectPrimitive.Group data-slot="select-group" {...props} />; } -function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) { +function SelectValue({ + ...props +}: React.ComponentProps<typeof SelectPrimitive.Value>) { return <SelectPrimitive.Value data-slot="select-value" {...props} />; } function SelectTrigger({ className, - size = 'default', + size = "default", children, ...props }: React.ComponentProps<typeof SelectPrimitive.Trigger> & { - size?: 'sm' | 'default'; + size?: "sm" | "default"; }) { return ( <SelectPrimitive.Trigger data-slot="select-trigger" data-size={size} className={cn( - 'tw:border-input tw:data-[placeholder]:text-muted-foreground tw:[&_svg:not([class*=text-])]:text-muted-foreground tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:aria-invalid:ring-destructive/20 tw:aria-invalid:border-destructive tw:flex tw:w-full tw:items-center tw:justify-between tw:gap-2 tw:rounded-md tw:border tw:px-3 tw:py-2 tw:text-sm tw:whitespace-nowrap tw:shadow-xs tw:transition-[color,box-shadow] tw:outline-none tw:focus-visible:ring-[3px] tw:disabled:cursor-not-allowed tw:disabled:opacity-50 tw:data-[size=default]:h-9 tw:data-[size=sm]:h-8 tw:*:data-[slot=select-value]:line-clamp-1 tw:*:data-[slot=select-value]:flex tw:*:data-[slot=select-value]:items-center tw:*:data-[slot=select-value]:gap-2 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4', - className + "tw:border-input tw:data-[placeholder]:text-muted-foreground tw:[&_svg:not([class*=text-])]:text-muted-foreground tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:aria-invalid:ring-destructive/20 tw:aria-invalid:border-destructive tw:flex tw:w-full tw:items-center tw:justify-between tw:gap-2 tw:rounded-md tw:border tw:px-3 tw:py-2 tw:text-sm tw:whitespace-nowrap tw:shadow-xs tw:transition-[color,box-shadow] tw:outline-none tw:focus-visible:ring-[3px] tw:disabled:cursor-not-allowed tw:disabled:opacity-50 tw:data-[size=default]:h-9 tw:data-[size=sm]:h-8 tw:*:data-[slot=select-value]:line-clamp-1 tw:*:data-[slot=select-value]:flex tw:*:data-[slot=select-value]:items-center tw:*:data-[slot=select-value]:gap-2 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4", + className, )} {...props} > @@ -44,8 +50,8 @@ function SelectTrigger({ function SelectContent({ className, children, - position = 'item-aligned', - align = 'center', + position = "item-aligned", + align = "center", ...props }: React.ComponentProps<typeof SelectPrimitive.Content>) { return ( @@ -53,10 +59,10 @@ function SelectContent({ <SelectPrimitive.Content data-slot="select-content" className={cn( - 'tw:bg-popover tw:text-popover-foreground tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=open]:fade-in-0 tw:data-[state=closed]:zoom-out-95 tw:data-[state=open]:zoom-in-95 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:relative tw:z-50 tw:max-h-(--radix-select-content-available-height) tw:min-w-[8rem] tw:origin-(--radix-select-content-transform-origin) tw:overflow-x-hidden tw:overflow-y-auto tw:rounded-md tw:border tw:shadow-md', - position === 'popper' && - 'tw:data-[side=bottom]:translate-y-1 tw:data-[side=left]:-translate-x-1 tw:data-[side=right]:translate-x-1 tw:data-[side=top]:-translate-y-1', - className + "tw:bg-popover tw:text-popover-foreground tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=open]:fade-in-0 tw:data-[state=closed]:zoom-out-95 tw:data-[state=open]:zoom-in-95 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:relative tw:z-50 tw:max-h-(--radix-select-content-available-height) tw:min-w-[8rem] tw:origin-(--radix-select-content-transform-origin) tw:overflow-x-hidden tw:overflow-y-auto tw:rounded-md tw:border tw:shadow-md", + position === "popper" && + "tw:data-[side=bottom]:translate-y-1 tw:data-[side=left]:-translate-x-1 tw:data-[side=right]:translate-x-1 tw:data-[side=top]:-translate-y-1", + className, )} position={position} align={align} @@ -65,9 +71,9 @@ function SelectContent({ <SelectScrollUpButton /> <SelectPrimitive.Viewport className={cn( - 'tw:p-1', - position === 'popper' && - 'tw:h-[var(--radix-select-trigger-height)] tw:w-full tw:min-w-[var(--radix-select-trigger-width)] tw:scroll-my-1' + "tw:p-1", + position === "popper" && + "tw:h-[var(--radix-select-trigger-height)] tw:w-full tw:min-w-[var(--radix-select-trigger-width)] tw:scroll-my-1", )} > {children} @@ -78,11 +84,17 @@ function SelectContent({ ); } -function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) { +function SelectLabel({ + className, + ...props +}: React.ComponentProps<typeof SelectPrimitive.Label>) { return ( <SelectPrimitive.Label data-slot="select-label" - className={cn('tw:text-muted-foreground tw:px-2 tw:py-1.5 tw:text-xs', className)} + className={cn( + "tw:text-muted-foreground tw:px-2 tw:py-1.5 tw:text-xs", + className, + )} {...props} /> ); @@ -97,8 +109,8 @@ function SelectItem({ <SelectPrimitive.Item data-slot="select-item" className={cn( - 'tw:focus:bg-accent tw:focus:text-accent-foreground tw:[&_svg:not([class*=text-])]:text-muted-foreground tw:relative tw:flex tw:w-full tw:cursor-default tw:items-center tw:gap-2 tw:rounded-sm tw:py-1.5 tw:pr-8 tw:pl-2 tw:text-sm tw:outline-hidden tw:select-none tw:data-[disabled]:pointer-events-none tw:data-[disabled]:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4 tw:*:[span]:last:flex tw:*:[span]:last:items-center tw:*:[span]:last:gap-2', - className + "tw:focus:bg-accent tw:focus:text-accent-foreground tw:[&_svg:not([class*=text-])]:text-muted-foreground tw:relative tw:flex tw:w-full tw:cursor-default tw:items-center tw:gap-2 tw:rounded-sm tw:py-1.5 tw:pr-8 tw:pl-2 tw:text-sm tw:outline-hidden tw:select-none tw:data-[disabled]:pointer-events-none tw:data-[disabled]:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4 tw:*:[span]:last:flex tw:*:[span]:last:items-center tw:*:[span]:last:gap-2", + className, )} {...props} > @@ -122,7 +134,10 @@ function SelectSeparator({ return ( <SelectPrimitive.Separator data-slot="select-separator" - className={cn('tw:bg-border tw:pointer-events-none tw:-mx-1 tw:my-1 tw:h-px', className)} + className={cn( + "tw:bg-border tw:pointer-events-none tw:-mx-1 tw:my-1 tw:h-px", + className, + )} {...props} /> ); @@ -136,8 +151,8 @@ function SelectScrollUpButton({ <SelectPrimitive.ScrollUpButton data-slot="select-scroll-up-button" className={cn( - 'tw:flex tw:cursor-default tw:items-center tw:justify-center tw:py-1', - className + "tw:flex tw:cursor-default tw:items-center tw:justify-center tw:py-1", + className, )} {...props} > @@ -154,8 +169,8 @@ function SelectScrollDownButton({ <SelectPrimitive.ScrollDownButton data-slot="select-scroll-down-button" className={cn( - 'tw:flex tw:cursor-default tw:items-center tw:justify-center tw:py-1', - className + "tw:flex tw:cursor-default tw:items-center tw:justify-center tw:py-1", + className, )} {...props} > diff --git a/website/src/components/ui/separator.tsx b/website/src/components/ui/separator.tsx index 62ec24a0c..422e2f57b 100644 --- a/website/src/components/ui/separator.tsx +++ b/website/src/components/ui/separator.tsx @@ -1,12 +1,12 @@ -'use client'; +"use client"; -import * as React from 'react'; -import { Separator as SeparatorPrimitive } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { Separator as SeparatorPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; function Separator({ className, - orientation = 'horizontal', + orientation = "horizontal", decorative = true, ...props }: React.ComponentProps<typeof SeparatorPrimitive.Root>) { @@ -16,8 +16,8 @@ function Separator({ decorative={decorative} orientation={orientation} className={cn( - 'tw:bg-border tw:shrink-0 tw:data-[orientation=horizontal]:h-px tw:data-[orientation=horizontal]:w-full tw:data-[orientation=vertical]:h-full tw:data-[orientation=vertical]:w-px', - className + "tw:bg-border tw:shrink-0 tw:data-[orientation=horizontal]:h-px tw:data-[orientation=horizontal]:w-full tw:data-[orientation=vertical]:h-full tw:data-[orientation=vertical]:w-px", + className, )} {...props} /> diff --git a/website/src/components/ui/sheet.tsx b/website/src/components/ui/sheet.tsx index 64de61f5c..8744d076a 100644 --- a/website/src/components/ui/sheet.tsx +++ b/website/src/components/ui/sheet.tsx @@ -1,21 +1,27 @@ -import * as React from 'react'; -import { XIcon } from 'lucide-react'; -import { Dialog as SheetPrimitive } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { XIcon } from "lucide-react"; +import { Dialog as SheetPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) { return <SheetPrimitive.Root data-slot="sheet" {...props} />; } -function SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>) { +function SheetTrigger({ + ...props +}: React.ComponentProps<typeof SheetPrimitive.Trigger>) { return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />; } -function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>) { +function SheetClose({ + ...props +}: React.ComponentProps<typeof SheetPrimitive.Close>) { return <SheetPrimitive.Close data-slot="sheet-close" {...props} />; } -function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) { +function SheetPortal({ + ...props +}: React.ComponentProps<typeof SheetPrimitive.Portal>) { return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />; } @@ -27,8 +33,8 @@ function SheetOverlay({ <SheetPrimitive.Overlay data-slot="sheet-overlay" className={cn( - 'tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=open]:fade-in-0 tw:fixed tw:inset-0 tw:z-50 tw:bg-black/50', - className + "tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=open]:fade-in-0 tw:fixed tw:inset-0 tw:z-50 tw:bg-black/50", + className, )} {...props} /> @@ -38,11 +44,11 @@ function SheetOverlay({ function SheetContent({ className, children, - side = 'right', + side = "right", showCloseButton = true, ...props }: React.ComponentProps<typeof SheetPrimitive.Content> & { - side?: 'top' | 'right' | 'bottom' | 'left'; + side?: "top" | "right" | "bottom" | "left"; showCloseButton?: boolean; }) { return ( @@ -51,16 +57,16 @@ function SheetContent({ <SheetPrimitive.Content data-slot="sheet-content" className={cn( - 'tw:bg-background tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:fixed tw:z-50 tw:flex tw:flex-col tw:gap-4 tw:shadow-lg tw:transition tw:ease-in-out tw:data-[state=closed]:duration-300 tw:data-[state=open]:duration-500', - side === 'right' && - 'tw:data-[state=closed]:slide-out-to-right tw:data-[state=open]:slide-in-from-right tw:inset-y-0 tw:right-0 tw:h-full tw:w-3/4 tw:border-l tw:sm:max-w-sm', - side === 'left' && - 'tw:data-[state=closed]:slide-out-to-left tw:data-[state=open]:slide-in-from-left tw:inset-y-0 tw:left-0 tw:h-full tw:w-3/4 tw:border-r tw:sm:max-w-sm', - side === 'top' && - 'tw:data-[state=closed]:slide-out-to-top tw:data-[state=open]:slide-in-from-top tw:inset-x-0 tw:top-0 tw:h-auto tw:border-b', - side === 'bottom' && - 'tw:data-[state=closed]:slide-out-to-bottom tw:data-[state=open]:slide-in-from-bottom tw:inset-x-0 tw:bottom-0 tw:h-auto tw:border-t', - className + "tw:bg-background tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out tw:fixed tw:z-50 tw:flex tw:flex-col tw:gap-4 tw:shadow-lg tw:transition tw:ease-in-out tw:data-[state=closed]:duration-300 tw:data-[state=open]:duration-500", + side === "right" && + "tw:data-[state=closed]:slide-out-to-right tw:data-[state=open]:slide-in-from-right tw:inset-y-0 tw:right-0 tw:h-full tw:w-3/4 tw:border-l tw:sm:max-w-sm", + side === "left" && + "tw:data-[state=closed]:slide-out-to-left tw:data-[state=open]:slide-in-from-left tw:inset-y-0 tw:left-0 tw:h-full tw:w-3/4 tw:border-r tw:sm:max-w-sm", + side === "top" && + "tw:data-[state=closed]:slide-out-to-top tw:data-[state=open]:slide-in-from-top tw:inset-x-0 tw:top-0 tw:h-auto tw:border-b", + side === "bottom" && + "tw:data-[state=closed]:slide-out-to-bottom tw:data-[state=open]:slide-in-from-bottom tw:inset-x-0 tw:bottom-0 tw:h-auto tw:border-t", + className, )} {...props} > @@ -76,31 +82,37 @@ function SheetContent({ ); } -function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) { +function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="sheet-header" - className={cn('tw:flex tw:flex-col tw:gap-1.5 tw:p-4', className)} + className={cn("tw:flex tw:flex-col tw:gap-1.5 tw:p-4", className)} {...props} /> ); } -function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) { +function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="sheet-footer" - className={cn('tw:mt-auto tw:flex tw:flex-col tw:gap-2 tw:p-4', className)} + className={cn( + "tw:mt-auto tw:flex tw:flex-col tw:gap-2 tw:p-4", + className, + )} {...props} /> ); } -function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) { +function SheetTitle({ + className, + ...props +}: React.ComponentProps<typeof SheetPrimitive.Title>) { return ( <SheetPrimitive.Title data-slot="sheet-title" - className={cn('tw:text-foreground tw:font-semibold', className)} + className={cn("tw:text-foreground tw:font-semibold", className)} {...props} /> ); @@ -113,7 +125,7 @@ function SheetDescription({ return ( <SheetPrimitive.Description data-slot="sheet-description" - className={cn('tw:text-muted-foreground tw:text-sm', className)} + className={cn("tw:text-muted-foreground tw:text-sm", className)} {...props} /> ); diff --git a/website/src/components/ui/skeleton.tsx b/website/src/components/ui/skeleton.tsx index c5dce1ca8..7416cd1ad 100644 --- a/website/src/components/ui/skeleton.tsx +++ b/website/src/components/ui/skeleton.tsx @@ -1,10 +1,10 @@ -import { cn } from '@/lib/utils'; +import { cn } from "@/lib/utils"; -function Skeleton({ className, ...props }: React.ComponentProps<'div'>) { +function Skeleton({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="skeleton" - className={cn('tw:bg-accent tw:animate-pulse tw:rounded-md', className)} + className={cn("tw:bg-accent tw:animate-pulse tw:rounded-md", className)} {...props} /> ); diff --git a/website/src/components/ui/switch.tsx b/website/src/components/ui/switch.tsx index f0626d3fb..1aca633e7 100644 --- a/website/src/components/ui/switch.tsx +++ b/website/src/components/ui/switch.tsx @@ -1,30 +1,30 @@ -'use client'; +"use client"; -import * as React from 'react'; -import { Switch as SwitchPrimitive } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { Switch as SwitchPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; function Switch({ className, - size = 'default', + size = "default", ...props }: React.ComponentProps<typeof SwitchPrimitive.Root> & { - size?: 'sm' | 'default'; + size?: "sm" | "default"; }) { return ( <SwitchPrimitive.Root data-slot="switch" data-size={size} className={cn( - 'tw:peer tw:data-[state=checked]:bg-primary tw:data-[state=unchecked]:bg-input tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:group/switch tw:inline-flex tw:shrink-0 tw:items-center tw:rounded-full tw:border tw:border-transparent tw:shadow-xs tw:transition-all tw:outline-none tw:focus-visible:ring-[3px] tw:disabled:cursor-not-allowed tw:disabled:opacity-50 tw:data-[size=default]:h-[1.15rem] tw:data-[size=default]:w-8 tw:data-[size=sm]:h-3.5 tw:data-[size=sm]:w-6', - className + "tw:peer tw:data-[state=checked]:bg-primary tw:data-[state=unchecked]:bg-input tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:group/switch tw:inline-flex tw:shrink-0 tw:items-center tw:rounded-full tw:border tw:border-transparent tw:shadow-xs tw:transition-all tw:outline-none tw:focus-visible:ring-[3px] tw:disabled:cursor-not-allowed tw:disabled:opacity-50 tw:data-[size=default]:h-[1.15rem] tw:data-[size=default]:w-8 tw:data-[size=sm]:h-3.5 tw:data-[size=sm]:w-6", + className, )} {...props} > <SwitchPrimitive.Thumb data-slot="switch-thumb" className={cn( - 'tw:bg-background tw:pointer-events-none tw:block tw:rounded-full tw:ring-0 tw:transition-transform tw:group-data-[size=default]/switch:size-4 tw:group-data-[size=sm]/switch:size-3 tw:data-[state=checked]:translate-x-[calc(100%-2px)] tw:data-[state=unchecked]:translate-x-0' + "tw:bg-background tw:pointer-events-none tw:block tw:rounded-full tw:ring-0 tw:transition-transform tw:group-data-[size=default]/switch:size-4 tw:group-data-[size=sm]/switch:size-3 tw:data-[state=checked]:translate-x-[calc(100%-2px)] tw:data-[state=unchecked]:translate-x-0", )} /> </SwitchPrimitive.Root> diff --git a/website/src/components/ui/table.tsx b/website/src/components/ui/table.tsx index d134553cb..4b76c3f72 100644 --- a/website/src/components/ui/table.tsx +++ b/website/src/components/ui/table.tsx @@ -1,100 +1,115 @@ -'use client'; +"use client"; -import * as React from 'react'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { cn } from "@/lib/utils"; -function Table({ className, ...props }: React.ComponentProps<'table'>) { +function Table({ className, ...props }: React.ComponentProps<"table">) { return ( - <div data-slot="table-container" className="tw:relative tw:w-full tw:overflow-x-auto"> + <div + data-slot="table-container" + className="tw:relative tw:w-full tw:overflow-x-auto" + > <table data-slot="table" - className={cn('tw:w-full tw:caption-bottom tw:text-sm', className)} + className={cn("tw:w-full tw:caption-bottom tw:text-sm", className)} {...props} /> </div> ); } -function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) { +function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { return ( <thead data-slot="table-header" - className={cn('tw:[&_tr]:border-b tw:[&_tr]:border-border', className)} + className={cn("tw:[&_tr]:border-b tw:[&_tr]:border-border", className)} {...props} /> ); } -function TableBody({ className, ...props }: React.ComponentProps<'tbody'>) { +function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { return ( <tbody data-slot="table-body" - className={cn('tw:[&_tr:last-child]:border-0', className)} + className={cn("tw:[&_tr:last-child]:border-0", className)} {...props} /> ); } -function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) { +function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { return ( <tfoot data-slot="table-footer" className={cn( - 'tw:bg-muted/50 tw:border-t tw:border-border tw:font-medium tw:[&>tr]:last:border-b-0', - className + "tw:bg-muted/50 tw:border-t tw:border-border tw:font-medium tw:[&>tr]:last:border-b-0", + className, )} {...props} /> ); } -function TableRow({ className, ...props }: React.ComponentProps<'tr'>) { +function TableRow({ className, ...props }: React.ComponentProps<"tr">) { return ( <tr data-slot="table-row" className={cn( - 'tw:hover:bg-muted/50 tw:data-[state=selected]:bg-muted tw:border-b tw:border-border tw:transition-colors', - className + "tw:hover:bg-muted/50 tw:data-[state=selected]:bg-muted tw:border-b tw:border-border tw:transition-colors", + className, )} {...props} /> ); } -function TableHead({ className, ...props }: React.ComponentProps<'th'>) { +function TableHead({ className, ...props }: React.ComponentProps<"th">) { return ( <th data-slot="table-head" className={cn( - 'tw:text-foreground tw:h-10 tw:px-2 tw:text-left tw:align-middle tw:font-medium tw:[&:has([role=checkbox])]:pr-0 tw:[&>[role=checkbox]]:translate-y-[2px]', - className + "tw:text-foreground tw:h-10 tw:px-2 tw:text-left tw:align-middle tw:font-medium tw:[&:has([role=checkbox])]:pr-0 tw:[&>[role=checkbox]]:translate-y-[2px]", + className, )} {...props} /> ); } -function TableCell({ className, ...props }: React.ComponentProps<'td'>) { +function TableCell({ className, ...props }: React.ComponentProps<"td">) { return ( <td data-slot="table-cell" className={cn( - 'tw:p-2 tw:align-middle tw:[&:has([role=checkbox])]:pr-0 tw:[&>[role=checkbox]]:translate-y-[2px]', - className + "tw:p-2 tw:align-middle tw:[&:has([role=checkbox])]:pr-0 tw:[&>[role=checkbox]]:translate-y-[2px]", + className, )} {...props} /> ); } -function TableCaption({ className, ...props }: React.ComponentProps<'caption'>) { +function TableCaption({ + className, + ...props +}: React.ComponentProps<"caption">) { return ( <caption data-slot="table-caption" - className={cn('tw:text-muted-foreground tw:mt-4 tw:text-sm', className)} + className={cn("tw:text-muted-foreground tw:mt-4 tw:text-sm", className)} {...props} /> ); } -export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }; +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +}; diff --git a/website/src/components/ui/tabs.tsx b/website/src/components/ui/tabs.tsx index 3efa13000..7c1c60f85 100644 --- a/website/src/components/ui/tabs.tsx +++ b/website/src/components/ui/tabs.tsx @@ -1,11 +1,11 @@ -import * as React from 'react'; -import { cva, type VariantProps } from 'class-variance-authority'; -import { Tabs as TabsPrimitive } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { Tabs as TabsPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; function Tabs({ className, - orientation = 'horizontal', + orientation = "horizontal", ...props }: React.ComponentProps<typeof TabsPrimitive.Root>) { return ( @@ -14,8 +14,8 @@ function Tabs({ data-orientation={orientation} orientation={orientation} className={cn( - 'tw:group/tabs tw:flex tw:gap-2 tw:data-[orientation=horizontal]:flex-col', - className + "tw:group/tabs tw:flex tw:gap-2 tw:data-[orientation=horizontal]:flex-col", + className, )} {...props} /> @@ -23,25 +23,26 @@ function Tabs({ } const tabsListVariants = cva( - 'tw:rounded-lg tw:p-[3px] tw:group-data-[orientation=horizontal]/tabs:h-9 tw:data-[variant=line]:rounded-none tw:group/tabs-list tw:text-muted-foreground tw:inline-flex tw:w-fit tw:items-center tw:justify-center tw:group-data-[orientation=vertical]/tabs:h-fit tw:group-data-[orientation=vertical]/tabs:flex-col', + "tw:rounded-lg tw:p-[3px] tw:group-data-[orientation=horizontal]/tabs:h-9 tw:data-[variant=line]:rounded-none tw:group/tabs-list tw:text-muted-foreground tw:inline-flex tw:w-fit tw:items-center tw:justify-center tw:group-data-[orientation=vertical]/tabs:h-fit tw:group-data-[orientation=vertical]/tabs:flex-col", { variants: { variant: { - default: 'tw:bg-muted', - line: 'tw:gap-1 tw:bg-transparent', + default: "tw:bg-muted", + line: "tw:gap-1 tw:bg-transparent", }, }, defaultVariants: { - variant: 'default', + variant: "default", }, - } + }, ); function TabsList({ className, - variant = 'default', + variant = "default", ...props -}: React.ComponentProps<typeof TabsPrimitive.List> & VariantProps<typeof tabsListVariants>) { +}: React.ComponentProps<typeof TabsPrimitive.List> & + VariantProps<typeof tabsListVariants>) { return ( <TabsPrimitive.List data-slot="tabs-list" @@ -52,27 +53,33 @@ function TabsList({ ); } -function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>) { +function TabsTrigger({ + className, + ...props +}: React.ComponentProps<typeof TabsPrimitive.Trigger>) { return ( <TabsPrimitive.Trigger data-slot="tabs-trigger" className={cn( - 'tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:focus-visible:outline-ring tw:text-foreground/60 tw:hover:text-foreground tw:relative tw:inline-flex tw:h-[calc(100%-1px)] tw:flex-1 tw:items-center tw:justify-center tw:gap-1.5 tw:rounded-md tw:border tw:border-transparent tw:px-2 tw:py-1 tw:text-sm tw:font-medium tw:whitespace-nowrap tw:transition-all tw:group-data-[orientation=vertical]/tabs:w-full tw:group-data-[orientation=vertical]/tabs:justify-start tw:focus-visible:ring-[3px] tw:focus-visible:outline-1 tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm tw:group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4', - 'tw:group-data-[variant=line]/tabs-list:bg-transparent tw:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent', - 'tw:data-[state=active]:bg-background tw:data-[state=active]:text-foreground', - 'tw:after:bg-foreground tw:after:absolute tw:after:opacity-0 tw:after:transition-opacity tw:group-data-[orientation=horizontal]/tabs:after:inset-x-0 tw:group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] tw:group-data-[orientation=horizontal]/tabs:after:h-0.5 tw:group-data-[orientation=vertical]/tabs:after:inset-y-0 tw:group-data-[orientation=vertical]/tabs:after:-right-1 tw:group-data-[orientation=vertical]/tabs:after:w-0.5 tw:group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100', - className + "tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:focus-visible:outline-ring tw:text-foreground/60 tw:hover:text-foreground tw:relative tw:inline-flex tw:h-[calc(100%-1px)] tw:flex-1 tw:items-center tw:justify-center tw:gap-1.5 tw:rounded-md tw:border tw:border-transparent tw:px-2 tw:py-1 tw:text-sm tw:font-medium tw:whitespace-nowrap tw:transition-all tw:group-data-[orientation=vertical]/tabs:w-full tw:group-data-[orientation=vertical]/tabs:justify-start tw:focus-visible:ring-[3px] tw:focus-visible:outline-1 tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm tw:group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4", + "tw:group-data-[variant=line]/tabs-list:bg-transparent tw:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent", + "tw:data-[state=active]:bg-background tw:data-[state=active]:text-foreground", + "tw:after:bg-foreground tw:after:absolute tw:after:opacity-0 tw:after:transition-opacity tw:group-data-[orientation=horizontal]/tabs:after:inset-x-0 tw:group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] tw:group-data-[orientation=horizontal]/tabs:after:h-0.5 tw:group-data-[orientation=vertical]/tabs:after:inset-y-0 tw:group-data-[orientation=vertical]/tabs:after:-right-1 tw:group-data-[orientation=vertical]/tabs:after:w-0.5 tw:group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100", + className, )} {...props} /> ); } -function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) { +function TabsContent({ + className, + ...props +}: React.ComponentProps<typeof TabsPrimitive.Content>) { return ( <TabsPrimitive.Content data-slot="tabs-content" - className={cn('tw:flex-1 tw:outline-none', className)} + className={cn("tw:flex-1 tw:outline-none", className)} {...props} /> ); diff --git a/website/src/components/ui/textarea.tsx b/website/src/components/ui/textarea.tsx index d81ce41b1..5a9e6ceb9 100644 --- a/website/src/components/ui/textarea.tsx +++ b/website/src/components/ui/textarea.tsx @@ -1,13 +1,13 @@ -import * as React from 'react'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { cn } from "@/lib/utils"; -function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) { +function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { return ( <textarea data-slot="textarea" className={cn( - 'tw:border-input tw:placeholder:text-muted-foreground tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:aria-invalid:ring-destructive/20 tw:aria-invalid:border-destructive tw:flex tw:field-sizing-content tw:min-h-16 tw:w-full tw:rounded-md tw:border tw:px-3 tw:py-2 tw:text-base tw:shadow-xs tw:transition-[color,box-shadow] tw:outline-none tw:focus-visible:ring-[3px] tw:disabled:cursor-not-allowed tw:disabled:opacity-50 tw:md:text-sm', - className + "tw:border-input tw:placeholder:text-muted-foreground tw:focus-visible:border-ring tw:focus-visible:ring-ring/50 tw:aria-invalid:ring-destructive/20 tw:aria-invalid:border-destructive tw:flex tw:field-sizing-content tw:min-h-16 tw:w-full tw:rounded-md tw:border tw:px-3 tw:py-2 tw:text-base tw:shadow-xs tw:transition-[color,box-shadow] tw:outline-none tw:focus-visible:ring-[3px] tw:disabled:cursor-not-allowed tw:disabled:opacity-50 tw:md:text-sm", + className, )} {...props} /> diff --git a/website/src/components/ui/tooltip.tsx b/website/src/components/ui/tooltip.tsx index 026fb6568..0fc5d8628 100644 --- a/website/src/components/ui/tooltip.tsx +++ b/website/src/components/ui/tooltip.tsx @@ -1,6 +1,6 @@ -import * as React from 'react'; -import { Tooltip as TooltipPrimitive } from 'radix-ui'; -import { cn } from '@/lib/utils'; +import * as React from "react"; +import { Tooltip as TooltipPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; function TooltipProvider({ delayDuration = 0, @@ -15,11 +15,15 @@ function TooltipProvider({ ); } -function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) { +function Tooltip({ + ...props +}: React.ComponentProps<typeof TooltipPrimitive.Root>) { return <TooltipPrimitive.Root data-slot="tooltip" {...props} />; } -function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) { +function TooltipTrigger({ + ...props +}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) { return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />; } @@ -35,8 +39,8 @@ function TooltipContent({ data-slot="tooltip-content" sideOffset={sideOffset} className={cn( - 'tw:bg-foreground tw:text-background tw:animate-in tw:fade-in-0 tw:zoom-in-95 tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=closed]:zoom-out-95 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:z-50 tw:w-fit tw:origin-(--radix-tooltip-content-transform-origin) tw:rounded-md tw:px-3 tw:py-1.5 tw:text-xs tw:text-balance', - className + "tw:bg-foreground tw:text-background tw:animate-in tw:fade-in-0 tw:zoom-in-95 tw:data-[state=closed]:animate-out tw:data-[state=closed]:fade-out-0 tw:data-[state=closed]:zoom-out-95 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:z-50 tw:w-fit tw:origin-(--radix-tooltip-content-transform-origin) tw:rounded-md tw:px-3 tw:py-1.5 tw:text-xs tw:text-balance", + className, )} {...props} > diff --git a/website/src/components/ui/year-picker.story.tsx b/website/src/components/ui/year-picker.story.tsx index 9f19f8ba4..07923e170 100644 --- a/website/src/components/ui/year-picker.story.tsx +++ b/website/src/components/ui/year-picker.story.tsx @@ -1,10 +1,10 @@ "use client"; -import { useState } from 'react'; -import type { Meta, StoryObj } from '@storybook/react'; -import { YearPicker } from './year-picker'; +import { useState } from "react"; +import type { Meta, StoryObj } from "@storybook/react"; +import { YearPicker } from "./year-picker"; const meta: Meta<typeof YearPicker> = { - title: 'UI/YearPicker', + title: "UI/YearPicker", component: YearPicker, decorators: [ (Story) => ( diff --git a/website/src/components/ui/year-picker.tsx b/website/src/components/ui/year-picker.tsx index b305656eb..92e0ebaa4 100644 --- a/website/src/components/ui/year-picker.tsx +++ b/website/src/components/ui/year-picker.tsx @@ -1,10 +1,10 @@ "use client"; -import * as React from 'react'; -import { useState } from 'react'; -import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react'; -import { cn } from '@/lib/utils'; -import { Button } from './button'; -import { Popover, PopoverContent, PopoverTrigger } from './popover'; +import * as React from "react"; +import { useState } from "react"; +import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react"; +import { cn } from "@/lib/utils"; +import { Button } from "./button"; +import { Popover, PopoverContent, PopoverTrigger } from "./popover"; interface YearPickerProps { value: Date | null | undefined; @@ -20,12 +20,14 @@ function YearPicker({ onChange, minDate, maxDate, - placeholder = 'Select year', + placeholder = "Select year", className, }: YearPickerProps) { const [open, setOpen] = useState(false); const currentYear = value ? value.getFullYear() : new Date().getFullYear(); - const [decadeStart, setDecadeStart] = useState(Math.floor(currentYear / 10) * 10); + const [decadeStart, setDecadeStart] = useState( + Math.floor(currentYear / 10) * 10, + ); const minYear = minDate ? minDate.getFullYear() : 1900; const maxYear = maxDate ? maxDate.getFullYear() : 2100; @@ -45,9 +47,9 @@ function YearPicker({ <button type="button" className={cn( - 'tw:border-input tw:flex tw:h-9 tw:w-full tw:items-center tw:justify-between tw:rounded-md tw:border tw:px-3 tw:py-2 tw:text-sm tw:shadow-xs tw:transition-[color,box-shadow] tw:outline-none', - !value && 'tw:text-muted-foreground', - className + "tw:border-input tw:flex tw:h-9 tw:w-full tw:items-center tw:justify-between tw:rounded-md tw:border tw:px-3 tw:py-2 tw:text-sm tw:shadow-xs tw:transition-[color,box-shadow] tw:outline-none", + !value && "tw:text-muted-foreground", + className, )} > {displayText} @@ -90,12 +92,12 @@ function YearPicker({ disabled={isDisabled} onClick={() => handleSelect(year)} className={cn( - 'tw:h-9 tw:rounded-md tw:text-sm tw:transition-colors', + "tw:h-9 tw:rounded-md tw:text-sm tw:transition-colors", isSelected - ? 'tw:bg-primary tw:text-primary-foreground tw:font-medium' - : 'tw:hover:bg-accent tw:hover:text-accent-foreground', - isOutOfDecade && !isSelected && 'tw:text-muted-foreground', - isDisabled && 'tw:opacity-30 tw:pointer-events-none' + ? "tw:bg-primary tw:text-primary-foreground tw:font-medium" + : "tw:hover:bg-accent tw:hover:text-accent-foreground", + isOutOfDecade && !isSelected && "tw:text-muted-foreground", + isDisabled && "tw:opacity-30 tw:pointer-events-none", )} > {year} diff --git a/website/src/data/supporters.ts b/website/src/data/supporters.ts index 3810b13bb..9299f3107 100644 --- a/website/src/data/supporters.ts +++ b/website/src/data/supporters.ts @@ -123,7 +123,7 @@ export const projects: SupportedProjectData[] = [ currency: "USD", awardDate: "2026-10", description: - "Modelling the revenue and distributional effects of a UK tax on AI inference, for the project \"Token taxes as a mechanism for reducing AI-driven power concentration\". The project is funded through Manifund by grantmaking.ai.", + 'Modelling the revenue and distributional effects of a UK tax on AI inference, for the project "Token taxes as a mechanism for reducing AI-driven power concentration". The project is funded through Manifund by grantmaking.ai.', supporterId: "manifund", }, { @@ -136,7 +136,8 @@ export const projects: SupportedProjectData[] = [ supporterId: "ses", }, { - title: "Advancing evidence-based policy with AI & tax-benefit microsimulation", + title: + "Advancing evidence-based policy with AI & tax-benefit microsimulation", projectUrl: "https://www.nuffieldfoundation.org/project/advancing-evidence-based-policy-with-ai-tax-benefit-microsimulation", amount: 300000, diff --git a/website/src/lib/betterstack.ts b/website/src/lib/betterstack.ts index 920056d3c..2f9088cb4 100644 --- a/website/src/lib/betterstack.ts +++ b/website/src/lib/betterstack.ts @@ -97,7 +97,10 @@ async function fetchAllIncidents( `/api/v3/incidents?monitor_id=${monitorId}&from=${from}&to=${to}&per_page=50`; while (path) { - const data: BetterStackIncidentResponse = await fetchBetterStack(path, token); + const data: BetterStackIncidentResponse = await fetchBetterStack( + path, + token, + ); if (data.data) { incidents.push(...data.data); } @@ -170,7 +173,10 @@ async function fetchMonitorData( token: string, ): Promise<MonitorData> { const [monitorRes, slaRes, incidents] = await Promise.all([ - fetchBetterStack<BetterStackMonitorResponse>(`/api/v2/monitors/${monitorId}`, token), + fetchBetterStack<BetterStackMonitorResponse>( + `/api/v2/monitors/${monitorId}`, + token, + ), fetchBetterStack<BetterStackSlaResponse>( `/api/v2/monitors/${monitorId}/sla?from=${from}&to=${to}`, token, @@ -207,7 +213,9 @@ export async function fetchStatusPageData( const { from, to, dates } = getDateRange(); const monitors = await Promise.all( - monitorIds.map((monitorId) => fetchMonitorData(monitorId, from, to, dates, token)), + monitorIds.map((monitorId) => + fetchMonitorData(monitorId, from, to, dates, token), + ), ); return { diff --git a/website/src/lib/utils.ts b/website/src/lib/utils.ts index 2819a830d..a5ef19350 100644 --- a/website/src/lib/utils.ts +++ b/website/src/lib/utils.ts @@ -1,5 +1,5 @@ -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)); diff --git a/website/src/styles/stylesheets/RichTextBlock.css b/website/src/styles/stylesheets/RichTextBlock.css index 6211fa9d7..074a8c356 100644 --- a/website/src/styles/stylesheets/RichTextBlock.css +++ b/website/src/styles/stylesheets/RichTextBlock.css @@ -6,7 +6,7 @@ Inter, -apple-system, BlinkMacSystemFont, - 'Segoe UI', + "Segoe UI", Roboto, sans-serif; font-size: 16px;