diff --git a/.changeset/config.json b/.changeset/config.json
index f2c804f..18a6f43 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -7,11 +7,7 @@
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
- "ignore": [
- "@tpmjs/config",
- "@tpmjs/storybook",
- "@tpmjs/web"
- ],
+ "ignore": ["@tpmjs/config", "@tpmjs/storybook", "@tpmjs/web"],
"privatePackages": {
"version": false,
"tag": false
diff --git a/.dependency-cruiser.js b/.dependency-cruiser.js
index 82ae22c..8f8540b 100644
--- a/.dependency-cruiser.js
+++ b/.dependency-cruiser.js
@@ -1,159 +1,159 @@
/** @type {import('dependency-cruiser').IConfiguration} */
export default {
- forbidden: [
- {
- name: "no-circular",
- severity: "error",
- comment:
- "This dependency is part of a circular relationship. You might want to revise " +
- "your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ",
- from: {},
- to: {
- circular: true,
- },
- },
- {
- name: "no-orphans",
- comment:
- "This is an orphan module - it's likely not used (anymore?). Either use it or " +
- "remove it. If it's logical this module is an orphan (i.e. it's a config file), " +
- "add an exception for it in your dependency-cruiser configuration. By default " +
- "this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " +
- "files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
- severity: "warn",
- from: {
- orphan: true,
- pathNot: [
- "(^|/)\\.[^/]+\\.(js|cjs|mjs|ts|json)$", // dot files
- "\\.d\\.ts$", // TypeScript declaration files
- "(^|/)tsconfig\\.json$", // tsconfig
- "(^|/)postcss\\.config\\.(js|cjs|mjs)$", // postcss config
- "(^|/)(babel|webpack|tailwind)\\.config\\.(js|cjs|mjs|ts|json)$", // other configs
- "/tokens\\.ts$", // token files
- ],
- },
- to: {},
- },
- {
- name: "no-deprecated-core",
- comment:
- "A module depends on a node core module that has been deprecated. Find an alternative - these are " +
- "bound to exist - node doesn't deprecate lightly.",
- severity: "warn",
- from: {},
- to: {
- dependencyTypes: ["core"],
- path: [
- "^(v8/tools/codemap)$",
- "^(v8/tools/consarray)$",
- "^(v8/tools/csvparser)$",
- "^(v8/tools/logreader)$",
- "^(v8/tools/profile_view)$",
- "^(v8/tools/profile)$",
- "^(v8/tools/SourceMap)$",
- "^(v8/tools/splaytree)$",
- "^(v8/tools/tickprocessor-driver)$",
- "^(v8/tools/tickprocessor)$",
- "^(node-inspect/lib/_inspect)$",
- "^(node-inspect/lib/internal/inspect_client)$",
- "^(node-inspect/lib/internal/inspect_repl)$",
- "^(async_hooks)$",
- "^(punycode)$",
- "^(domain)$",
- "^(constants)$",
- "^(sys)$",
- "^(_linklist)$",
- "^(_stream_wrap)$",
- ],
- },
- },
- {
- name: "not-to-deprecated",
- comment:
- "This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " +
- "version of that module, or find an alternative. Deprecated modules are a security risk.",
- severity: "warn",
- from: {},
- to: {
- dependencyTypes: ["deprecated"],
- },
- },
- {
- name: "no-non-package-json",
- severity: "error",
- comment:
- "This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " +
- "That's problematic as the package either (1) won't be available on live (2 - worse) will be " +
- "available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " +
- "in your package.json.",
- from: {},
- to: {
- dependencyTypes: ["npm-no-pkg", "npm-unknown"],
- },
- },
- {
- name: "not-to-unresolvable",
- comment:
- "This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " +
- "module: add it to your package.json. In all other cases you likely already know what to do.",
- severity: "error",
- from: {},
- to: {
- couldNotResolve: true,
- },
- },
- {
- name: "no-duplicate-dep-types",
- comment:
- "Likeley this module depends on an external ('npm') package that occurs more than once " +
- "in your package.json i.e. both as a devDependencies and in dependencies. This will cause " +
- "maintenance problems later on.",
- severity: "warn",
- from: {},
- to: {
- moreThanOneDependencyType: true,
- // as it's pretty common to have a type import be a type only import
- // _and_ (e.g.) a devDependency - don't consider type-only dependency
- // types for this rule
- dependencyTypesNot: ["type-only"],
- },
- },
+ forbidden: [
+ {
+ name: 'no-circular',
+ severity: 'error',
+ comment:
+ 'This dependency is part of a circular relationship. You might want to revise ' +
+ 'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ',
+ from: {},
+ to: {
+ circular: true,
+ },
+ },
+ {
+ name: 'no-orphans',
+ comment:
+ "This is an orphan module - it's likely not used (anymore?). Either use it or " +
+ "remove it. If it's logical this module is an orphan (i.e. it's a config file), " +
+ 'add an exception for it in your dependency-cruiser configuration. By default ' +
+ 'this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration ' +
+ 'files (.d.ts), tsconfig.json and some of the babel and webpack configs.',
+ severity: 'warn',
+ from: {
+ orphan: true,
+ pathNot: [
+ '(^|/)\\.[^/]+\\.(js|cjs|mjs|ts|json)$', // dot files
+ '\\.d\\.ts$', // TypeScript declaration files
+ '(^|/)tsconfig\\.json$', // tsconfig
+ '(^|/)postcss\\.config\\.(js|cjs|mjs)$', // postcss config
+ '(^|/)(babel|webpack|tailwind)\\.config\\.(js|cjs|mjs|ts|json)$', // other configs
+ '/tokens\\.ts$', // token files
+ ],
+ },
+ to: {},
+ },
+ {
+ name: 'no-deprecated-core',
+ comment:
+ 'A module depends on a node core module that has been deprecated. Find an alternative - these are ' +
+ "bound to exist - node doesn't deprecate lightly.",
+ severity: 'warn',
+ from: {},
+ to: {
+ dependencyTypes: ['core'],
+ path: [
+ '^(v8/tools/codemap)$',
+ '^(v8/tools/consarray)$',
+ '^(v8/tools/csvparser)$',
+ '^(v8/tools/logreader)$',
+ '^(v8/tools/profile_view)$',
+ '^(v8/tools/profile)$',
+ '^(v8/tools/SourceMap)$',
+ '^(v8/tools/splaytree)$',
+ '^(v8/tools/tickprocessor-driver)$',
+ '^(v8/tools/tickprocessor)$',
+ '^(node-inspect/lib/_inspect)$',
+ '^(node-inspect/lib/internal/inspect_client)$',
+ '^(node-inspect/lib/internal/inspect_repl)$',
+ '^(async_hooks)$',
+ '^(punycode)$',
+ '^(domain)$',
+ '^(constants)$',
+ '^(sys)$',
+ '^(_linklist)$',
+ '^(_stream_wrap)$',
+ ],
+ },
+ },
+ {
+ name: 'not-to-deprecated',
+ comment:
+ 'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' +
+ 'version of that module, or find an alternative. Deprecated modules are a security risk.',
+ severity: 'warn',
+ from: {},
+ to: {
+ dependencyTypes: ['deprecated'],
+ },
+ },
+ {
+ name: 'no-non-package-json',
+ severity: 'error',
+ comment:
+ "This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " +
+ "That's problematic as the package either (1) won't be available on live (2 - worse) will be " +
+ 'available on live with an non-guaranteed version. Fix it by adding the package to the dependencies ' +
+ 'in your package.json.',
+ from: {},
+ to: {
+ dependencyTypes: ['npm-no-pkg', 'npm-unknown'],
+ },
+ },
+ {
+ name: 'not-to-unresolvable',
+ comment:
+ "This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " +
+ 'module: add it to your package.json. In all other cases you likely already know what to do.',
+ severity: 'error',
+ from: {},
+ to: {
+ couldNotResolve: true,
+ },
+ },
+ {
+ name: 'no-duplicate-dep-types',
+ comment:
+ "Likeley this module depends on an external ('npm') package that occurs more than once " +
+ 'in your package.json i.e. both as a devDependencies and in dependencies. This will cause ' +
+ 'maintenance problems later on.',
+ severity: 'warn',
+ from: {},
+ to: {
+ moreThanOneDependencyType: true,
+ // as it's pretty common to have a type import be a type only import
+ // _and_ (e.g.) a devDependency - don't consider type-only dependency
+ // types for this rule
+ dependencyTypesNot: ['type-only'],
+ },
+ },
- /* Custom monorepo rules - keep it simple */
- {
- name: "no-package-to-app-imports",
- comment: "Packages cannot import from apps - keeps packages reusable",
- severity: "error",
- from: {
- path: "^packages/",
- },
- to: {
- path: "^apps/",
- },
- },
- ],
- options: {
- doNotFollow: {
- path: ["node_modules", "\\.next", "dist", "\\.turbo", "storybook-static"],
- },
- tsPreCompilationDeps: true,
- tsConfig: {
- fileName: "./tsconfig.json",
- },
- enhancedResolveOptions: {
- exportsFields: ["exports"],
- conditionNames: ["import", "require", "node", "default"],
- },
- reporterOptions: {
- dot: {
- collapsePattern: "node_modules/[^/]+",
- },
- archi: {
- collapsePattern: "^(packages|apps)/[^/]+|node_modules/[^/]+",
- },
- text: {
- highlightFocused: true,
- },
- },
- },
+ /* Custom monorepo rules - keep it simple */
+ {
+ name: 'no-package-to-app-imports',
+ comment: 'Packages cannot import from apps - keeps packages reusable',
+ severity: 'error',
+ from: {
+ path: '^packages/',
+ },
+ to: {
+ path: '^apps/',
+ },
+ },
+ ],
+ options: {
+ doNotFollow: {
+ path: ['node_modules', '\\.next', 'dist', '\\.turbo', 'storybook-static'],
+ },
+ tsPreCompilationDeps: true,
+ tsConfig: {
+ fileName: './tsconfig.json',
+ },
+ enhancedResolveOptions: {
+ exportsFields: ['exports'],
+ conditionNames: ['import', 'require', 'node', 'default'],
+ },
+ reporterOptions: {
+ dot: {
+ collapsePattern: 'node_modules/[^/]+',
+ },
+ archi: {
+ collapsePattern: '^(packages|apps)/[^/]+|node_modules/[^/]+',
+ },
+ text: {
+ highlightFocused: true,
+ },
+ },
+ },
};
diff --git a/apps/web/eslint.config.mjs b/apps/web/eslint.config.mjs
index c622a53..f2ac8a1 100644
--- a/apps/web/eslint.config.mjs
+++ b/apps/web/eslint.config.mjs
@@ -9,8 +9,8 @@ export default [
'*.config.js',
'*.config.ts',
'next-env.d.ts',
- 'eslint.config.mjs'
- ]
+ 'eslint.config.mjs',
+ ],
},
...reactConfig,
];
diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts
index 9edff1c..1511519 100644
--- a/apps/web/next-env.d.ts
+++ b/apps/web/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/types/routes.d.ts";
+import './.next/types/routes.d.ts';
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/apps/web/package.json b/apps/web/package.json
index 0979353..1143268 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -1,38 +1,38 @@
{
- "name": "@tpmjs/web",
- "version": "0.0.0",
- "private": true,
- "scripts": {
- "dev": "next dev",
- "build": "next build",
- "start": "next start",
- "lint": "eslint .",
- "type-check": "tsc --noEmit",
- "clean": "rm -rf .next .turbo"
- },
- "dependencies": {
- "@tpmjs/env": "workspace:*",
- "@tpmjs/types": "workspace:*",
- "@tpmjs/ui": "workspace:*",
- "@tpmjs/utils": "workspace:*",
- "next": "^16.0.4",
- "next-themes": "^0.4.6",
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
- "zod": "^3.24.1"
- },
- "devDependencies": {
- "@tpmjs/eslint-config": "workspace:*",
- "@tpmjs/tailwind-config": "workspace:*",
- "@tpmjs/tsconfig": "workspace:*",
- "@types/node": "^22.10.2",
- "@types/react": "^19.0.2",
- "@types/react-dom": "^19.0.2",
- "autoprefixer": "^10.4.20",
- "eslint": "^9.39.1",
- "eslint-config-next": "^16.0.4",
- "postcss": "^8.5.1",
- "tailwindcss": "^3.4.17",
- "typescript": "^5.9.3"
- }
+ "name": "@tpmjs/web",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "eslint .",
+ "type-check": "tsc --noEmit",
+ "clean": "rm -rf .next .turbo"
+ },
+ "dependencies": {
+ "@tpmjs/env": "workspace:*",
+ "@tpmjs/types": "workspace:*",
+ "@tpmjs/ui": "workspace:*",
+ "@tpmjs/utils": "workspace:*",
+ "next": "^16.0.4",
+ "next-themes": "^0.4.6",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "zod": "^3.24.1"
+ },
+ "devDependencies": {
+ "@tpmjs/eslint-config": "workspace:*",
+ "@tpmjs/tailwind-config": "workspace:*",
+ "@tpmjs/tsconfig": "workspace:*",
+ "@types/node": "^22.10.2",
+ "@types/react": "^19.0.2",
+ "@types/react-dom": "^19.0.2",
+ "autoprefixer": "^10.4.20",
+ "eslint": "^9.39.1",
+ "eslint-config-next": "^16.0.4",
+ "postcss": "^8.5.1",
+ "tailwindcss": "^3.4.17",
+ "typescript": "^5.9.3"
+ }
}
diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css
index 2098b3a..efdf0ed 100644
--- a/apps/web/src/app/globals.css
+++ b/apps/web/src/app/globals.css
@@ -6,31 +6,31 @@
/* Light mode (default) */
:root {
/* Backgrounds & Surfaces */
- --background: 0 0% 100%; /* Pure white */
- --surface: 210 20% 98%; /* Off-white */
- --surface-elevated: 0 0% 100%; /* White (elevated) */
- --surface-overlay: 0 0% 98%; /* Light gray */
+ --background: 0 0% 100%; /* Pure white */
+ --surface: 210 20% 98%; /* Off-white */
+ --surface-elevated: 0 0% 100%; /* White (elevated) */
+ --surface-overlay: 0 0% 98%; /* Light gray */
/* Foreground (Text) */
- --foreground: 222 47% 11%; /* Almost black */
+ --foreground: 222 47% 11%; /* Almost black */
--foreground-secondary: 215 16% 47%; /* Medium gray */
- --foreground-tertiary: 215 16% 65%; /* Light gray */
- --foreground-muted: 215 16% 75%; /* Very light gray */
+ --foreground-tertiary: 215 16% 65%; /* Light gray */
+ --foreground-muted: 215 16% 75%; /* Very light gray */
/* Borders */
- --border: 214 32% 91%; /* Light gray */
- --border-strong: 214 32% 80%; /* Medium gray */
- --border-subtle: 214 20% 95%; /* Very light gray */
+ --border: 214 32% 91%; /* Light gray */
+ --border-strong: 214 32% 80%; /* Medium gray */
+ --border-subtle: 214 20% 95%; /* Very light gray */
/* Interactive States */
- --primary: 222 47% 11%; /* Dark for light mode */
- --primary-foreground: 210 40% 98%; /* Light text */
- --secondary: 210 40% 96%; /* Light secondary */
+ --primary: 222 47% 11%; /* Dark for light mode */
+ --primary-foreground: 210 40% 98%; /* Light text */
+ --secondary: 210 40% 96%; /* Light secondary */
--secondary-foreground: 222 47% 11%; /* Dark text */
- --accent: 210 40% 96%; /* Accent bg */
- --accent-foreground: 222 47% 11%; /* Accent text */
- --muted: 210 40% 96%; /* Muted bg */
- --muted-foreground: 215 16% 47%; /* Muted text */
+ --accent: 210 40% 96%; /* Accent bg */
+ --accent-foreground: 222 47% 11%; /* Accent text */
+ --muted: 210 40% 96%; /* Muted bg */
+ --muted-foreground: 215 16% 47%; /* Muted text */
/* Status Colors */
--success: 142 71% 45%;
@@ -53,7 +53,7 @@
/* Grid/Blueprint Pattern */
--grid-color: 214 32% 95%;
- --grid-size: 24px; /* Grid cell size */
+ --grid-size: 24px; /* Grid cell size */
/* Card */
--card: 0 0% 100%;
@@ -67,15 +67,15 @@
--radius: var(--radius-md);
/* Spacing - Editorial scale for airy layouts */
- --spacing-section: 6rem; /* 96px - Between major sections */
- --spacing-component: 3rem; /* 48px - Between components */
- --spacing-element: 1.5rem; /* 24px - Between related elements */
- --spacing-tight: 0.75rem; /* 12px - Tight groupings */
- --spacing-comfortable: 2rem; /* 32px - Comfortable breathing room */
+ --spacing-section: 6rem; /* 96px - Between major sections */
+ --spacing-component: 3rem; /* 48px - Between components */
+ --spacing-element: 1.5rem; /* 24px - Between related elements */
+ --spacing-tight: 0.75rem; /* 12px - Tight groupings */
+ --spacing-comfortable: 2rem; /* 32px - Comfortable breathing room */
/* Typography */
--font-sans: var(--font-sans), -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
- --font-mono: var(--font-mono), 'SF Mono', Consolas, monospace;
+ --font-mono: var(--font-mono), "SF Mono", Consolas, monospace;
/* Editorial Line Heights - Airy, readable */
--leading-tight: 1.25;
@@ -83,7 +83,7 @@
--leading-normal: 1.5;
--leading-relaxed: 1.625;
--leading-loose: 1.75;
- --leading-editorial: 1.8; /* Extra generous for body copy */
+ --leading-editorial: 1.8; /* Extra generous for body copy */
/* Letter Spacing - Technical precision */
--tracking-tighter: -0.05em;
@@ -97,53 +97,53 @@
/* Dark mode (opt-in) - Vercel/Cursor/Perplexity aesthetic */
.dark {
/* Backgrounds & Surfaces */
- --background: 210 10% 5%; /* #0d0d0f - Almost black */
- --surface: 210 10% 8%; /* #14141a - Slightly lighter */
- --surface-elevated: 210 10% 12%; /* #1c1c24 - Cards, modals */
- --surface-overlay: 210 10% 16%; /* #25252f - Overlays, popovers */
+ --background: 210 10% 5%; /* #0d0d0f - Almost black */
+ --surface: 210 10% 8%; /* #14141a - Slightly lighter */
+ --surface-elevated: 210 10% 12%; /* #1c1c24 - Cards, modals */
+ --surface-overlay: 210 10% 16%; /* #25252f - Overlays, popovers */
/* Foreground (Text) */
- --foreground: 210 10% 90%; /* #e3e3e8 - Primary text */
+ --foreground: 210 10% 90%; /* #e3e3e8 - Primary text */
--foreground-secondary: 210 8% 60%; /* #949499 - Secondary text */
- --foreground-tertiary: 210 6% 40%; /* #64646a - Tertiary text */
- --foreground-muted: 210 6% 30%; /* #4a4a50 - Muted text */
+ --foreground-tertiary: 210 6% 40%; /* #64646a - Tertiary text */
+ --foreground-muted: 210 6% 30%; /* #4a4a50 - Muted text */
/* Borders (1px, subtle) */
- --border: 210 10% 20%; /* #30303a - Default border */
- --border-strong: 210 12% 30%; /* #494954 - Emphasized border */
- --border-subtle: 210 8% 15%; /* #22222c - Subtle border */
+ --border: 210 10% 20%; /* #30303a - Default border */
+ --border-strong: 210 12% 30%; /* #494954 - Emphasized border */
+ --border-subtle: 210 8% 15%; /* #22222c - Subtle border */
/* Interactive States */
- --primary: 210 10% 98%; /* White-ish for dark mode */
- --primary-foreground: 210 10% 5%; /* Dark text on light bg */
- --secondary: 210 10% 15%; /* Dark secondary */
+ --primary: 210 10% 98%; /* White-ish for dark mode */
+ --primary-foreground: 210 10% 5%; /* Dark text on light bg */
+ --secondary: 210 10% 15%; /* Dark secondary */
--secondary-foreground: 210 10% 90%; /* Light text on dark bg */
- --accent: 210 10% 18%; /* Accent background */
- --accent-foreground: 210 10% 95%; /* Accent text */
- --muted: 210 10% 12%; /* Muted background */
- --muted-foreground: 210 8% 60%; /* Muted text */
+ --accent: 210 10% 18%; /* Accent background */
+ --accent-foreground: 210 10% 95%; /* Accent text */
+ --muted: 210 10% 12%; /* Muted background */
+ --muted-foreground: 210 8% 60%; /* Muted text */
/* Status Colors (desaturated for dark mode) */
- --success: 142 71% 45%; /* #10b981 - Green */
- --success-foreground: 142 76% 95%; /* Light green text */
- --error: 0 72% 51%; /* #ef4444 - Red */
- --error-foreground: 0 86% 97%; /* Light red text */
- --warning: 38 92% 50%; /* #f59e0b - Amber */
- --warning-foreground: 48 96% 89%; /* Light amber text */
- --info: 217 91% 60%; /* #3b82f6 - Blue */
- --info-foreground: 214 95% 93%; /* Light blue text */
+ --success: 142 71% 45%; /* #10b981 - Green */
+ --success-foreground: 142 76% 95%; /* Light green text */
+ --error: 0 72% 51%; /* #ef4444 - Red */
+ --error-foreground: 0 86% 97%; /* Light red text */
+ --warning: 38 92% 50%; /* #f59e0b - Amber */
+ --warning-foreground: 48 96% 89%; /* Light amber text */
+ --info: 217 91% 60%; /* #3b82f6 - Blue */
+ --info-foreground: 214 95% 93%; /* Light blue text */
/* Destructive (legacy support) */
--destructive: 0 72% 51%;
--destructive-foreground: 0 86% 97%;
/* Form Elements */
- --input: 210 10% 20%; /* Input border */
- --ring: 210 10% 90%; /* Focus ring */
- --ring-offset: 210 10% 5%; /* Focus ring offset */
+ --input: 210 10% 20%; /* Input border */
+ --ring: 210 10% 90%; /* Focus ring */
+ --ring-offset: 210 10% 5%; /* Focus ring offset */
/* Grid/Blueprint Pattern */
- --grid-color: 210 10% 15%; /* Grid line color */
+ --grid-color: 210 10% 15%; /* Grid line color */
/* Card */
--card: 210 10% 8%;
@@ -158,39 +158,34 @@
body {
@apply bg-background text-foreground;
font-family: var(--font-sans);
- font-feature-settings: 'rlig' 1, 'calt' 1;
+ font-feature-settings: "rlig" 1, "calt" 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Grid/Blueprint background patterns */
.grid-background {
- background-image:
- linear-gradient(hsl(var(--grid-color)) 1px, transparent 1px),
+ background-image: linear-gradient(hsl(var(--grid-color)) 1px, transparent 1px),
linear-gradient(90deg, hsl(var(--grid-color)) 1px, transparent 1px);
background-size: var(--grid-size) var(--grid-size);
background-position: -1px -1px;
}
.dotted-grid-background {
- background-image:
- radial-gradient(circle, hsl(var(--grid-color)) 1px, transparent 1px);
+ background-image: radial-gradient(circle, hsl(var(--grid-color)) 1px, transparent 1px);
background-size: var(--grid-size) var(--grid-size);
}
.blueprint-background {
background-color: hsl(var(--background));
- background-image:
- /* Major grid lines (4x size) */
- linear-gradient(hsl(var(--grid-color)) 1px, transparent 1px),
+ /* Major grid lines (4x size) */
+ background-image: linear-gradient(hsl(var(--grid-color)) 1px, transparent 1px),
linear-gradient(90deg, hsl(var(--grid-color)) 1px, transparent 1px),
/* Minor grid lines (1x size) */
linear-gradient(hsl(var(--grid-color) / 0.3) 1px, transparent 1px),
linear-gradient(90deg, hsl(var(--grid-color) / 0.3) 1px, transparent 1px);
- background-size:
- calc(var(--grid-size) * 4) calc(var(--grid-size) * 4),
- calc(var(--grid-size) * 4) calc(var(--grid-size) * 4),
- var(--grid-size) var(--grid-size),
+ background-size: calc(var(--grid-size) * 4) calc(var(--grid-size) * 4),
+ calc(var(--grid-size) * 4) calc(var(--grid-size) * 4), var(--grid-size) var(--grid-size),
var(--grid-size) var(--grid-size);
}
@@ -201,8 +196,13 @@
}
@keyframes grid-pulse {
- 0%, 100% { opacity: 0.4; }
- 50% { opacity: 0.6; }
+ 0%,
+ 100% {
+ opacity: 0.4;
+ }
+ 50% {
+ opacity: 0.6;
+ }
}
}
@@ -250,15 +250,11 @@
/* Shadow utilities - Blueprint aesthetic */
.shadow-blueprint {
- box-shadow:
- 0 1px 2px 0 rgb(0 0 0 / 0.03),
- 0 0 0 1px rgb(0 0 0 / 0.02);
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.03), 0 0 0 1px rgb(0 0 0 / 0.02);
}
.shadow-blueprint-hover {
- box-shadow:
- 0 2px 4px 0 rgb(0 0 0 / 0.05),
- 0 0 0 1px rgb(0 0 0 / 0.03);
+ box-shadow: 0 2px 4px 0 rgb(0 0 0 / 0.05), 0 0 0 1px rgb(0 0 0 / 0.03);
}
/* Transition presets */
diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx
index 0cf440a..e54d1f4 100644
--- a/apps/web/src/app/layout.tsx
+++ b/apps/web/src/app/layout.tsx
@@ -1,48 +1,48 @@
-import type { Metadata } from "next";
-import { Space_Grotesk, Space_Mono } from "next/font/google";
-import { ThemeProvider } from "../components/providers/ThemeProvider";
-import "./globals.css";
+import type { Metadata } from 'next';
+import { Space_Grotesk, Space_Mono } from 'next/font/google';
+import { ThemeProvider } from '../components/providers/ThemeProvider';
+import './globals.css';
const spaceGrotesk = Space_Grotesk({
- subsets: ["latin"],
- variable: "--font-sans",
- display: "swap",
+ subsets: ['latin'],
+ variable: '--font-sans',
+ display: 'swap',
});
const spaceMono = Space_Mono({
- subsets: ["latin"],
- weight: ["400", "700"],
- variable: "--font-mono",
- display: "swap",
+ subsets: ['latin'],
+ weight: ['400', '700'],
+ variable: '--font-mono',
+ display: 'swap',
});
export const metadata: Metadata = {
- title: "TPMJS - Tool Package Manager for AI Agents",
- description:
- "The registry for AI tools. Discover, share, and integrate tools that give your agents superpowers.",
+ title: 'TPMJS - Tool Package Manager for AI Agents',
+ description:
+ 'The registry for AI tools. Discover, share, and integrate tools that give your agents superpowers.',
};
export default function RootLayout({
- children,
+ children,
}: {
- children: React.ReactNode;
+ children: React.ReactNode;
}): React.ReactElement {
- return (
-
-
-
- {children}
-
-
-
- );
+ return (
+
+
+
+ {children}
+
+
+
+ );
}
diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx
index 4d77380..d451e07 100644
--- a/apps/web/src/app/page.tsx
+++ b/apps/web/src/app/page.tsx
@@ -1,230 +1,176 @@
-import { Badge } from "@tpmjs/ui/Badge/Badge";
-import { Button } from "@tpmjs/ui/Button/Button";
-import { Card, CardContent } from "@tpmjs/ui/Card/Card";
-import { Container } from "@tpmjs/ui/Container/Container";
-import { Header } from "@tpmjs/ui/Header/Header";
-import { Icon } from "@tpmjs/ui/Icon/Icon";
-import { Input } from "@tpmjs/ui/Input/Input";
-import Link from "next/link";
-import { ThemeToggle } from "../components/ThemeToggle";
-import { categories, featuredTools, statistics } from "../data/homePageData";
+import { Badge } from '@tpmjs/ui/Badge/Badge';
+import { Button } from '@tpmjs/ui/Button/Button';
+import { Card, CardContent } from '@tpmjs/ui/Card/Card';
+import { Container } from '@tpmjs/ui/Container/Container';
+import { Header } from '@tpmjs/ui/Header/Header';
+import { Icon } from '@tpmjs/ui/Icon/Icon';
+import { Input } from '@tpmjs/ui/Input/Input';
+import Link from 'next/link';
+import { ThemeToggle } from '../components/ThemeToggle';
+import { categories, featuredTools, statistics } from '../data/homePageData';
export default function HomePage(): React.ReactElement {
- return (
-
+ );
}
diff --git a/apps/web/src/app/playground/page.tsx b/apps/web/src/app/playground/page.tsx
index 5ab8b51..39aceb6 100644
--- a/apps/web/src/app/playground/page.tsx
+++ b/apps/web/src/app/playground/page.tsx
@@ -1,433 +1,383 @@
-"use client";
+'use client';
-import { Badge } from "@tpmjs/ui/Badge/Badge";
-import { Button } from "@tpmjs/ui/Button/Button";
+import { Badge } from '@tpmjs/ui/Badge/Badge';
+import { Button } from '@tpmjs/ui/Button/Button';
import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@tpmjs/ui/Card/Card";
-import { CodeBlock } from "@tpmjs/ui/CodeBlock/CodeBlock";
-import { Container } from "@tpmjs/ui/Container/Container";
-import { Header } from "@tpmjs/ui/Header/Header";
-import { Icon } from "@tpmjs/ui/Icon/Icon";
-import { Input } from "@tpmjs/ui/Input/Input";
-import { Label } from "@tpmjs/ui/Label/Label";
-import { ProgressBar } from "@tpmjs/ui/ProgressBar/ProgressBar";
-import { Tabs } from "@tpmjs/ui/Tabs/Tabs";
-import Link from "next/link";
-import { useState } from "react";
-import { ThemeToggle } from "../../components/ThemeToggle";
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@tpmjs/ui/Card/Card';
+import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
+import { Container } from '@tpmjs/ui/Container/Container';
+import { Header } from '@tpmjs/ui/Header/Header';
+import { Icon } from '@tpmjs/ui/Icon/Icon';
+import { Input } from '@tpmjs/ui/Input/Input';
+import { Label } from '@tpmjs/ui/Label/Label';
+import { ProgressBar } from '@tpmjs/ui/ProgressBar/ProgressBar';
+import { Tabs } from '@tpmjs/ui/Tabs/Tabs';
+import Link from 'next/link';
+import { useState } from 'react';
+import { ThemeToggle } from '../../components/ThemeToggle';
export default function PlaygroundPage() {
- const [activeTab, setActiveTab] = useState("all");
- const [progress, setProgress] = useState(65);
+ const [activeTab, setActiveTab] = useState('all');
+ const [progress, setProgress] = useState(65);
- return (
-
- {/* Header */}
-
- TPMJS Playground
-
- }
- size="md"
- sticky={true}
- actions={
-
-
-
-
-
-
- }
- />
+ return (
+
+ {/* Header */}
+
+ TPMJS Playground
+
+ }
+ size="md"
+ sticky={true}
+ actions={
+
+
+
+
+
+
+ }
+ />
-
-
-
-
- {/* Page Title */}
-
-
Component Playground
-
- Explore and test all TPMJS UI components in one place
-
-
+
+
+
+
+ {/* Page Title */}
+
+
Component Playground
+
+ Explore and test all TPMJS UI components in one place
+
+
- {/* Buttons Section */}
-
-
- Buttons
-
-
- {/* Button Variants */}
-
-
Variants
-
-
-
-
-
-
-
-
-
+ {/* Buttons Section */}
+
+ Buttons
+
+ {/* Button Variants */}
+
+
Variants
+
+
+
+
+
+
+
+
+
- {/* Button Sizes */}
-
-
Sizes
-
-
-
-
-
-
+ {/* Button Sizes */}
+
+
Sizes
+
+
+
+
+
+
- {/* Button States */}
-
-
States
-
-
-
-
-
-
-
-
+ {/* Button States */}
+
+
States
+
+
+
+
+
+
+
+
- {/* Badges Section */}
-
-
- Badges
-
-
- {/* Badge Variants */}
-
-
Variants
-
- Default
- Secondary
- Error
- Outline
- Success
- Warning
- Info
-
-
+ {/* Badges Section */}
+
+ Badges
+
+ {/* Badge Variants */}
+
+
Variants
+
+ Default
+ Secondary
+ Error
+ Outline
+ Success
+ Warning
+ Info
+
+
- {/* Badge Sizes */}
-
-
Sizes
-
- Small
- Medium
- Large
-
-
-
-
+ {/* Badge Sizes */}
+
+
Sizes
+
+ Small
+ Medium
+ Large
+
+
+
+
- {/* Icons Section */}
-
-
- Icons
-
-
- {/* Icon Sizes */}
-
-
Sizes
-
-
-
- Small
-
-
-
- Medium
-
-
-
- Large
-
-
-
+ {/* Icons Section */}
+
+ Icons
+
+ {/* Icon Sizes */}
+
+
Sizes
+
+
+
+ Small
+
+
+
+ Medium
+
+
+
+ Large
+
+
+
- {/* Icon Gallery */}
-
-
Available Icons
-
- {(
- [
- "check",
- "x",
- "chevronDown",
- "copy",
- "externalLink",
- "github",
- "sun",
- "moon",
- ] as const
- ).map((icon) => (
-
-
- {icon}
-
- ))}
-
-
-
-
+ {/* Icon Gallery */}
+
+
Available Icons
+
+ {(
+ [
+ 'check',
+ 'x',
+ 'chevronDown',
+ 'copy',
+ 'externalLink',
+ 'github',
+ 'sun',
+ 'moon',
+ ] as const
+ ).map((icon) => (
+
+
+ {icon}
+
+ ))}
+
+
+
+
- {/* Cards Section */}
-
-
- Cards
-
-
- {/* Card Variants */}
-
-
Variants
-
-
-
- Default Card
-
- This is the default card style
-
-
-
-
- Cards are flexible containers for content.
-
-
-
-
-
-
+ {/* Cards Section */}
+
+ Cards
+
+ {/* Card Variants */}
+
+
Variants
+
+
+
+ Default Card
+ This is the default card style
+
+
+ Cards are flexible containers for content.
+
+
+
+
+
-
-
- Elevated Card
-
- This card has a shadow elevation
-
-
-
-
- Perfect for highlighting important content.
-
-
-
-
-
-
+
+
+ Elevated Card
+ This card has a shadow elevation
+
+
+ Perfect for highlighting important content.
+
+
+
+
+
-
-
- Outline Card
-
- This card has an outline border
-
-
-
-
- Great for subtle content separation.
-
-
-
-
-
-
-
-
-
-
+
+
+ Outline Card
+ This card has an outline border
+
+
+ Great for subtle content separation.
+
+
+
+
+
+
+
+
+
- {/* Inputs Section */}
-
-
- Inputs
-
-
- {/* Input Sizes */}
-
+ {/* Inputs Section */}
+
+ Inputs
+
+ {/* Input Sizes */}
+
- {/* Input States */}
-
-
-
+ {/* Input States */}
+
+
+
- {/* Progress Bars Section */}
-
-
- Progress Bars
-
-
- {/* Progress Bar Variants */}
-
-
Variants
-
-
-
- Primary
- {progress}%
-
-
-
-
-
-
-
-
+ {/* Progress Bars Section */}
+
+ Progress Bars
+
+ {/* Progress Bar Variants */}
+
+
Variants
+
+
+
+ Primary
+ {progress}%
+
+
+
+
+
+
+
+
- {/* Interactive Progress */}
-
-
Interactive
-
-
-
-
-
- Adjust Progress
-
-
- {progress}%
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {/* Interactive Progress */}
+
+
Interactive
+
+
+
+
+ Adjust Progress
+ {progress}%
+
+
+
+
+
+
+
+
+
+
+
+
+
- {/* Tabs Section */}
-
-
- Tabs
-
-
-
-
Interactive Tabs
-
-
-
-
-
- Active tab: {activeTab}
-
-
-
-
-
-
-
+ {/* Tabs Section */}
+
+ Tabs
+
+
+
Interactive Tabs
+
+
+
+
+
+ Active tab: {activeTab}
+
+
+
+
+
+
+
- {/* Code Block Section */}
-
-
- Code Block
-
-
-
-
Example Code
-
+ Code Block
+
+
+
Example Code
+
);
}`}
- />
-
+ />
+
-
-
JSON Example
-
+ JSON Example
+
-
-
-
+ />
+
+
+
- {/* Containers Section */}
-
-
- Containers
-
-
-
-
Container Sizes
-
-
-
-
- size="sm"
-
-
-
-
-
-
- size="md"
-
-
-
-
-
-
- size="lg"
-
-
-
-
-
-
- size="xl"
-
-
-
-
-
-
-
+ {/* Containers Section */}
+
+ Containers
+
+
+
Container Sizes
+
+
+
+
+ size="sm"
+
+
+
+
+
+
+ size="md"
+
+
+
+
+
+
+ size="lg"
+
+
+
+
+
+
+ size="xl"
+
+
+
+
+
+
+
- {/* Labels Section */}
-
-
-
-
+ {/* Labels Section */}
+
+
+
+
- {/* Footer */}
-
-
- );
+ {/* Footer */}
+
+
+ );
}
diff --git a/apps/web/src/app/tool/tool-search/page.tsx b/apps/web/src/app/tool/tool-search/page.tsx
index 262143f..287848f 100644
--- a/apps/web/src/app/tool/tool-search/page.tsx
+++ b/apps/web/src/app/tool/tool-search/page.tsx
@@ -1,24 +1,24 @@
-"use client";
+'use client';
-import { Badge } from "@tpmjs/ui/Badge/Badge";
-import { Button } from "@tpmjs/ui/Button/Button";
+import { Badge } from '@tpmjs/ui/Badge/Badge';
+import { Button } from '@tpmjs/ui/Button/Button';
import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@tpmjs/ui/Card/Card";
-import { CodeBlock } from "@tpmjs/ui/CodeBlock/CodeBlock";
-import { Container } from "@tpmjs/ui/Container/Container";
-import { Header } from "@tpmjs/ui/Header/Header";
-import { Icon } from "@tpmjs/ui/Icon/Icon";
-import { Input } from "@tpmjs/ui/Input/Input";
-import { ProgressBar } from "@tpmjs/ui/ProgressBar/ProgressBar";
-import { Tabs } from "@tpmjs/ui/Tabs/Tabs";
-import { createElement, useState } from "react";
-import { getFeaturedTools, mockTools } from "../../../data/toolData";
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@tpmjs/ui/Card/Card';
+import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
+import { Container } from '@tpmjs/ui/Container/Container';
+import { Header } from '@tpmjs/ui/Header/Header';
+import { Icon } from '@tpmjs/ui/Icon/Icon';
+import { Input } from '@tpmjs/ui/Input/Input';
+import { ProgressBar } from '@tpmjs/ui/ProgressBar/ProgressBar';
+import { Tabs } from '@tpmjs/ui/Tabs/Tabs';
+import { createElement, useState } from 'react';
+import { getFeaturedTools, mockTools } from '../../../data/toolData';
/**
* Tool Registry Search Page
@@ -27,278 +27,228 @@ import { getFeaturedTools, mockTools } from "../../../data/toolData";
* Built with .ts-only React using createElement.
*/
export default function ToolSearchPage(): React.ReactElement {
- const [activeTab, setActiveTab] = useState("all");
- const [searchQuery, setSearchQuery] = useState("");
+ const [activeTab, setActiveTab] = useState('all');
+ const [searchQuery, setSearchQuery] = useState('');
- // Get tools based on active tab
- const displayedTools =
- activeTab === "featured" ? getFeaturedTools() : mockTools;
+ // Get tools based on active tab
+ const displayedTools = activeTab === 'featured' ? getFeaturedTools() : mockTools;
- // Filter by search query
- const filteredTools = searchQuery
- ? displayedTools.filter(
- (tool) =>
- tool.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
- tool.description.toLowerCase().includes(searchQuery.toLowerCase()),
- )
- : displayedTools;
+ // Filter by search query
+ const filteredTools = searchQuery
+ ? displayedTools.filter(
+ (tool) =>
+ tool.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ tool.description.toLowerCase().includes(searchQuery.toLowerCase())
+ )
+ : displayedTools;
- return createElement("div", { className: "min-h-screen bg-background" }, [
- // Header
- createElement(Header, {
- key: "header",
- title: createElement("div", { className: "flex items-center gap-2" }, [
- createElement(
- "span",
- { key: "title", className: "text-2xl font-bold" },
- "TPMJS",
- ),
- createElement(
- Badge,
- { key: "badge", variant: "outline", size: "sm" },
- "Beta",
- ),
- ]),
- actions: createElement("div", { className: "flex items-center gap-3" }, [
- createElement(
- Button,
- { key: "docs", variant: "ghost", size: "sm" },
- "Docs",
- ),
- createElement(
- "a",
- {
- key: "github",
- href: "https://github.com/tpmjs/tpmjs",
- target: "_blank",
- rel: "noopener noreferrer",
- className: "text-foreground-secondary hover:text-foreground transition-colors",
- },
- createElement(Icon, { icon: "github", size: "md" }),
- ),
- createElement(
- Button,
- { key: "publish", variant: "default", size: "sm" },
- "Publish Tool",
- ),
- ]),
- sticky: true,
- size: "md",
- }),
+ return createElement('div', { className: 'min-h-screen bg-background' }, [
+ // Header
+ createElement(Header, {
+ key: 'header',
+ title: createElement('div', { className: 'flex items-center gap-2' }, [
+ createElement('span', { key: 'title', className: 'text-2xl font-bold' }, 'TPMJS'),
+ createElement(Badge, { key: 'badge', variant: 'outline', size: 'sm' }, 'Beta'),
+ ]),
+ actions: createElement('div', { className: 'flex items-center gap-3' }, [
+ createElement(Button, { key: 'docs', variant: 'ghost', size: 'sm' }, 'Docs'),
+ createElement(
+ 'a',
+ {
+ key: 'github',
+ href: 'https://github.com/tpmjs/tpmjs',
+ target: '_blank',
+ rel: 'noopener noreferrer',
+ className: 'text-foreground-secondary hover:text-foreground transition-colors',
+ },
+ createElement(Icon, { icon: 'github', size: 'md' })
+ ),
+ createElement(Button, { key: 'publish', variant: 'default', size: 'sm' }, 'Publish Tool'),
+ ]),
+ sticky: true,
+ size: 'md',
+ }),
- // Main content
- createElement(
- Container,
- { key: "container", size: "xl", padding: "md", className: "py-8" },
- [
- // Page header
- createElement(
- "div",
- { key: "page-header", className: "space-y-4 mb-8" },
- [
- createElement(
- "h1",
- { key: "title", className: "text-4xl font-bold text-foreground" },
- "Tool Registry",
- ),
- createElement(
- "p",
- { key: "description", className: "text-lg text-foreground-secondary" },
- "Discover, share, and integrate tools that give your AI agents superpowers.",
- ),
- ],
- ),
+ // Main content
+ createElement(Container, { key: 'container', size: 'xl', padding: 'md', className: 'py-8' }, [
+ // Page header
+ createElement('div', { key: 'page-header', className: 'space-y-4 mb-8' }, [
+ createElement(
+ 'h1',
+ { key: 'title', className: 'text-4xl font-bold text-foreground' },
+ 'Tool Registry'
+ ),
+ createElement(
+ 'p',
+ { key: 'description', className: 'text-lg text-foreground-secondary' },
+ 'Discover, share, and integrate tools that give your AI agents superpowers.'
+ ),
+ ]),
- // Search input
- createElement(Input, {
- key: "search",
- placeholder: "Search tools...",
- value: searchQuery,
- onChange: (e) => setSearchQuery(e.target.value),
- className: "mb-6",
- }),
+ // Search input
+ createElement(Input, {
+ key: 'search',
+ placeholder: 'Search tools...',
+ value: searchQuery,
+ onChange: (e) => setSearchQuery(e.target.value),
+ className: 'mb-6',
+ }),
- // Tabs
- createElement(Tabs, {
- key: "tabs",
- tabs: [
- { id: "all", label: "All Tools", count: mockTools.length },
- {
- id: "featured",
- label: "Featured",
- count: getFeaturedTools().length,
- },
- ],
- activeTab,
- onTabChange: setActiveTab,
- size: "md",
- className: "mb-8",
- }),
+ // Tabs
+ createElement(Tabs, {
+ key: 'tabs',
+ tabs: [
+ { id: 'all', label: 'All Tools', count: mockTools.length },
+ {
+ id: 'featured',
+ label: 'Featured',
+ count: getFeaturedTools().length,
+ },
+ ],
+ activeTab,
+ onTabChange: setActiveTab,
+ size: 'md',
+ className: 'mb-8',
+ }),
- // Tool grid
- createElement(
- "div",
- {
- key: "tools-grid",
- className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6",
- },
- filteredTools.length > 0
- ? filteredTools.map((tool) =>
- createElement(
- Card,
- { key: tool.id, className: "flex flex-col" },
- [
- createElement(CardHeader, { key: "header" }, [
- createElement(
- "div",
- {
- key: "title-row",
- className: "flex items-start justify-between gap-2",
- },
- [
- createElement(CardTitle, { key: "title" }, tool.name),
- createElement(
- "a",
- {
- key: "link",
- href: tool.repository,
- target: "_blank",
- rel: "noopener noreferrer",
- className:
- "text-foreground-secondary hover:text-foreground transition-colors",
- },
- createElement(Icon, {
- icon: "externalLink",
- size: "sm",
- }),
- ),
- ],
- ),
- createElement(
- CardDescription,
- { key: "description" },
- tool.description,
- ),
- ]),
- createElement(
- CardContent,
- { key: "content", className: "flex-1 space-y-4" },
- [
- // Category badge
- createElement(
- "div",
- {
- key: "category",
- className: "flex items-center gap-2",
- },
- [
- createElement(
- Badge,
- {
- key: "badge",
- variant: "secondary",
- size: "sm",
- },
- tool.category,
- ),
- createElement(
- "span",
- {
- key: "version",
- className: "text-xs text-foreground-tertiary",
- },
- `v${tool.version}`,
- ),
- ],
- ),
+ // Tool grid
+ createElement(
+ 'div',
+ {
+ key: 'tools-grid',
+ className: 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6',
+ },
+ filteredTools.length > 0
+ ? filteredTools.map((tool) =>
+ createElement(Card, { key: tool.id, className: 'flex flex-col' }, [
+ createElement(CardHeader, { key: 'header' }, [
+ createElement(
+ 'div',
+ {
+ key: 'title-row',
+ className: 'flex items-start justify-between gap-2',
+ },
+ [
+ createElement(CardTitle, { key: 'title' }, tool.name),
+ createElement(
+ 'a',
+ {
+ key: 'link',
+ href: tool.repository,
+ target: '_blank',
+ rel: 'noopener noreferrer',
+ className:
+ 'text-foreground-secondary hover:text-foreground transition-colors',
+ },
+ createElement(Icon, {
+ icon: 'externalLink',
+ size: 'sm',
+ })
+ ),
+ ]
+ ),
+ createElement(CardDescription, { key: 'description' }, tool.description),
+ ]),
+ createElement(CardContent, { key: 'content', className: 'flex-1 space-y-4' }, [
+ // Category badge
+ createElement(
+ 'div',
+ {
+ key: 'category',
+ className: 'flex items-center gap-2',
+ },
+ [
+ createElement(
+ Badge,
+ {
+ key: 'badge',
+ variant: 'secondary',
+ size: 'sm',
+ },
+ tool.category
+ ),
+ createElement(
+ 'span',
+ {
+ key: 'version',
+ className: 'text-xs text-foreground-tertiary',
+ },
+ `v${tool.version}`
+ ),
+ ]
+ ),
- // Tags
- createElement(
- "div",
- { key: "tags", className: "flex flex-wrap gap-2" },
- tool.tags.map((tag) =>
- createElement(
- Badge,
- { key: tag, variant: "outline", size: "sm" },
- tag,
- ),
- ),
- ),
+ // Tags
+ createElement(
+ 'div',
+ { key: 'tags', className: 'flex flex-wrap gap-2' },
+ tool.tags.map((tag) =>
+ createElement(Badge, { key: tag, variant: 'outline', size: 'sm' }, tag)
+ )
+ ),
- // Usage progress
- createElement(
- "div",
- { key: "usage", className: "space-y-2" },
- [
- createElement(
- "div",
- {
- key: "label",
- className:
- "flex items-center justify-between text-sm",
- },
- [
- createElement(
- "span",
- { key: "text", className: "text-foreground-secondary" },
- "Usage",
- ),
- createElement(
- "span",
- { key: "stars", className: "text-foreground-tertiary" },
- `⭐ ${tool.stars.toLocaleString()}`,
- ),
- ],
- ),
- createElement(ProgressBar, {
- key: "progress",
- value: tool.usage,
- variant:
- tool.usage >= 70
- ? "success"
- : tool.usage >= 50
- ? "primary"
- : "warning",
- size: "sm",
- showLabel: true,
- }),
- ],
- ),
+ // Usage progress
+ createElement('div', { key: 'usage', className: 'space-y-2' }, [
+ createElement(
+ 'div',
+ {
+ key: 'label',
+ className: 'flex items-center justify-between text-sm',
+ },
+ [
+ createElement(
+ 'span',
+ { key: 'text', className: 'text-foreground-secondary' },
+ 'Usage'
+ ),
+ createElement(
+ 'span',
+ { key: 'stars', className: 'text-foreground-tertiary' },
+ `⭐ ${tool.stars.toLocaleString()}`
+ ),
+ ]
+ ),
+ createElement(ProgressBar, {
+ key: 'progress',
+ value: tool.usage,
+ variant:
+ tool.usage >= 70 ? 'success' : tool.usage >= 50 ? 'primary' : 'warning',
+ size: 'sm',
+ showLabel: true,
+ }),
+ ]),
- // Install command
- createElement(CodeBlock, {
- key: "install",
- code: tool.installCommand,
- language: "bash",
- size: "sm",
- showCopy: true,
- }),
- ],
- ),
- createElement(
- CardFooter,
- { key: "footer" },
- createElement(
- Button,
- { variant: "outline", size: "sm", className: "w-full" },
- "View Details",
- ),
- ),
- ],
- ),
- )
- : [
- createElement(
- "div",
- {
- key: "no-results",
- className: "col-span-full text-center py-12 text-foreground-tertiary",
- },
- `No tools found matching "${searchQuery}"`,
- ),
- ],
- ),
- ],
- ),
- ]);
+ // Install command
+ createElement(CodeBlock, {
+ key: 'install',
+ code: tool.installCommand,
+ language: 'bash',
+ size: 'sm',
+ showCopy: true,
+ }),
+ ]),
+ createElement(
+ CardFooter,
+ { key: 'footer' },
+ createElement(
+ Button,
+ { variant: 'outline', size: 'sm', className: 'w-full' },
+ 'View Details'
+ )
+ ),
+ ])
+ )
+ : [
+ createElement(
+ 'div',
+ {
+ key: 'no-results',
+ className: 'col-span-full text-center py-12 text-foreground-tertiary',
+ },
+ `No tools found matching "${searchQuery}"`
+ ),
+ ]
+ ),
+ ]),
+ ]);
}
diff --git a/apps/web/src/components/ThemeToggle.tsx b/apps/web/src/components/ThemeToggle.tsx
index d9dfd24..beb5d92 100644
--- a/apps/web/src/components/ThemeToggle.tsx
+++ b/apps/web/src/components/ThemeToggle.tsx
@@ -1,45 +1,45 @@
-"use client";
+'use client';
-import { Button } from "@tpmjs/ui/Button/Button";
-import { Icon } from "@tpmjs/ui/Icon/Icon";
-import { useTheme } from "next-themes";
-import { createElement, useEffect, useState } from "react";
+import { Button } from '@tpmjs/ui/Button/Button';
+import { Icon } from '@tpmjs/ui/Icon/Icon';
+import { useTheme } from 'next-themes';
+import { createElement, useEffect, useState } from 'react';
export function ThemeToggle(): React.ReactElement {
- const { theme, setTheme } = useTheme();
- const [mounted, setMounted] = useState(false);
+ const { theme, setTheme } = useTheme();
+ const [mounted, setMounted] = useState(false);
- // useEffect only runs on the client, so we can safely show the UI
- useEffect(() => {
- setMounted(true);
- }, []);
+ // useEffect only runs on the client, so we can safely show the UI
+ useEffect(() => {
+ setMounted(true);
+ }, []);
- if (!mounted) {
- // Return a placeholder with the same dimensions to avoid layout shift
- return createElement(Button, {
- variant: "ghost",
- size: "sm",
- className: "w-9 h-9 p-0",
- "aria-label": "Toggle theme",
- });
- }
+ if (!mounted) {
+ // Return a placeholder with the same dimensions to avoid layout shift
+ return createElement(Button, {
+ variant: 'ghost',
+ size: 'sm',
+ className: 'w-9 h-9 p-0',
+ 'aria-label': 'Toggle theme',
+ });
+ }
- const isDark = theme === "dark";
+ const isDark = theme === 'dark';
- return createElement(
- Button,
- {
- variant: "ghost",
- size: "sm",
- className: "w-9 h-9 p-0",
- onClick: () => setTheme(isDark ? "light" : "dark"),
- "aria-label": `Switch to ${isDark ? "light" : "dark"} mode`,
- title: `Switch to ${isDark ? "light" : "dark"} mode`,
- },
- createElement(Icon, {
- icon: isDark ? "sun" : "moon",
- size: "md",
- className: "transition-transform duration-300",
- }),
- );
+ return createElement(
+ Button,
+ {
+ variant: 'ghost',
+ size: 'sm',
+ className: 'w-9 h-9 p-0',
+ onClick: () => setTheme(isDark ? 'light' : 'dark'),
+ 'aria-label': `Switch to ${isDark ? 'light' : 'dark'} mode`,
+ title: `Switch to ${isDark ? 'light' : 'dark'} mode`,
+ },
+ createElement(Icon, {
+ icon: isDark ? 'sun' : 'moon',
+ size: 'md',
+ className: 'transition-transform duration-300',
+ })
+ );
}
diff --git a/apps/web/src/components/providers/ThemeProvider.tsx b/apps/web/src/components/providers/ThemeProvider.tsx
index 39ccc70..387ba76 100644
--- a/apps/web/src/components/providers/ThemeProvider.tsx
+++ b/apps/web/src/components/providers/ThemeProvider.tsx
@@ -1,11 +1,8 @@
-"use client";
+'use client';
-import { ThemeProvider as NextThemesProvider } from "next-themes";
-import { type ComponentProps, createElement } from "react";
+import { ThemeProvider as NextThemesProvider } from 'next-themes';
+import { type ComponentProps, createElement } from 'react';
-export function ThemeProvider({
- children,
- ...props
-}: ComponentProps) {
- return createElement(NextThemesProvider, props, children);
+export function ThemeProvider({ children, ...props }: ComponentProps) {
+ return createElement(NextThemesProvider, props, children);
}
diff --git a/apps/web/src/data/homePageData.ts b/apps/web/src/data/homePageData.ts
index 4736148..2ddf6bf 100644
--- a/apps/web/src/data/homePageData.ts
+++ b/apps/web/src/data/homePageData.ts
@@ -1,230 +1,222 @@
-import type { IconName } from "@tpmjs/ui/Icon/Icon";
+import type { IconName } from '@tpmjs/ui/Icon/Icon';
export interface ToolCard {
- id: string;
- name: string;
- icon: IconName;
- description: string;
- category: string;
- categoryVariant:
- | "default"
- | "secondary"
- | "outline"
- | "success"
- | "error"
- | "warning"
- | "info";
- weeklyUsage: string;
- href: string;
+ id: string;
+ name: string;
+ icon: IconName;
+ description: string;
+ category: string;
+ categoryVariant: 'default' | 'secondary' | 'outline' | 'success' | 'error' | 'warning' | 'info';
+ weeklyUsage: string;
+ href: string;
}
export interface Category {
- id: string;
- name: string;
- icon: IconName;
- colorClass: string;
- toolCount: number;
- href: string;
+ id: string;
+ name: string;
+ icon: IconName;
+ colorClass: string;
+ toolCount: number;
+ href: string;
}
export interface Statistic {
- icon: IconName;
- value: string;
- label: string;
- subtext?: string;
+ icon: IconName;
+ value: string;
+ label: string;
+ subtext?: string;
}
export const featuredTools: ToolCard[] = [
- {
- id: "tool-search",
- name: "tool-search",
- icon: "check",
- description:
- "Meta-tool that lets AI agents discover and load tools on-demand using semantic search.",
- category: "Meta-Tools",
- categoryVariant: "warning",
- weeklyUsage: "847K/week",
- href: "/tool/tool-search",
- },
- {
- id: "web-scraper",
- name: "web-scraper",
- icon: "externalLink",
- description:
- "Extract structured data from any website with automatic schema detection and pagination support.",
- category: "Web & APIs",
- categoryVariant: "info",
- weeklyUsage: "524K/week",
- href: "/tool/web-scraper",
- },
- {
- id: "github-manager",
- name: "github-manager",
- icon: "github",
- description:
- "Manage GitHub repositories, issues, pull requests, and workflows programmatically.",
- category: "Code & Git",
- categoryVariant: "default",
- weeklyUsage: "392K/week",
- href: "/tool/github-manager",
- },
- {
- id: "sql-query",
- name: "sql-query",
- icon: "copy",
- description:
- "Execute SQL queries across PostgreSQL, MySQL, and SQLite databases with connection pooling.",
- category: "Databases",
- categoryVariant: "success",
- weeklyUsage: "287K/week",
- href: "/tool/sql-query",
- },
- {
- id: "pdf-parser",
- name: "pdf-parser",
- icon: "copy",
- description:
- "Extract text, tables, and metadata from PDF documents with OCR support for scanned pages.",
- category: "Documents",
- categoryVariant: "outline",
- weeklyUsage: "156K/week",
- href: "/tool/pdf-parser",
- },
- {
- id: "email-sender",
- name: "email-sender",
- icon: "check",
- description:
- "Send transactional emails with templates, attachments, and delivery tracking.",
- category: "Email",
- categoryVariant: "secondary",
- weeklyUsage: "203K/week",
- href: "/tool/email-sender",
- },
+ {
+ id: 'tool-search',
+ name: 'tool-search',
+ icon: 'check',
+ description:
+ 'Meta-tool that lets AI agents discover and load tools on-demand using semantic search.',
+ category: 'Meta-Tools',
+ categoryVariant: 'warning',
+ weeklyUsage: '847K/week',
+ href: '/tool/tool-search',
+ },
+ {
+ id: 'web-scraper',
+ name: 'web-scraper',
+ icon: 'externalLink',
+ description:
+ 'Extract structured data from any website with automatic schema detection and pagination support.',
+ category: 'Web & APIs',
+ categoryVariant: 'info',
+ weeklyUsage: '524K/week',
+ href: '/tool/web-scraper',
+ },
+ {
+ id: 'github-manager',
+ name: 'github-manager',
+ icon: 'github',
+ description:
+ 'Manage GitHub repositories, issues, pull requests, and workflows programmatically.',
+ category: 'Code & Git',
+ categoryVariant: 'default',
+ weeklyUsage: '392K/week',
+ href: '/tool/github-manager',
+ },
+ {
+ id: 'sql-query',
+ name: 'sql-query',
+ icon: 'copy',
+ description:
+ 'Execute SQL queries across PostgreSQL, MySQL, and SQLite databases with connection pooling.',
+ category: 'Databases',
+ categoryVariant: 'success',
+ weeklyUsage: '287K/week',
+ href: '/tool/sql-query',
+ },
+ {
+ id: 'pdf-parser',
+ name: 'pdf-parser',
+ icon: 'copy',
+ description:
+ 'Extract text, tables, and metadata from PDF documents with OCR support for scanned pages.',
+ category: 'Documents',
+ categoryVariant: 'outline',
+ weeklyUsage: '156K/week',
+ href: '/tool/pdf-parser',
+ },
+ {
+ id: 'email-sender',
+ name: 'email-sender',
+ icon: 'check',
+ description: 'Send transactional emails with templates, attachments, and delivery tracking.',
+ category: 'Email',
+ categoryVariant: 'secondary',
+ weeklyUsage: '203K/week',
+ href: '/tool/email-sender',
+ },
];
export const categories: Category[] = [
- {
- id: "web-apis",
- name: "Web & APIs",
- icon: "externalLink",
- colorClass: "bg-blue-500 text-white",
- toolCount: 423,
- href: "/category/web-apis",
- },
- {
- id: "databases",
- name: "Databases",
- icon: "copy",
- colorClass: "bg-emerald-500 text-white",
- toolCount: 198,
- href: "/category/databases",
- },
- {
- id: "documents",
- name: "Documents",
- icon: "copy",
- colorClass: "bg-amber-500 text-white",
- toolCount: 156,
- href: "/category/documents",
- },
- {
- id: "images",
- name: "Images",
- icon: "check",
- colorClass: "bg-pink-500 text-white",
- toolCount: 134,
- href: "/category/images",
- },
- {
- id: "email",
- name: "Email",
- icon: "check",
- colorClass: "bg-indigo-500 text-white",
- toolCount: 89,
- href: "/category/email",
- },
- {
- id: "calendar",
- name: "Calendar",
- icon: "check",
- colorClass: "bg-orange-500 text-white",
- toolCount: 67,
- href: "/category/calendar",
- },
- {
- id: "search",
- name: "Search",
- icon: "check",
- colorClass: "bg-red-500 text-white",
- toolCount: 112,
- href: "/category/search",
- },
- {
- id: "code-execution",
- name: "Code Execution",
- icon: "github",
- colorClass: "bg-zinc-700 text-white",
- toolCount: 245,
- href: "/category/code-execution",
- },
- {
- id: "communication",
- name: "Communication",
- icon: "check",
- colorClass: "bg-cyan-500 text-white",
- toolCount: 178,
- href: "/category/communication",
- },
- {
- id: "analytics",
- name: "Analytics",
- icon: "check",
- colorClass: "bg-violet-500 text-white",
- toolCount: 203,
- href: "/category/analytics",
- },
- {
- id: "security",
- name: "Security",
- icon: "check",
- colorClass: "bg-slate-600 text-white",
- toolCount: 91,
- href: "/category/security",
- },
- {
- id: "workflows",
- name: "Workflows",
- icon: "check",
- colorClass: "bg-teal-500 text-white",
- toolCount: 167,
- href: "/category/workflows",
- },
+ {
+ id: 'web-apis',
+ name: 'Web & APIs',
+ icon: 'externalLink',
+ colorClass: 'bg-blue-500 text-white',
+ toolCount: 423,
+ href: '/category/web-apis',
+ },
+ {
+ id: 'databases',
+ name: 'Databases',
+ icon: 'copy',
+ colorClass: 'bg-emerald-500 text-white',
+ toolCount: 198,
+ href: '/category/databases',
+ },
+ {
+ id: 'documents',
+ name: 'Documents',
+ icon: 'copy',
+ colorClass: 'bg-amber-500 text-white',
+ toolCount: 156,
+ href: '/category/documents',
+ },
+ {
+ id: 'images',
+ name: 'Images',
+ icon: 'check',
+ colorClass: 'bg-pink-500 text-white',
+ toolCount: 134,
+ href: '/category/images',
+ },
+ {
+ id: 'email',
+ name: 'Email',
+ icon: 'check',
+ colorClass: 'bg-indigo-500 text-white',
+ toolCount: 89,
+ href: '/category/email',
+ },
+ {
+ id: 'calendar',
+ name: 'Calendar',
+ icon: 'check',
+ colorClass: 'bg-orange-500 text-white',
+ toolCount: 67,
+ href: '/category/calendar',
+ },
+ {
+ id: 'search',
+ name: 'Search',
+ icon: 'check',
+ colorClass: 'bg-red-500 text-white',
+ toolCount: 112,
+ href: '/category/search',
+ },
+ {
+ id: 'code-execution',
+ name: 'Code Execution',
+ icon: 'github',
+ colorClass: 'bg-zinc-700 text-white',
+ toolCount: 245,
+ href: '/category/code-execution',
+ },
+ {
+ id: 'communication',
+ name: 'Communication',
+ icon: 'check',
+ colorClass: 'bg-cyan-500 text-white',
+ toolCount: 178,
+ href: '/category/communication',
+ },
+ {
+ id: 'analytics',
+ name: 'Analytics',
+ icon: 'check',
+ colorClass: 'bg-violet-500 text-white',
+ toolCount: 203,
+ href: '/category/analytics',
+ },
+ {
+ id: 'security',
+ name: 'Security',
+ icon: 'check',
+ colorClass: 'bg-slate-600 text-white',
+ toolCount: 91,
+ href: '/category/security',
+ },
+ {
+ id: 'workflows',
+ name: 'Workflows',
+ icon: 'check',
+ colorClass: 'bg-teal-500 text-white',
+ toolCount: 167,
+ href: '/category/workflows',
+ },
];
export const statistics: Statistic[] = [
- {
- icon: "copy",
- value: "2,847",
- label: "Published Tools",
- subtext: "Across 24 categories",
- },
- {
- icon: "github",
- value: "48K+",
- label: "Active Developers",
- subtext: "Building with TPMJS",
- },
- {
- icon: "check",
- value: "12M+",
- label: "Weekly Invocations",
- subtext: "Across all tools",
- },
- {
- icon: "chevronDown",
- value: "47ms",
- label: "Average Response",
- subtext: "95th percentile latency",
- },
+ {
+ icon: 'copy',
+ value: '2,847',
+ label: 'Published Tools',
+ subtext: 'Across 24 categories',
+ },
+ {
+ icon: 'github',
+ value: '48K+',
+ label: 'Active Developers',
+ subtext: 'Building with TPMJS',
+ },
+ {
+ icon: 'check',
+ value: '12M+',
+ label: 'Weekly Invocations',
+ subtext: 'Across all tools',
+ },
+ {
+ icon: 'chevronDown',
+ value: '47ms',
+ label: 'Average Response',
+ subtext: '95th percentile latency',
+ },
];
diff --git a/apps/web/src/data/toolData.ts b/apps/web/src/data/toolData.ts
index e1ad7ec..d2858fd 100644
--- a/apps/web/src/data/toolData.ts
+++ b/apps/web/src/data/toolData.ts
@@ -4,235 +4,223 @@
*/
export interface Tool {
- id: string;
- name: string;
- description: string;
- category: string;
- installCommand: string;
- usage: number; // 0-100 percentage
- stars: number;
- version: string;
- tags: string[];
- homepage?: string;
- repository?: string;
+ id: string;
+ name: string;
+ description: string;
+ category: string;
+ installCommand: string;
+ usage: number; // 0-100 percentage
+ stars: number;
+ version: string;
+ tags: string[];
+ homepage?: string;
+ repository?: string;
}
export const toolCategories = [
- "Web & APIs",
- "Databases",
- "Documents",
- "Images",
- "Email",
- "Calendar",
- "Search",
- "Code Execution",
- "Communication",
- "Analytics",
- "Security",
- "Workflows",
+ 'Web & APIs',
+ 'Databases',
+ 'Documents',
+ 'Images',
+ 'Email',
+ 'Calendar',
+ 'Search',
+ 'Code Execution',
+ 'Communication',
+ 'Analytics',
+ 'Security',
+ 'Workflows',
] as const;
export const mockTools: Tool[] = [
- {
- id: "web-fetch",
- name: "web-fetch",
- description:
- "Fetch and parse web content with automatic HTML-to-markdown conversion",
- category: "Web & APIs",
- installCommand: "npm install @tpmjs/web-fetch",
- usage: 87,
- stars: 12453,
- version: "2.1.4",
- tags: ["http", "scraping", "markdown"],
- homepage: "https://tpmjs.com/tools/web-fetch",
- repository: "https://github.com/tpmjs/web-fetch",
- },
- {
- id: "database-query",
- name: "database-query",
- description:
- "Execute SQL queries across multiple database engines with type-safe results",
- category: "Databases",
- installCommand: "npm install @tpmjs/database-query",
- usage: 72,
- stars: 8921,
- version: "3.0.1",
- tags: ["sql", "postgres", "mysql", "sqlite"],
- homepage: "https://tpmjs.com/tools/database-query",
- repository: "https://github.com/tpmjs/database-query",
- },
- {
- id: "pdf-extract",
- name: "pdf-extract",
- description:
- "Extract text, images, and metadata from PDF documents with OCR support",
- category: "Documents",
- installCommand: "npm install @tpmjs/pdf-extract",
- usage: 65,
- stars: 7832,
- version: "1.9.2",
- tags: ["pdf", "ocr", "document-processing"],
- homepage: "https://tpmjs.com/tools/pdf-extract",
- repository: "https://github.com/tpmjs/pdf-extract",
- },
- {
- id: "image-transform",
- name: "image-transform",
- description:
- "Resize, crop, and optimize images with automatic format conversion",
- category: "Images",
- installCommand: "npm install @tpmjs/image-transform",
- usage: 58,
- stars: 6745,
- version: "2.4.0",
- tags: ["images", "resize", "optimization"],
- homepage: "https://tpmjs.com/tools/image-transform",
- repository: "https://github.com/tpmjs/image-transform",
- },
- {
- id: "email-send",
- name: "email-send",
- description:
- "Send transactional emails with template support and delivery tracking",
- category: "Email",
- installCommand: "npm install @tpmjs/email-send",
- usage: 51,
- stars: 5623,
- version: "1.7.3",
- tags: ["email", "smtp", "templates"],
- homepage: "https://tpmjs.com/tools/email-send",
- repository: "https://github.com/tpmjs/email-send",
- },
- {
- id: "calendar-sync",
- name: "calendar-sync",
- description:
- "Sync events across Google Calendar, Outlook, and Apple Calendar",
- category: "Calendar",
- installCommand: "npm install @tpmjs/calendar-sync",
- usage: 43,
- stars: 4891,
- version: "2.0.5",
- tags: ["calendar", "events", "sync"],
- homepage: "https://tpmjs.com/tools/calendar-sync",
- repository: "https://github.com/tpmjs/calendar-sync",
- },
- {
- id: "semantic-search",
- name: "semantic-search",
- description:
- "Vector-based semantic search with support for embeddings and similarity",
- category: "Search",
- installCommand: "npm install @tpmjs/semantic-search",
- usage: 79,
- stars: 11234,
- version: "3.2.1",
- tags: ["search", "embeddings", "vector", "ai"],
- homepage: "https://tpmjs.com/tools/semantic-search",
- repository: "https://github.com/tpmjs/semantic-search",
- },
- {
- id: "code-execute",
- name: "code-execute",
- description:
- "Safely execute code in sandboxed environments with timeout controls",
- category: "Code Execution",
- installCommand: "npm install @tpmjs/code-execute",
- usage: 68,
- stars: 9567,
- version: "1.5.8",
- tags: ["sandbox", "execution", "security"],
- homepage: "https://tpmjs.com/tools/code-execute",
- repository: "https://github.com/tpmjs/code-execute",
- },
- {
- id: "slack-notify",
- name: "slack-notify",
- description:
- "Send rich notifications to Slack channels with interactive components",
- category: "Communication",
- installCommand: "npm install @tpmjs/slack-notify",
- usage: 54,
- stars: 6234,
- version: "2.3.2",
- tags: ["slack", "notifications", "webhooks"],
- homepage: "https://tpmjs.com/tools/slack-notify",
- repository: "https://github.com/tpmjs/slack-notify",
- },
- {
- id: "analytics-track",
- name: "analytics-track",
- description:
- "Track user events and metrics across multiple analytics platforms",
- category: "Analytics",
- installCommand: "npm install @tpmjs/analytics-track",
- usage: 61,
- stars: 7123,
- version: "1.8.4",
- tags: ["analytics", "tracking", "metrics"],
- homepage: "https://tpmjs.com/tools/analytics-track",
- repository: "https://github.com/tpmjs/analytics-track",
- },
- {
- id: "secret-vault",
- name: "secret-vault",
- description:
- "Securely store and retrieve secrets with encryption and access control",
- category: "Security",
- installCommand: "npm install @tpmjs/secret-vault",
- usage: 76,
- stars: 10432,
- version: "2.6.0",
- tags: ["security", "secrets", "encryption"],
- homepage: "https://tpmjs.com/tools/secret-vault",
- repository: "https://github.com/tpmjs/secret-vault",
- },
- {
- id: "workflow-orchestrate",
- name: "workflow-orchestrate",
- description:
- "Orchestrate complex workflows with conditional logic and error handling",
- category: "Workflows",
- installCommand: "npm install @tpmjs/workflow-orchestrate",
- usage: 47,
- stars: 5789,
- version: "3.1.2",
- tags: ["workflows", "orchestration", "automation"],
- homepage: "https://tpmjs.com/tools/workflow-orchestrate",
- repository: "https://github.com/tpmjs/workflow-orchestrate",
- },
+ {
+ id: 'web-fetch',
+ name: 'web-fetch',
+ description: 'Fetch and parse web content with automatic HTML-to-markdown conversion',
+ category: 'Web & APIs',
+ installCommand: 'npm install @tpmjs/web-fetch',
+ usage: 87,
+ stars: 12453,
+ version: '2.1.4',
+ tags: ['http', 'scraping', 'markdown'],
+ homepage: 'https://tpmjs.com/tools/web-fetch',
+ repository: 'https://github.com/tpmjs/web-fetch',
+ },
+ {
+ id: 'database-query',
+ name: 'database-query',
+ description: 'Execute SQL queries across multiple database engines with type-safe results',
+ category: 'Databases',
+ installCommand: 'npm install @tpmjs/database-query',
+ usage: 72,
+ stars: 8921,
+ version: '3.0.1',
+ tags: ['sql', 'postgres', 'mysql', 'sqlite'],
+ homepage: 'https://tpmjs.com/tools/database-query',
+ repository: 'https://github.com/tpmjs/database-query',
+ },
+ {
+ id: 'pdf-extract',
+ name: 'pdf-extract',
+ description: 'Extract text, images, and metadata from PDF documents with OCR support',
+ category: 'Documents',
+ installCommand: 'npm install @tpmjs/pdf-extract',
+ usage: 65,
+ stars: 7832,
+ version: '1.9.2',
+ tags: ['pdf', 'ocr', 'document-processing'],
+ homepage: 'https://tpmjs.com/tools/pdf-extract',
+ repository: 'https://github.com/tpmjs/pdf-extract',
+ },
+ {
+ id: 'image-transform',
+ name: 'image-transform',
+ description: 'Resize, crop, and optimize images with automatic format conversion',
+ category: 'Images',
+ installCommand: 'npm install @tpmjs/image-transform',
+ usage: 58,
+ stars: 6745,
+ version: '2.4.0',
+ tags: ['images', 'resize', 'optimization'],
+ homepage: 'https://tpmjs.com/tools/image-transform',
+ repository: 'https://github.com/tpmjs/image-transform',
+ },
+ {
+ id: 'email-send',
+ name: 'email-send',
+ description: 'Send transactional emails with template support and delivery tracking',
+ category: 'Email',
+ installCommand: 'npm install @tpmjs/email-send',
+ usage: 51,
+ stars: 5623,
+ version: '1.7.3',
+ tags: ['email', 'smtp', 'templates'],
+ homepage: 'https://tpmjs.com/tools/email-send',
+ repository: 'https://github.com/tpmjs/email-send',
+ },
+ {
+ id: 'calendar-sync',
+ name: 'calendar-sync',
+ description: 'Sync events across Google Calendar, Outlook, and Apple Calendar',
+ category: 'Calendar',
+ installCommand: 'npm install @tpmjs/calendar-sync',
+ usage: 43,
+ stars: 4891,
+ version: '2.0.5',
+ tags: ['calendar', 'events', 'sync'],
+ homepage: 'https://tpmjs.com/tools/calendar-sync',
+ repository: 'https://github.com/tpmjs/calendar-sync',
+ },
+ {
+ id: 'semantic-search',
+ name: 'semantic-search',
+ description: 'Vector-based semantic search with support for embeddings and similarity',
+ category: 'Search',
+ installCommand: 'npm install @tpmjs/semantic-search',
+ usage: 79,
+ stars: 11234,
+ version: '3.2.1',
+ tags: ['search', 'embeddings', 'vector', 'ai'],
+ homepage: 'https://tpmjs.com/tools/semantic-search',
+ repository: 'https://github.com/tpmjs/semantic-search',
+ },
+ {
+ id: 'code-execute',
+ name: 'code-execute',
+ description: 'Safely execute code in sandboxed environments with timeout controls',
+ category: 'Code Execution',
+ installCommand: 'npm install @tpmjs/code-execute',
+ usage: 68,
+ stars: 9567,
+ version: '1.5.8',
+ tags: ['sandbox', 'execution', 'security'],
+ homepage: 'https://tpmjs.com/tools/code-execute',
+ repository: 'https://github.com/tpmjs/code-execute',
+ },
+ {
+ id: 'slack-notify',
+ name: 'slack-notify',
+ description: 'Send rich notifications to Slack channels with interactive components',
+ category: 'Communication',
+ installCommand: 'npm install @tpmjs/slack-notify',
+ usage: 54,
+ stars: 6234,
+ version: '2.3.2',
+ tags: ['slack', 'notifications', 'webhooks'],
+ homepage: 'https://tpmjs.com/tools/slack-notify',
+ repository: 'https://github.com/tpmjs/slack-notify',
+ },
+ {
+ id: 'analytics-track',
+ name: 'analytics-track',
+ description: 'Track user events and metrics across multiple analytics platforms',
+ category: 'Analytics',
+ installCommand: 'npm install @tpmjs/analytics-track',
+ usage: 61,
+ stars: 7123,
+ version: '1.8.4',
+ tags: ['analytics', 'tracking', 'metrics'],
+ homepage: 'https://tpmjs.com/tools/analytics-track',
+ repository: 'https://github.com/tpmjs/analytics-track',
+ },
+ {
+ id: 'secret-vault',
+ name: 'secret-vault',
+ description: 'Securely store and retrieve secrets with encryption and access control',
+ category: 'Security',
+ installCommand: 'npm install @tpmjs/secret-vault',
+ usage: 76,
+ stars: 10432,
+ version: '2.6.0',
+ tags: ['security', 'secrets', 'encryption'],
+ homepage: 'https://tpmjs.com/tools/secret-vault',
+ repository: 'https://github.com/tpmjs/secret-vault',
+ },
+ {
+ id: 'workflow-orchestrate',
+ name: 'workflow-orchestrate',
+ description: 'Orchestrate complex workflows with conditional logic and error handling',
+ category: 'Workflows',
+ installCommand: 'npm install @tpmjs/workflow-orchestrate',
+ usage: 47,
+ stars: 5789,
+ version: '3.1.2',
+ tags: ['workflows', 'orchestration', 'automation'],
+ homepage: 'https://tpmjs.com/tools/workflow-orchestrate',
+ repository: 'https://github.com/tpmjs/workflow-orchestrate',
+ },
];
/**
* Get tools by category
*/
export function getToolsByCategory(category: string): Tool[] {
- return mockTools.filter((tool) => tool.category === category);
+ return mockTools.filter((tool) => tool.category === category);
}
/**
* Get featured tools (top 6 by usage)
*/
export function getFeaturedTools(): Tool[] {
- return [...mockTools].sort((a, b) => b.usage - a.usage).slice(0, 6);
+ return [...mockTools].sort((a, b) => b.usage - a.usage).slice(0, 6);
}
/**
* Get tool by ID
*/
export function getToolById(id: string): Tool | undefined {
- return mockTools.find((tool) => tool.id === id);
+ return mockTools.find((tool) => tool.id === id);
}
/**
* Search tools by name or description
*/
export function searchTools(query: string): Tool[] {
- const lowerQuery = query.toLowerCase();
- return mockTools.filter(
- (tool) =>
- tool.name.toLowerCase().includes(lowerQuery) ||
- tool.description.toLowerCase().includes(lowerQuery) ||
- tool.tags.some((tag) => tag.toLowerCase().includes(lowerQuery)),
- );
+ const lowerQuery = query.toLowerCase();
+ return mockTools.filter(
+ (tool) =>
+ tool.name.toLowerCase().includes(lowerQuery) ||
+ tool.description.toLowerCase().includes(lowerQuery) ||
+ tool.tags.some((tag) => tag.toLowerCase().includes(lowerQuery))
+ );
}
diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts
index cf17f94..a4aea36 100644
--- a/apps/web/tailwind.config.ts
+++ b/apps/web/tailwind.config.ts
@@ -1,5 +1,5 @@
-import type { Config } from 'tailwindcss';
import baseConfig from '@tpmjs/tailwind-config/base';
+import type { Config } from 'tailwindcss';
export default {
...baseConfig,
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
index ee70ce0..00b1618 100644
--- a/apps/web/tsconfig.json
+++ b/apps/web/tsconfig.json
@@ -6,11 +6,6 @@
"@/*": ["./src/*"]
}
},
- "include": [
- "next-env.d.ts",
- "**/*.ts",
- "**/*.tsx",
- ".next/types/**/*.ts"
- ],
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000..9aceb0e
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
+ "extends": ["./packages/config/biome.json"]
+}
diff --git a/knip.json b/knip.json
index a9da12d..1591e2c 100644
--- a/knip.json
+++ b/knip.json
@@ -1,57 +1,57 @@
{
- "$schema": "https://unpkg.com/knip@5/schema.json",
- "workspaces": {
- ".": {
- "entry": ["turbo.json"],
- "project": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
- "ignore": [
- "**/*.test.ts",
- "**/*.test.tsx",
- "**/*.spec.ts",
- "**/*.spec.tsx",
- "**/dist/**",
- "**/.next/**",
- "**/node_modules/**",
- "**/.turbo/**",
- "**/storybook-static/**"
- ]
- },
- "apps/*": {
- "entry": [
- "app/**/*.ts",
- "app/**/*.tsx",
- "pages/**/*.ts",
- "pages/**/*.tsx",
- "src/**/*.ts",
- "src/**/*.tsx"
- ],
- "project": ["**/*.ts", "**/*.tsx"]
- },
- "packages/*": {
- "entry": ["src/**/*.ts", "src/**/*.tsx", "index.ts", "index.tsx"],
- "project": ["**/*.ts", "**/*.tsx"]
- }
- },
- "ignore": [
- "**/*.test.ts",
- "**/*.test.tsx",
- "**/*.spec.ts",
- "**/*.spec.tsx",
- "**/vitest.config.ts",
- "**/tsup.config.ts",
- "**/tailwind.config.ts",
- "reset.d.ts"
- ],
- "ignoreDependencies": [
- "@changesets/cli",
- "lefthook",
- "turbo",
- "typescript",
- "@biomejs/biome",
- "@total-typescript/ts-reset",
- "type-coverage",
- "knip",
- "dependency-cruiser"
- ],
- "ignoreExportsUsedInFile": true
+ "$schema": "https://unpkg.com/knip@5/schema.json",
+ "workspaces": {
+ ".": {
+ "entry": ["turbo.json"],
+ "project": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
+ "ignore": [
+ "**/*.test.ts",
+ "**/*.test.tsx",
+ "**/*.spec.ts",
+ "**/*.spec.tsx",
+ "**/dist/**",
+ "**/.next/**",
+ "**/node_modules/**",
+ "**/.turbo/**",
+ "**/storybook-static/**"
+ ]
+ },
+ "apps/*": {
+ "entry": [
+ "app/**/*.ts",
+ "app/**/*.tsx",
+ "pages/**/*.ts",
+ "pages/**/*.tsx",
+ "src/**/*.ts",
+ "src/**/*.tsx"
+ ],
+ "project": ["**/*.ts", "**/*.tsx"]
+ },
+ "packages/*": {
+ "entry": ["src/**/*.ts", "src/**/*.tsx", "index.ts", "index.tsx"],
+ "project": ["**/*.ts", "**/*.tsx"]
+ }
+ },
+ "ignore": [
+ "**/*.test.ts",
+ "**/*.test.tsx",
+ "**/*.spec.ts",
+ "**/*.spec.tsx",
+ "**/vitest.config.ts",
+ "**/tsup.config.ts",
+ "**/tailwind.config.ts",
+ "reset.d.ts"
+ ],
+ "ignoreDependencies": [
+ "@changesets/cli",
+ "lefthook",
+ "turbo",
+ "typescript",
+ "@biomejs/biome",
+ "@total-typescript/ts-reset",
+ "type-coverage",
+ "knip",
+ "dependency-cruiser"
+ ],
+ "ignoreExportsUsedInFile": true
}
diff --git a/package.json b/package.json
index e9996ad..76d4f84 100644
--- a/package.json
+++ b/package.json
@@ -1,41 +1,41 @@
{
- "name": "@tpmjs/monorepo",
- "version": "0.0.0",
- "private": true,
- "type": "module",
- "engines": {
- "node": ">=22",
- "pnpm": ">=8"
- },
- "packageManager": "pnpm@10.14.0",
- "scripts": {
- "dev": "turbo dev",
- "build": "turbo build",
- "test": "turbo test",
- "test:ui": "turbo test:ui",
- "lint": "turbo lint",
- "format": "biome format --write .",
- "format:check": "biome check .",
- "type-check": "turbo type-check",
- "type-coverage": "type-coverage --at-least 95",
- "find-deadcode": "knip",
- "check-architecture": "depcruise --validate --config .dependency-cruiser.js apps packages",
- "clean": "turbo clean && rm -rf node_modules .turbo",
- "changeset": "changeset",
- "changeset:version": "changeset version && pnpm install --no-frozen-lockfile",
- "changeset:publish": "pnpm build && changeset publish",
- "prepare": "lefthook install"
- },
- "devDependencies": {
- "@biomejs/biome": "^1.9.4",
- "@changesets/cli": "^2.27.10",
- "@total-typescript/ts-reset": "^0.6.1",
- "@types/node": "^22.10.2",
- "dependency-cruiser": "^17.3.1",
- "knip": "^5.70.2",
- "lefthook": "^1.10.1",
- "turbo": "^2.6.1",
- "type-coverage": "^2.29.7",
- "typescript": "^5.9.3"
- }
+ "name": "@tpmjs/monorepo",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "engines": {
+ "node": ">=22",
+ "pnpm": ">=8"
+ },
+ "packageManager": "pnpm@10.14.0",
+ "scripts": {
+ "dev": "turbo dev",
+ "build": "turbo build",
+ "test": "turbo test",
+ "test:ui": "turbo test:ui",
+ "lint": "turbo lint",
+ "format": "biome format --write .",
+ "format:check": "biome check .",
+ "type-check": "turbo type-check",
+ "type-coverage": "type-coverage --at-least 95",
+ "find-deadcode": "knip",
+ "check-architecture": "depcruise --validate --config .dependency-cruiser.js apps packages",
+ "clean": "turbo clean && rm -rf node_modules .turbo",
+ "changeset": "changeset",
+ "changeset:version": "changeset version && pnpm install --no-frozen-lockfile",
+ "changeset:publish": "pnpm build && changeset publish",
+ "prepare": "lefthook install"
+ },
+ "devDependencies": {
+ "@biomejs/biome": "^1.9.4",
+ "@changesets/cli": "^2.27.10",
+ "@total-typescript/ts-reset": "^0.6.1",
+ "@types/node": "^22.10.2",
+ "dependency-cruiser": "^17.3.1",
+ "knip": "^5.70.2",
+ "lefthook": "^1.10.1",
+ "turbo": "^2.6.1",
+ "type-coverage": "^2.29.7",
+ "typescript": "^5.9.3"
+ }
}
diff --git a/packages/config/biome.json b/packages/config/biome.json
index d18ec6a..48a14a4 100644
--- a/packages/config/biome.json
+++ b/packages/config/biome.json
@@ -5,6 +5,19 @@
"clientKind": "git",
"useIgnoreFile": true
},
+ "files": {
+ "ignore": [
+ "**/node_modules/**",
+ "**/dist/**",
+ "**/build/**",
+ "**/.next/**",
+ "**/out/**",
+ "**/coverage/**",
+ "**/.turbo/**",
+ "**/storybook-static/**",
+ "**/*.tsbuildinfo"
+ ]
+ },
"formatter": {
"enabled": true,
"indentStyle": "space",
@@ -25,6 +38,10 @@
},
"suspicious": {
"noExplicitAny": "warn"
+ },
+ "a11y": {
+ "noSvgWithoutTitle": "warn",
+ "useSemanticElements": "warn"
}
}
},
diff --git a/packages/config/eslint/base.js b/packages/config/eslint/base.js
index 986566f..c52ba61 100644
--- a/packages/config/eslint/base.js
+++ b/packages/config/eslint/base.js
@@ -1,44 +1,37 @@
-import js from "@eslint/js";
-import importPlugin from "eslint-plugin-import";
-import tseslint from "typescript-eslint";
+import js from '@eslint/js';
+import importPlugin from 'eslint-plugin-import';
+import tseslint from 'typescript-eslint';
-export default tseslint.config(
- js.configs.recommended,
- ...tseslint.configs.recommended,
- {
- plugins: {
- import: importPlugin,
- },
- rules: {
- // Module boundaries
- "import/no-internal-modules": [
- "error",
- {
- allow: [
- "@tpmjs/ui/*/[!index]*",
- "@tpmjs/utils/*",
- "@tpmjs/types/*",
- "@tpmjs/eslint-config/*",
- "next/**",
- "./.next/*",
- ],
- },
- ],
- "import/no-restricted-paths": [
- "error",
- {
- zones: [
- { target: "./packages", from: "./apps" },
- { target: "./packages/ui", from: "./packages/utils" },
- ],
- },
- ],
- "import/no-anonymous-default-export": "error",
- "@typescript-eslint/no-unused-vars": [
- "error",
- { argsIgnorePattern: "^_" },
- ],
- "@typescript-eslint/no-explicit-any": "warn",
- },
- },
-);
+export default tseslint.config(js.configs.recommended, ...tseslint.configs.recommended, {
+ plugins: {
+ import: importPlugin,
+ },
+ rules: {
+ // Module boundaries
+ 'import/no-internal-modules': [
+ 'error',
+ {
+ allow: [
+ '@tpmjs/ui/*/[!index]*',
+ '@tpmjs/utils/*',
+ '@tpmjs/types/*',
+ '@tpmjs/eslint-config/*',
+ 'next/**',
+ './.next/*',
+ ],
+ },
+ ],
+ 'import/no-restricted-paths': [
+ 'error',
+ {
+ zones: [
+ { target: './packages', from: './apps' },
+ { target: './packages/ui', from: './packages/utils' },
+ ],
+ },
+ ],
+ 'import/no-anonymous-default-export': 'error',
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
+ '@typescript-eslint/no-explicit-any': 'warn',
+ },
+});
diff --git a/packages/config/eslint/package.json b/packages/config/eslint/package.json
index e8a3b6e..35e2d4d 100644
--- a/packages/config/eslint/package.json
+++ b/packages/config/eslint/package.json
@@ -1,17 +1,17 @@
{
- "name": "@tpmjs/eslint-config",
- "version": "0.0.0",
- "private": true,
- "type": "module",
- "main": "./base.js",
- "files": ["base.js", "react.js"],
- "dependencies": {
- "@eslint/js": "^9.18.0",
- "eslint": "^9.18.0",
- "eslint-plugin-import": "^2.31.0",
- "eslint-plugin-jsx-a11y": "^6.10.2",
- "eslint-plugin-react": "^7.37.3",
- "eslint-plugin-react-hooks": "^5.1.0",
- "typescript-eslint": "^8.19.1"
- }
+ "name": "@tpmjs/eslint-config",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "main": "./base.js",
+ "files": ["base.js", "react.js"],
+ "dependencies": {
+ "@eslint/js": "^9.18.0",
+ "eslint": "^9.18.0",
+ "eslint-plugin-import": "^2.31.0",
+ "eslint-plugin-jsx-a11y": "^6.10.2",
+ "eslint-plugin-react": "^7.37.3",
+ "eslint-plugin-react-hooks": "^5.1.0",
+ "typescript-eslint": "^8.19.1"
+ }
}
diff --git a/packages/config/eslint/react.js b/packages/config/eslint/react.js
index 7fa28a8..6c975b1 100644
--- a/packages/config/eslint/react.js
+++ b/packages/config/eslint/react.js
@@ -1,28 +1,28 @@
-import jsxA11yPlugin from "eslint-plugin-jsx-a11y";
-import reactPlugin from "eslint-plugin-react";
-import hooksPlugin from "eslint-plugin-react-hooks";
-import baseConfig from "./base.js";
+import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
+import reactPlugin from 'eslint-plugin-react';
+import hooksPlugin from 'eslint-plugin-react-hooks';
+import baseConfig from './base.js';
export default [
- ...baseConfig,
- {
- files: ["**/*.{js,jsx,ts,tsx}"],
- plugins: {
- react: reactPlugin,
- "react-hooks": hooksPlugin,
- "jsx-a11y": jsxA11yPlugin,
- },
- rules: {
- ...reactPlugin.configs.recommended.rules,
- ...hooksPlugin.configs.recommended.rules,
- ...jsxA11yPlugin.configs.recommended.rules,
- "react/react-in-jsx-scope": "off", // Not needed in Next.js/React 17+
- "react/prop-types": "off", // Using TypeScript
- },
- settings: {
- react: {
- version: "detect",
- },
- },
- },
+ ...baseConfig,
+ {
+ files: ['**/*.{js,jsx,ts,tsx}'],
+ plugins: {
+ react: reactPlugin,
+ 'react-hooks': hooksPlugin,
+ 'jsx-a11y': jsxA11yPlugin,
+ },
+ rules: {
+ ...reactPlugin.configs.recommended.rules,
+ ...hooksPlugin.configs.recommended.rules,
+ ...jsxA11yPlugin.configs.recommended.rules,
+ 'react/react-in-jsx-scope': 'off', // Not needed in Next.js/React 17+
+ 'react/prop-types': 'off', // Using TypeScript
+ },
+ settings: {
+ react: {
+ version: 'detect',
+ },
+ },
+ },
];
diff --git a/packages/config/package.json b/packages/config/package.json
index 6b0c2d1..4a38b4d 100644
--- a/packages/config/package.json
+++ b/packages/config/package.json
@@ -2,10 +2,5 @@
"name": "@tpmjs/config",
"version": "0.0.0",
"private": true,
- "files": [
- "biome.json",
- "eslint",
- "tailwind",
- "tsconfig"
- ]
+ "files": ["biome.json", "eslint", "tailwind", "tsconfig"]
}
diff --git a/packages/config/tailwind/base.ts b/packages/config/tailwind/base.ts
index 2aed1a2..d70fc7b 100644
--- a/packages/config/tailwind/base.ts
+++ b/packages/config/tailwind/base.ts
@@ -1,185 +1,184 @@
-import type { Config } from "tailwindcss";
+import type { Config } from 'tailwindcss';
export default {
- content: [],
- safelist: [
- // Custom border utilities
- "border-dotted-1",
- "border-dotted-2",
- "border-dashed-1",
- "border-t-dotted",
- "border-b-dotted",
- "border-l-dotted",
- "border-r-dotted",
- // Custom shadow utilities
- "shadow-blueprint",
- "shadow-blueprint-hover",
- // Grid backgrounds
- "dotted-grid-background",
- "blueprint-background",
- "grid-background",
- ],
- theme: {
- extend: {
- colors: {
- // Backgrounds & Surfaces
- background: "hsl(var(--background))",
- surface: "hsl(var(--surface))",
- "surface-elevated": "hsl(var(--surface-elevated))",
- "surface-overlay": "hsl(var(--surface-overlay))",
+ content: [],
+ safelist: [
+ // Custom border utilities
+ 'border-dotted-1',
+ 'border-dotted-2',
+ 'border-dashed-1',
+ 'border-t-dotted',
+ 'border-b-dotted',
+ 'border-l-dotted',
+ 'border-r-dotted',
+ // Custom shadow utilities
+ 'shadow-blueprint',
+ 'shadow-blueprint-hover',
+ // Grid backgrounds
+ 'dotted-grid-background',
+ 'blueprint-background',
+ 'grid-background',
+ ],
+ theme: {
+ extend: {
+ colors: {
+ // Backgrounds & Surfaces
+ background: 'hsl(var(--background))',
+ surface: 'hsl(var(--surface))',
+ 'surface-elevated': 'hsl(var(--surface-elevated))',
+ 'surface-overlay': 'hsl(var(--surface-overlay))',
- // Foreground (Text)
- foreground: {
- DEFAULT: "hsl(var(--foreground))",
- secondary: "hsl(var(--foreground-secondary))",
- tertiary: "hsl(var(--foreground-tertiary))",
- muted: "hsl(var(--foreground-muted))",
- },
+ // Foreground (Text)
+ foreground: {
+ DEFAULT: 'hsl(var(--foreground))',
+ secondary: 'hsl(var(--foreground-secondary))',
+ tertiary: 'hsl(var(--foreground-tertiary))',
+ muted: 'hsl(var(--foreground-muted))',
+ },
- // Borders
- border: {
- DEFAULT: "hsl(var(--border))",
- strong: "hsl(var(--border-strong))",
- subtle: "hsl(var(--border-subtle))",
- },
+ // Borders
+ border: {
+ DEFAULT: 'hsl(var(--border))',
+ strong: 'hsl(var(--border-strong))',
+ subtle: 'hsl(var(--border-subtle))',
+ },
- // Interactive States
- primary: {
- DEFAULT: "hsl(var(--primary))",
- foreground: "hsl(var(--primary-foreground))",
- },
- secondary: {
- DEFAULT: "hsl(var(--secondary))",
- foreground: "hsl(var(--secondary-foreground))",
- },
- accent: {
- DEFAULT: "hsl(var(--accent))",
- foreground: "hsl(var(--accent-foreground))",
- },
- muted: {
- DEFAULT: "hsl(var(--muted))",
- foreground: "hsl(var(--muted-foreground))",
- },
+ // Interactive States
+ primary: {
+ DEFAULT: 'hsl(var(--primary))',
+ foreground: 'hsl(var(--primary-foreground))',
+ },
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary))',
+ foreground: 'hsl(var(--secondary-foreground))',
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent))',
+ foreground: 'hsl(var(--accent-foreground))',
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted))',
+ foreground: 'hsl(var(--muted-foreground))',
+ },
- // Status Colors
- success: {
- DEFAULT: "hsl(var(--success))",
- foreground: "hsl(var(--success-foreground))",
- },
- error: {
- DEFAULT: "hsl(var(--error))",
- foreground: "hsl(var(--error-foreground))",
- },
- warning: {
- DEFAULT: "hsl(var(--warning))",
- foreground: "hsl(var(--warning-foreground))",
- },
- info: {
- DEFAULT: "hsl(var(--info))",
- foreground: "hsl(var(--info-foreground))",
- },
+ // Status Colors
+ success: {
+ DEFAULT: 'hsl(var(--success))',
+ foreground: 'hsl(var(--success-foreground))',
+ },
+ error: {
+ DEFAULT: 'hsl(var(--error))',
+ foreground: 'hsl(var(--error-foreground))',
+ },
+ warning: {
+ DEFAULT: 'hsl(var(--warning))',
+ foreground: 'hsl(var(--warning-foreground))',
+ },
+ info: {
+ DEFAULT: 'hsl(var(--info))',
+ foreground: 'hsl(var(--info-foreground))',
+ },
- // Destructive (legacy support)
- destructive: {
- DEFAULT: "hsl(var(--destructive))",
- foreground: "hsl(var(--destructive-foreground))",
- },
+ // Destructive (legacy support)
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive))',
+ foreground: 'hsl(var(--destructive-foreground))',
+ },
- // Form Elements
- input: "hsl(var(--input))",
- ring: "hsl(var(--ring))",
+ // Form Elements
+ input: 'hsl(var(--input))',
+ ring: 'hsl(var(--ring))',
- // Card
- card: {
- DEFAULT: "hsl(var(--card))",
- foreground: "hsl(var(--card-foreground))",
- },
+ // Card
+ card: {
+ DEFAULT: 'hsl(var(--card))',
+ foreground: 'hsl(var(--card-foreground))',
+ },
- // Grid/Blueprint
- grid: "hsl(var(--grid-color))",
- },
+ // Grid/Blueprint
+ grid: 'hsl(var(--grid-color))',
+ },
- borderRadius: {
- sm: "var(--radius-sm)",
- md: "var(--radius-md)",
- lg: "var(--radius-lg)",
- xl: "var(--radius-xl)",
- },
+ borderRadius: {
+ sm: 'var(--radius-sm)',
+ md: 'var(--radius-md)',
+ lg: 'var(--radius-lg)',
+ xl: 'var(--radius-xl)',
+ },
- fontFamily: {
- sans: "var(--font-sans)",
- mono: "var(--font-mono)",
- },
+ fontFamily: {
+ sans: 'var(--font-sans)',
+ mono: 'var(--font-mono)',
+ },
+ lineHeight: {
+ tight: 'var(--leading-tight)',
+ snug: 'var(--leading-snug)',
+ normal: 'var(--leading-normal)',
+ relaxed: 'var(--leading-relaxed)',
+ loose: 'var(--leading-loose)',
+ editorial: 'var(--leading-editorial)',
+ },
- lineHeight: {
- tight: "var(--leading-tight)",
- snug: "var(--leading-snug)",
- normal: "var(--leading-normal)",
- relaxed: "var(--leading-relaxed)",
- loose: "var(--leading-loose)",
- editorial: "var(--leading-editorial)",
- },
+ letterSpacing: {
+ tighter: 'var(--tracking-tighter)',
+ tight: 'var(--tracking-tight)',
+ normal: 'var(--tracking-normal)',
+ wide: 'var(--tracking-wide)',
+ wider: 'var(--tracking-wider)',
+ widest: 'var(--tracking-widest)',
+ },
+ keyframes: {
+ 'slide-up': {
+ from: { transform: 'translateY(10px)', opacity: '0' },
+ to: { transform: 'translateY(0)', opacity: '1' },
+ },
+ 'slide-down': {
+ from: { transform: 'translateY(-10px)', opacity: '0' },
+ to: { transform: 'translateY(0)', opacity: '1' },
+ },
+ 'slide-left': {
+ from: { transform: 'translateX(10px)', opacity: '0' },
+ to: { transform: 'translateX(0)', opacity: '1' },
+ },
+ 'slide-right': {
+ from: { transform: 'translateX(-10px)', opacity: '0' },
+ to: { transform: 'translateX(0)', opacity: '1' },
+ },
+ 'scale-in': {
+ from: { transform: 'scale(0.95)', opacity: '0' },
+ to: { transform: 'scale(1)', opacity: '1' },
+ },
+ 'scale-out': {
+ from: { transform: 'scale(1)', opacity: '1' },
+ to: { transform: 'scale(0.95)', opacity: '0' },
+ },
+ shimmer: {
+ '0%': { backgroundPosition: '-1000px 0' },
+ '100%': { backgroundPosition: '1000px 0' },
+ },
+ pulse: {
+ '0%, 100%': { opacity: '1' },
+ '50%': { opacity: '0.5' },
+ },
+ 'grid-pulse': {
+ '0%, 100%': { opacity: '0.4' },
+ '50%': { opacity: '0.6' },
+ },
+ },
- letterSpacing: {
- tighter: "var(--tracking-tighter)",
- tight: "var(--tracking-tight)",
- normal: "var(--tracking-normal)",
- wide: "var(--tracking-wide)",
- wider: "var(--tracking-wider)",
- widest: "var(--tracking-widest)",
- },
- keyframes: {
- "slide-up": {
- from: { transform: "translateY(10px)", opacity: "0" },
- to: { transform: "translateY(0)", opacity: "1" },
- },
- "slide-down": {
- from: { transform: "translateY(-10px)", opacity: "0" },
- to: { transform: "translateY(0)", opacity: "1" },
- },
- "slide-left": {
- from: { transform: "translateX(10px)", opacity: "0" },
- to: { transform: "translateX(0)", opacity: "1" },
- },
- "slide-right": {
- from: { transform: "translateX(-10px)", opacity: "0" },
- to: { transform: "translateX(0)", opacity: "1" },
- },
- "scale-in": {
- from: { transform: "scale(0.95)", opacity: "0" },
- to: { transform: "scale(1)", opacity: "1" },
- },
- "scale-out": {
- from: { transform: "scale(1)", opacity: "1" },
- to: { transform: "scale(0.95)", opacity: "0" },
- },
- shimmer: {
- "0%": { backgroundPosition: "-1000px 0" },
- "100%": { backgroundPosition: "1000px 0" },
- },
- pulse: {
- "0%, 100%": { opacity: "1" },
- "50%": { opacity: "0.5" },
- },
- "grid-pulse": {
- "0%, 100%": { opacity: "0.4" },
- "50%": { opacity: "0.6" },
- },
- },
-
- animation: {
- "slide-up": "slide-up 300ms cubic-bezier(0.4, 0, 0.2, 1)",
- "slide-down": "slide-down 300ms cubic-bezier(0.4, 0, 0.2, 1)",
- "slide-left": "slide-left 300ms cubic-bezier(0.4, 0, 0.2, 1)",
- "slide-right": "slide-right 300ms cubic-bezier(0.4, 0, 0.2, 1)",
- "scale-in": "scale-in 200ms cubic-bezier(0.4, 0, 0.2, 1)",
- "scale-out": "scale-out 200ms cubic-bezier(0.4, 0, 0.2, 1)",
- shimmer: "shimmer 2s infinite linear",
- pulse: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",
- "grid-pulse": "grid-pulse 8s ease-in-out infinite",
- },
- },
- },
- plugins: [],
+ animation: {
+ 'slide-up': 'slide-up 300ms cubic-bezier(0.4, 0, 0.2, 1)',
+ 'slide-down': 'slide-down 300ms cubic-bezier(0.4, 0, 0.2, 1)',
+ 'slide-left': 'slide-left 300ms cubic-bezier(0.4, 0, 0.2, 1)',
+ 'slide-right': 'slide-right 300ms cubic-bezier(0.4, 0, 0.2, 1)',
+ 'scale-in': 'scale-in 200ms cubic-bezier(0.4, 0, 0.2, 1)',
+ 'scale-out': 'scale-out 200ms cubic-bezier(0.4, 0, 0.2, 1)',
+ shimmer: 'shimmer 2s infinite linear',
+ pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
+ 'grid-pulse': 'grid-pulse 8s ease-in-out infinite',
+ },
+ },
+ },
+ plugins: [],
} satisfies Config;
diff --git a/packages/config/tailwind/package.json b/packages/config/tailwind/package.json
index f735efb..5a86024 100644
--- a/packages/config/tailwind/package.json
+++ b/packages/config/tailwind/package.json
@@ -3,9 +3,7 @@
"version": "0.0.0",
"private": true,
"main": "./base.ts",
- "files": [
- "base.ts"
- ],
+ "files": ["base.ts"],
"dependencies": {
"tailwindcss": "^3.4.17"
},
diff --git a/packages/config/tsconfig/base.json b/packages/config/tsconfig/base.json
index d8ce03d..9c8e189 100644
--- a/packages/config/tsconfig/base.json
+++ b/packages/config/tsconfig/base.json
@@ -20,9 +20,5 @@
"target": "ES2022",
"verbatimModuleSyntax": true
},
- "exclude": [
- "node_modules",
- "dist",
- ".turbo"
- ]
+ "exclude": ["node_modules", "dist", ".turbo"]
}
diff --git a/packages/config/tsconfig/nextjs.json b/packages/config/tsconfig/nextjs.json
index df2d563..aa17949 100644
--- a/packages/config/tsconfig/nextjs.json
+++ b/packages/config/tsconfig/nextjs.json
@@ -12,13 +12,6 @@
],
"target": "ES2022"
},
- "include": [
- "next-env.d.ts",
- "**/*.ts",
- "**/*.tsx",
- ".next/types/**/*.ts"
- ],
- "exclude": [
- "node_modules"
- ]
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
}
diff --git a/packages/config/tsconfig/package.json b/packages/config/tsconfig/package.json
index a29e5be..2e3c20e 100644
--- a/packages/config/tsconfig/package.json
+++ b/packages/config/tsconfig/package.json
@@ -2,9 +2,5 @@
"name": "@tpmjs/tsconfig",
"version": "0.0.0",
"private": true,
- "files": [
- "base.json",
- "nextjs.json",
- "react-library.json"
- ]
+ "files": ["base.json", "nextjs.json", "react-library.json"]
}
diff --git a/packages/env/package.json b/packages/env/package.json
index e605993..ed29ba4 100644
--- a/packages/env/package.json
+++ b/packages/env/package.json
@@ -8,9 +8,7 @@
"default": "./dist/index.js"
}
},
- "files": [
- "dist"
- ],
+ "files": ["dist"],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
diff --git a/packages/mocks/package.json b/packages/mocks/package.json
index 9f59ac8..fe76237 100644
--- a/packages/mocks/package.json
+++ b/packages/mocks/package.json
@@ -13,9 +13,7 @@
"default": "./dist/handlers.js"
}
},
- "files": [
- "dist"
- ],
+ "files": ["dist"],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
diff --git a/packages/storybook/.storybook/main.ts b/packages/storybook/.storybook/main.ts
index 056cfd0..4536331 100644
--- a/packages/storybook/.storybook/main.ts
+++ b/packages/storybook/.storybook/main.ts
@@ -1,19 +1,19 @@
-import type { StorybookConfig } from "@storybook/react-vite";
+import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
- stories: ["../stories/**/*.stories.@(ts|tsx)"],
- addons: [
- "@storybook/addon-links",
- "@storybook/addon-essentials",
- "@storybook/addon-interactions",
- ],
- framework: {
- name: "@storybook/react-vite",
- options: {},
- },
- docs: {
- autodocs: "tag",
- },
+ stories: ['../stories/**/*.stories.@(ts|tsx)'],
+ addons: [
+ '@storybook/addon-links',
+ '@storybook/addon-essentials',
+ '@storybook/addon-interactions',
+ ],
+ framework: {
+ name: '@storybook/react-vite',
+ options: {},
+ },
+ docs: {
+ autodocs: 'tag',
+ },
};
export default config;
diff --git a/packages/storybook/.storybook/preview.ts b/packages/storybook/.storybook/preview.ts
index e2f735d..7747a5a 100644
--- a/packages/storybook/.storybook/preview.ts
+++ b/packages/storybook/.storybook/preview.ts
@@ -1,15 +1,15 @@
-import type { Preview } from "@storybook/react";
-import "../styles/globals.css";
+import type { Preview } from '@storybook/react';
+import '../styles/globals.css';
const preview: Preview = {
- parameters: {
- controls: {
- matchers: {
- color: /(background|color)$/i,
- date: /Date$/i,
- },
- },
- },
+ parameters: {
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/i,
+ },
+ },
+ },
};
export default preview;
diff --git a/packages/storybook/package.json b/packages/storybook/package.json
index 4b1520b..b98db15 100644
--- a/packages/storybook/package.json
+++ b/packages/storybook/package.json
@@ -1,33 +1,33 @@
{
- "name": "@tpmjs/storybook",
- "version": "0.0.0",
- "private": true,
- "type": "module",
- "scripts": {
- "dev": "storybook dev -p 6006",
- "build": "storybook build",
- "clean": "rm -rf storybook-static .turbo"
- },
- "dependencies": {
- "@tpmjs/ui": "workspace:*",
- "react": "^19.0.0",
- "react-dom": "^19.0.0"
- },
- "devDependencies": {
- "@storybook/addon-essentials": "^8.5.0",
- "@storybook/addon-interactions": "^8.5.0",
- "@storybook/addon-links": "^8.5.0",
- "@storybook/blocks": "^8.5.0",
- "@storybook/react": "^8.5.0",
- "@storybook/react-vite": "^8.5.0",
- "@storybook/test": "^8.5.0",
- "@tpmjs/tailwind-config": "workspace:*",
- "@types/react": "^19.0.2",
- "@types/react-dom": "^19.0.2",
- "autoprefixer": "^10.4.20",
- "postcss": "^8.5.1",
- "storybook": "^8.5.0",
- "tailwindcss": "^3.4.17",
- "vite": "^6.0.7"
- }
+ "name": "@tpmjs/storybook",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "storybook dev -p 6006",
+ "build": "storybook build",
+ "clean": "rm -rf storybook-static .turbo"
+ },
+ "dependencies": {
+ "@tpmjs/ui": "workspace:*",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "@storybook/addon-essentials": "^8.5.0",
+ "@storybook/addon-interactions": "^8.5.0",
+ "@storybook/addon-links": "^8.5.0",
+ "@storybook/blocks": "^8.5.0",
+ "@storybook/react": "^8.5.0",
+ "@storybook/react-vite": "^8.5.0",
+ "@storybook/test": "^8.5.0",
+ "@tpmjs/tailwind-config": "workspace:*",
+ "@types/react": "^19.0.2",
+ "@types/react-dom": "^19.0.2",
+ "autoprefixer": "^10.4.20",
+ "postcss": "^8.5.1",
+ "storybook": "^8.5.0",
+ "tailwindcss": "^3.4.17",
+ "vite": "^6.0.7"
+ }
}
diff --git a/packages/storybook/stories/Badge.stories.ts b/packages/storybook/stories/Badge.stories.ts
index 91704eb..a5f9ebd 100644
--- a/packages/storybook/stories/Badge.stories.ts
+++ b/packages/storybook/stories/Badge.stories.ts
@@ -1,54 +1,53 @@
-import type { Meta, StoryObj } from "@storybook/react";
-import { Badge } from "@tpmjs/ui/Badge/Badge";
-import { createElement } from "react";
+import type { Meta, StoryObj } from '@storybook/react';
+import { Badge } from '@tpmjs/ui/Badge/Badge';
+import { createElement } from 'react';
const meta = {
- title: "Components/Badge",
- component: Badge,
- tags: ["autodocs"],
- parameters: {
- layout: "centered",
- },
+ title: 'Components/Badge',
+ component: Badge,
+ tags: ['autodocs'],
+ parameters: {
+ layout: 'centered',
+ },
} satisfies Meta;
export default meta;
type Story = StoryObj;
export const Default: Story = {
- render: () => createElement(Badge, {}, "Default"),
+ render: () => createElement(Badge, {}, 'Default'),
};
export const AllVariants: Story = {
- render: () =>
- createElement(
- "div",
- {
- className: "flex flex-wrap items-center gap-3",
- },
- createElement(Badge, { variant: "default" }, "Default"),
- createElement(Badge, { variant: "secondary" }, "Secondary"),
- createElement(Badge, { variant: "outline" }, "Outline"),
- createElement(Badge, { variant: "success" }, "Success"),
- createElement(Badge, { variant: "error" }, "Error"),
- createElement(Badge, { variant: "warning" }, "Warning"),
- createElement(Badge, { variant: "info" }, "Info"),
- ),
+ render: () =>
+ createElement(
+ 'div',
+ {
+ className: 'flex flex-wrap items-center gap-3',
+ },
+ createElement(Badge, { variant: 'default' }, 'Default'),
+ createElement(Badge, { variant: 'secondary' }, 'Secondary'),
+ createElement(Badge, { variant: 'outline' }, 'Outline'),
+ createElement(Badge, { variant: 'success' }, 'Success'),
+ createElement(Badge, { variant: 'error' }, 'Error'),
+ createElement(Badge, { variant: 'warning' }, 'Warning'),
+ createElement(Badge, { variant: 'info' }, 'Info')
+ ),
};
export const AllSizes: Story = {
- render: () =>
- createElement(
- "div",
- {
- className: "flex items-center gap-3",
- },
- createElement(Badge, { size: "sm" }, "Small"),
- createElement(Badge, { size: "md" }, "Medium"),
- createElement(Badge, { size: "lg" }, "Large"),
- ),
+ render: () =>
+ createElement(
+ 'div',
+ {
+ className: 'flex items-center gap-3',
+ },
+ createElement(Badge, { size: 'sm' }, 'Small'),
+ createElement(Badge, { size: 'md' }, 'Medium'),
+ createElement(Badge, { size: 'lg' }, 'Large')
+ ),
};
export const WithLongText: Story = {
- render: () =>
- createElement(Badge, {}, "This is a very long badge with lots of text"),
+ render: () => createElement(Badge, {}, 'This is a very long badge with lots of text'),
};
diff --git a/packages/storybook/stories/Button.stories.ts b/packages/storybook/stories/Button.stories.ts
index e1f756f..63cde48 100644
--- a/packages/storybook/stories/Button.stories.ts
+++ b/packages/storybook/stories/Button.stories.ts
@@ -1,69 +1,69 @@
-import type { Meta, StoryObj } from "@storybook/react";
-import { Button } from "@tpmjs/ui/Button/Button";
+import type { Meta, StoryObj } from '@storybook/react';
+import { Button } from '@tpmjs/ui/Button/Button';
const meta = {
- title: "Components/Button",
- component: Button,
- parameters: {
- layout: "centered",
- },
- tags: ["autodocs"],
+ title: 'Components/Button',
+ component: Button,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
} satisfies Meta;
export default meta;
type Story = StoryObj;
export const Default: Story = {
- args: {
- children: "Button",
- },
+ args: {
+ children: 'Button',
+ },
};
export const Destructive: Story = {
- args: {
- variant: "destructive",
- children: "Delete",
- },
+ args: {
+ variant: 'destructive',
+ children: 'Delete',
+ },
};
export const Outline: Story = {
- args: {
- variant: "outline",
- children: "Outline",
- },
+ args: {
+ variant: 'outline',
+ children: 'Outline',
+ },
};
export const Secondary: Story = {
- args: {
- variant: "secondary",
- children: "Secondary",
- },
+ args: {
+ variant: 'secondary',
+ children: 'Secondary',
+ },
};
export const Ghost: Story = {
- args: {
- variant: "ghost",
- children: "Ghost",
- },
+ args: {
+ variant: 'ghost',
+ children: 'Ghost',
+ },
};
export const Link: Story = {
- args: {
- variant: "link",
- children: "Link",
- },
+ args: {
+ variant: 'link',
+ children: 'Link',
+ },
};
export const Large: Story = {
- args: {
- size: "lg",
- children: "Large Button",
- },
+ args: {
+ size: 'lg',
+ children: 'Large Button',
+ },
};
export const Small: Story = {
- args: {
- size: "sm",
- children: "Small Button",
- },
+ args: {
+ size: 'sm',
+ children: 'Small Button',
+ },
};
diff --git a/packages/storybook/stories/Card.stories.ts b/packages/storybook/stories/Card.stories.ts
index 37caf23..98cd46d 100644
--- a/packages/storybook/stories/Card.stories.ts
+++ b/packages/storybook/stories/Card.stories.ts
@@ -1,39 +1,39 @@
-import type { Meta, StoryObj } from "@storybook/react";
-import { Card, CardContent, CardHeader, CardTitle } from "@tpmjs/ui/Card/Card";
-import { createElement } from "react";
+import type { Meta, StoryObj } from '@storybook/react';
+import { Card, CardContent, CardHeader, CardTitle } from '@tpmjs/ui/Card/Card';
+import { createElement } from 'react';
const meta = {
- title: "Components/Card",
- component: Card,
- parameters: {
- layout: "centered",
- },
- tags: ["autodocs"],
+ title: 'Components/Card',
+ component: Card,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
} satisfies Meta;
export default meta;
type Story = StoryObj;
export const Default: Story = {
- render: () =>
- createElement(
- Card,
- { className: "w-[350px]" },
- createElement(CardHeader, {}, createElement(CardTitle, {}, "Card Title")),
- createElement(CardContent, {}, "This is the card content."),
- ),
+ render: () =>
+ createElement(
+ Card,
+ { className: 'w-[350px]' },
+ createElement(CardHeader, {}, createElement(CardTitle, {}, 'Card Title')),
+ createElement(CardContent, {}, 'This is the card content.')
+ ),
};
export const WithLongContent: Story = {
- render: () =>
- createElement(
- Card,
- { className: "w-[400px]" },
- createElement(CardHeader, {}, createElement(CardTitle, {}, "TPMJS")),
- createElement(
- CardContent,
- {},
- "Tool Package Manager for AI Agents. The registry for AI tools. Discover, share, and integrate tools that give your agents superpowers.",
- ),
- ),
+ render: () =>
+ createElement(
+ Card,
+ { className: 'w-[400px]' },
+ createElement(CardHeader, {}, createElement(CardTitle, {}, 'TPMJS')),
+ createElement(
+ CardContent,
+ {},
+ 'Tool Package Manager for AI Agents. The registry for AI tools. Discover, share, and integrate tools that give your agents superpowers.'
+ )
+ ),
};
diff --git a/packages/storybook/stories/CodeBlock.stories.ts b/packages/storybook/stories/CodeBlock.stories.ts
index d0e8008..e7dcfda 100644
--- a/packages/storybook/stories/CodeBlock.stories.ts
+++ b/packages/storybook/stories/CodeBlock.stories.ts
@@ -1,38 +1,38 @@
-import type { Meta, StoryObj } from "@storybook/react";
-import { CodeBlock } from "@tpmjs/ui/CodeBlock/CodeBlock";
-import { createElement } from "react";
+import type { Meta, StoryObj } from '@storybook/react';
+import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
+import { createElement } from 'react';
const meta = {
- title: "Components/CodeBlock",
- component: CodeBlock,
- tags: ["autodocs"],
- parameters: {
- layout: "centered",
- },
+ title: 'Components/CodeBlock',
+ component: CodeBlock,
+ tags: ['autodocs'],
+ parameters: {
+ layout: 'centered',
+ },
} satisfies Meta;
export default meta;
type Story = StoryObj;
export const Default: Story = {
- render: () =>
- createElement(CodeBlock, {
- code: `console.log('Hello, World!');`,
- }),
+ render: () =>
+ createElement(CodeBlock, {
+ code: `console.log('Hello, World!');`,
+ }),
};
export const BashCommand: Story = {
- render: () =>
- createElement(CodeBlock, {
- code: "npm install @tpmjs/ui",
- language: "bash",
- }),
+ render: () =>
+ createElement(CodeBlock, {
+ code: 'npm install @tpmjs/ui',
+ language: 'bash',
+ }),
};
export const TypeScriptCode: Story = {
- render: () =>
- createElement(CodeBlock, {
- code: `import { Button } from '@tpmjs/ui/Button/Button';
+ render: () =>
+ createElement(CodeBlock, {
+ code: `import { Button } from '@tpmjs/ui/Button/Button';
import { createElement } from 'react';
export function MyComponent() {
@@ -41,29 +41,29 @@ export function MyComponent() {
onClick: () => console.log('clicked'),
}, 'Click me');
}`,
- language: "typescript",
- }),
+ language: 'typescript',
+ }),
};
export const JSONData: Story = {
- render: () =>
- createElement(CodeBlock, {
- code: `{
+ render: () =>
+ createElement(CodeBlock, {
+ code: `{
"name": "@tpmjs/ui",
"version": "0.0.1",
"description": "UI components for TPMJS"
}`,
- language: "json",
- }),
+ language: 'json',
+ }),
};
export const LongCode: Story = {
- render: () =>
- createElement(
- "div",
- { className: "w-96" },
- createElement(CodeBlock, {
- code: `export const buttonVariants = createVariants({
+ render: () =>
+ createElement(
+ 'div',
+ { className: 'w-96' },
+ createElement(CodeBlock, {
+ code: `export const buttonVariants = createVariants({
base: 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2',
variants: {
variant: {
@@ -73,7 +73,7 @@ export const LongCode: Story = {
},
},
});`,
- language: "typescript",
- }),
- ),
+ language: 'typescript',
+ })
+ ),
};
diff --git a/packages/storybook/stories/Icon.stories.tsx b/packages/storybook/stories/Icon.stories.tsx
index 9b8acfd..b67000e 100644
--- a/packages/storybook/stories/Icon.stories.tsx
+++ b/packages/storybook/stories/Icon.stories.tsx
@@ -1,62 +1,53 @@
-import type { Meta, StoryObj } from "@storybook/react";
-import { Icon } from "@tpmjs/ui/Icon/Icon";
+import type { Meta, StoryObj } from '@storybook/react';
+import { Icon } from '@tpmjs/ui/Icon/Icon';
const meta = {
- title: "Components/Icon",
- component: Icon,
- tags: ["autodocs"],
- parameters: {
- layout: "centered",
- },
+ title: 'Components/Icon',
+ component: Icon,
+ tags: ['autodocs'],
+ parameters: {
+ layout: 'centered',
+ },
} satisfies Meta;
export default meta;
type Story = StoryObj;
export const AllIcons: Story = {
- render: () => (
-
- {(
- [
- "copy",
- "externalLink",
- "github",
- "check",
- "x",
- "chevronDown",
- "sun",
- "moon",
- ] as const
- ).map((iconName) => (
-
-
- {iconName}
-
- ))}
-
- ),
+ render: () => (
+
+ {(
+ ['copy', 'externalLink', 'github', 'check', 'x', 'chevronDown', 'sun', 'moon'] as const
+ ).map((iconName) => (
+
+
+ {iconName}
+
+ ))}
+
+ ),
};
export const AllSizes: Story = {
- render: () => (
-
- {(["sm", "md", "lg"] as const).map((size) => (
-
-
- {size}
-
- ))}
-
- ),
+ render: () => (
+
+ {(['sm', 'md', 'lg'] as const).map((size) => (
+
+
+ {size}
+
+ ))}
+
+ ),
};
export const WithColors: Story = {
- render: () => (
-
-
-
-
-
-
- ),
+ render: () => (
+
+
+
+
+
+
+ ),
};
diff --git a/packages/storybook/stories/Input.stories.ts b/packages/storybook/stories/Input.stories.ts
index 8d5f474..2e5ec98 100644
--- a/packages/storybook/stories/Input.stories.ts
+++ b/packages/storybook/stories/Input.stories.ts
@@ -1,94 +1,94 @@
-import type { Meta, StoryObj } from "@storybook/react";
-import { Input } from "@tpmjs/ui/Input/Input";
-import { Label } from "@tpmjs/ui/Label/Label";
-import { createElement } from "react";
+import type { Meta, StoryObj } from '@storybook/react';
+import { Input } from '@tpmjs/ui/Input/Input';
+import { Label } from '@tpmjs/ui/Label/Label';
+import { createElement } from 'react';
const meta = {
- title: "Components/Input",
- component: Input,
- tags: ["autodocs"],
- parameters: {
- layout: "centered",
- },
+ title: 'Components/Input',
+ component: Input,
+ tags: ['autodocs'],
+ parameters: {
+ layout: 'centered',
+ },
} satisfies Meta;
export default meta;
type Story = StoryObj;
export const Default: Story = {
- render: () => createElement(Input, { placeholder: "Enter text..." }),
+ render: () => createElement(Input, { placeholder: 'Enter text...' }),
};
export const WithLabel: Story = {
- render: () =>
- createElement(
- "div",
- { className: "flex flex-col gap-2 w-80" },
- createElement(Label, { htmlFor: "email" }, "Email address"),
- createElement(Input, {
- id: "email",
- type: "email",
- placeholder: "name@example.com",
- }),
- ),
+ render: () =>
+ createElement(
+ 'div',
+ { className: 'flex flex-col gap-2 w-80' },
+ createElement(Label, { htmlFor: 'email' }, 'Email address'),
+ createElement(Input, {
+ id: 'email',
+ type: 'email',
+ placeholder: 'name@example.com',
+ })
+ ),
};
export const AllStates: Story = {
- render: () =>
- createElement(
- "div",
- { className: "flex flex-col gap-4 w-80" },
- createElement(
- "div",
- { className: "flex flex-col gap-2" },
- createElement(Label, {}, "Normal"),
- createElement(Input, { placeholder: "Normal state" }),
- ),
- createElement(
- "div",
- { className: "flex flex-col gap-2" },
- createElement(Label, {}, "Success"),
- createElement(Input, {
- placeholder: "Success state",
- state: "success",
- }),
- ),
- createElement(
- "div",
- { className: "flex flex-col gap-2" },
- createElement(Label, {}, "Error"),
- createElement(Input, { placeholder: "Error state", state: "error" }),
- ),
- createElement(
- "div",
- { className: "flex flex-col gap-2" },
- createElement(Label, {}, "Disabled"),
- createElement(Input, { placeholder: "Disabled state", disabled: true }),
- ),
- ),
+ render: () =>
+ createElement(
+ 'div',
+ { className: 'flex flex-col gap-4 w-80' },
+ createElement(
+ 'div',
+ { className: 'flex flex-col gap-2' },
+ createElement(Label, {}, 'Normal'),
+ createElement(Input, { placeholder: 'Normal state' })
+ ),
+ createElement(
+ 'div',
+ { className: 'flex flex-col gap-2' },
+ createElement(Label, {}, 'Success'),
+ createElement(Input, {
+ placeholder: 'Success state',
+ state: 'success',
+ })
+ ),
+ createElement(
+ 'div',
+ { className: 'flex flex-col gap-2' },
+ createElement(Label, {}, 'Error'),
+ createElement(Input, { placeholder: 'Error state', state: 'error' })
+ ),
+ createElement(
+ 'div',
+ { className: 'flex flex-col gap-2' },
+ createElement(Label, {}, 'Disabled'),
+ createElement(Input, { placeholder: 'Disabled state', disabled: true })
+ )
+ ),
};
export const AllSizes: Story = {
- render: () =>
- createElement(
- "div",
- { className: "flex flex-col gap-4 w-80" },
- createElement(Input, { size: "sm", placeholder: "Small size" }),
- createElement(Input, { size: "md", placeholder: "Medium size" }),
- createElement(Input, { size: "lg", placeholder: "Large size" }),
- ),
+ render: () =>
+ createElement(
+ 'div',
+ { className: 'flex flex-col gap-4 w-80' },
+ createElement(Input, { size: 'sm', placeholder: 'Small size' }),
+ createElement(Input, { size: 'md', placeholder: 'Medium size' }),
+ createElement(Input, { size: 'lg', placeholder: 'Large size' })
+ ),
};
export const PasswordInput: Story = {
- render: () =>
- createElement(
- "div",
- { className: "flex flex-col gap-2 w-80" },
- createElement(Label, { htmlFor: "password" }, "Password"),
- createElement(Input, {
- id: "password",
- type: "password",
- placeholder: "Enter password",
- }),
- ),
+ render: () =>
+ createElement(
+ 'div',
+ { className: 'flex flex-col gap-2 w-80' },
+ createElement(Label, { htmlFor: 'password' }, 'Password'),
+ createElement(Input, {
+ id: 'password',
+ type: 'password',
+ placeholder: 'Enter password',
+ })
+ ),
};
diff --git a/packages/storybook/stories/ProgressBar.stories.ts b/packages/storybook/stories/ProgressBar.stories.ts
index 36e570e..152cb42 100644
--- a/packages/storybook/stories/ProgressBar.stories.ts
+++ b/packages/storybook/stories/ProgressBar.stories.ts
@@ -1,74 +1,70 @@
-import type { Meta, StoryObj } from "@storybook/react";
-import { ProgressBar } from "@tpmjs/ui/ProgressBar/ProgressBar";
-import { createElement } from "react";
+import type { Meta, StoryObj } from '@storybook/react';
+import { ProgressBar } from '@tpmjs/ui/ProgressBar/ProgressBar';
+import { createElement } from 'react';
const meta = {
- title: "Components/ProgressBar",
- component: ProgressBar,
- tags: ["autodocs"],
- parameters: {
- layout: "centered",
- },
+ title: 'Components/ProgressBar',
+ component: ProgressBar,
+ tags: ['autodocs'],
+ parameters: {
+ layout: 'centered',
+ },
} satisfies Meta;
export default meta;
type Story = StoryObj;
export const Default: Story = {
- render: () =>
- createElement(
- "div",
- { className: "w-80" },
- createElement(ProgressBar, { value: 50 }),
- ),
+ render: () =>
+ createElement('div', { className: 'w-80' }, createElement(ProgressBar, { value: 50 })),
};
export const AllVariants: Story = {
- render: () =>
- createElement(
- "div",
- { className: "flex flex-col gap-4 w-80" },
- createElement(ProgressBar, { variant: "primary", value: 75 }),
- createElement(ProgressBar, { variant: "success", value: 85 }),
- createElement(ProgressBar, { variant: "warning", value: 60 }),
- createElement(ProgressBar, { variant: "danger", value: 30 }),
- ),
+ render: () =>
+ createElement(
+ 'div',
+ { className: 'flex flex-col gap-4 w-80' },
+ createElement(ProgressBar, { variant: 'primary', value: 75 }),
+ createElement(ProgressBar, { variant: 'success', value: 85 }),
+ createElement(ProgressBar, { variant: 'warning', value: 60 }),
+ createElement(ProgressBar, { variant: 'danger', value: 30 })
+ ),
};
export const AllSizes: Story = {
- render: () =>
- createElement(
- "div",
- { className: "flex flex-col gap-4 w-80" },
- createElement(ProgressBar, { size: "sm", value: 65 }),
- createElement(ProgressBar, { size: "md", value: 65 }),
- createElement(ProgressBar, { size: "lg", value: 65 }),
- ),
+ render: () =>
+ createElement(
+ 'div',
+ { className: 'flex flex-col gap-4 w-80' },
+ createElement(ProgressBar, { size: 'sm', value: 65 }),
+ createElement(ProgressBar, { size: 'md', value: 65 }),
+ createElement(ProgressBar, { size: 'lg', value: 65 })
+ ),
};
export const WithLabel: Story = {
- render: () =>
- createElement(
- "div",
- { className: "w-80" },
- createElement(ProgressBar, { value: 75, showLabel: true }),
- ),
+ render: () =>
+ createElement(
+ 'div',
+ { className: 'w-80' },
+ createElement(ProgressBar, { value: 75, showLabel: true })
+ ),
};
export const ZeroPercent: Story = {
- render: () =>
- createElement(
- "div",
- { className: "w-80" },
- createElement(ProgressBar, { value: 0, showLabel: true }),
- ),
+ render: () =>
+ createElement(
+ 'div',
+ { className: 'w-80' },
+ createElement(ProgressBar, { value: 0, showLabel: true })
+ ),
};
export const HundredPercent: Story = {
- render: () =>
- createElement(
- "div",
- { className: "w-80" },
- createElement(ProgressBar, { value: 100, showLabel: true }),
- ),
+ render: () =>
+ createElement(
+ 'div',
+ { className: 'w-80' },
+ createElement(ProgressBar, { value: 100, showLabel: true })
+ ),
};
diff --git a/packages/storybook/stories/Tabs.stories.ts b/packages/storybook/stories/Tabs.stories.ts
index b412930..019592c 100644
--- a/packages/storybook/stories/Tabs.stories.ts
+++ b/packages/storybook/stories/Tabs.stories.ts
@@ -1,90 +1,90 @@
-import type { Meta, StoryObj } from "@storybook/react";
-import { type Tab, Tabs } from "@tpmjs/ui/Tabs/Tabs";
-import { createElement } from "react";
+import type { Meta, StoryObj } from '@storybook/react';
+import { type Tab, Tabs } from '@tpmjs/ui/Tabs/Tabs';
+import { createElement } from 'react';
const meta = {
- title: "Components/Tabs",
- component: Tabs,
- tags: ["autodocs"],
- parameters: {
- layout: "centered",
- },
+ title: 'Components/Tabs',
+ component: Tabs,
+ tags: ['autodocs'],
+ parameters: {
+ layout: 'centered',
+ },
} satisfies Meta;
export default meta;
type Story = StoryObj;
const mockTabs: Tab[] = [
- { id: "overview", label: "Overview" },
- { id: "usage", label: "Usage" },
- { id: "api", label: "API" },
+ { id: 'overview', label: 'Overview' },
+ { id: 'usage', label: 'Usage' },
+ { id: 'api', label: 'API' },
];
export const Default: Story = {
- render: () =>
- createElement(Tabs, {
- tabs: mockTabs,
- activeTab: "overview",
- onTabChange: (id: string) => console.log("Tab changed:", id),
- }),
+ render: () =>
+ createElement(Tabs, {
+ tabs: mockTabs,
+ activeTab: 'overview',
+ onTabChange: (id: string) => console.log('Tab changed:', id),
+ }),
};
export const AllSizes: Story = {
- render: () =>
- createElement(
- "div",
- { className: "flex flex-col gap-8 w-full" },
- createElement(Tabs, {
- tabs: mockTabs,
- activeTab: "overview",
- size: "sm",
- onTabChange: () => {},
- }),
- createElement(Tabs, {
- tabs: mockTabs,
- activeTab: "overview",
- size: "md",
- onTabChange: () => {},
- }),
- createElement(Tabs, {
- tabs: mockTabs,
- activeTab: "overview",
- size: "lg",
- onTabChange: () => {},
- }),
- ),
+ render: () =>
+ createElement(
+ 'div',
+ { className: 'flex flex-col gap-8 w-full' },
+ createElement(Tabs, {
+ tabs: mockTabs,
+ activeTab: 'overview',
+ size: 'sm',
+ onTabChange: () => {},
+ }),
+ createElement(Tabs, {
+ tabs: mockTabs,
+ activeTab: 'overview',
+ size: 'md',
+ onTabChange: () => {},
+ }),
+ createElement(Tabs, {
+ tabs: mockTabs,
+ activeTab: 'overview',
+ size: 'lg',
+ onTabChange: () => {},
+ })
+ ),
};
export const WithCounts: Story = {
- render: () =>
- createElement(Tabs, {
- tabs: [
- { id: "all", label: "All Tools", count: 1234 },
- { id: "featured", label: "Featured", count: 42 },
- { id: "recent", label: "Recent", count: 18 },
- ],
- activeTab: "all",
- onTabChange: () => {},
- }),
+ render: () =>
+ createElement(Tabs, {
+ tabs: [
+ { id: 'all', label: 'All Tools', count: 1234 },
+ { id: 'featured', label: 'Featured', count: 42 },
+ { id: 'recent', label: 'Recent', count: 18 },
+ ],
+ activeTab: 'all',
+ onTabChange: () => {},
+ }),
};
export const ManyTabs: Story = {
- render: () =>
- createElement(
- "div",
- { className: "w-96" },
- createElement(Tabs, {
- tabs: [
- { id: "tab1", label: "Overview" },
- { id: "tab2", label: "Documentation" },
- { id: "tab3", label: "Examples" },
- { id: "tab4", label: "API Reference" },
- { id: "tab5", label: "Configuration" },
- { id: "tab6", label: "Changelog" },
- { id: "tab7", label: "Contributing" },
- ],
- activeTab: "tab1",
- onTabChange: () => {},
- }),
- ),
+ render: () =>
+ createElement(
+ 'div',
+ { className: 'w-96' },
+ createElement(Tabs, {
+ tabs: [
+ { id: 'tab1', label: 'Overview' },
+ { id: 'tab2', label: 'Documentation' },
+ { id: 'tab3', label: 'Examples' },
+ { id: 'tab4', label: 'API Reference' },
+ { id: 'tab5', label: 'Configuration' },
+ { id: 'tab6', label: 'Changelog' },
+ { id: 'tab7', label: 'Contributing' },
+ ],
+ activeTab: 'tab1',
+ onTabChange: () => {},
+ })
+ ),
};
diff --git a/packages/storybook/styles/globals.css b/packages/storybook/styles/globals.css
index 4254162..e62ea3f 100644
--- a/packages/storybook/styles/globals.css
+++ b/packages/storybook/styles/globals.css
@@ -6,31 +6,31 @@
/* Light mode (default) */
:root {
/* Backgrounds & Surfaces */
- --background: 0 0% 100%; /* Pure white */
- --surface: 210 20% 98%; /* Off-white */
- --surface-elevated: 0 0% 100%; /* White (elevated) */
- --surface-overlay: 0 0% 98%; /* Light gray */
+ --background: 0 0% 100%; /* Pure white */
+ --surface: 210 20% 98%; /* Off-white */
+ --surface-elevated: 0 0% 100%; /* White (elevated) */
+ --surface-overlay: 0 0% 98%; /* Light gray */
/* Foreground (Text) */
- --foreground: 222 47% 11%; /* Almost black */
+ --foreground: 222 47% 11%; /* Almost black */
--foreground-secondary: 215 16% 47%; /* Medium gray */
- --foreground-tertiary: 215 16% 65%; /* Light gray */
- --foreground-muted: 215 16% 75%; /* Very light gray */
+ --foreground-tertiary: 215 16% 65%; /* Light gray */
+ --foreground-muted: 215 16% 75%; /* Very light gray */
/* Borders */
- --border: 214 32% 91%; /* Light gray */
- --border-strong: 214 32% 80%; /* Medium gray */
- --border-subtle: 214 20% 95%; /* Very light gray */
+ --border: 214 32% 91%; /* Light gray */
+ --border-strong: 214 32% 80%; /* Medium gray */
+ --border-subtle: 214 20% 95%; /* Very light gray */
/* Interactive States */
- --primary: 222 47% 11%; /* Dark for light mode */
- --primary-foreground: 210 40% 98%; /* Light text */
- --secondary: 210 40% 96%; /* Light secondary */
+ --primary: 222 47% 11%; /* Dark for light mode */
+ --primary-foreground: 210 40% 98%; /* Light text */
+ --secondary: 210 40% 96%; /* Light secondary */
--secondary-foreground: 222 47% 11%; /* Dark text */
- --accent: 210 40% 96%; /* Accent bg */
- --accent-foreground: 222 47% 11%; /* Accent text */
- --muted: 210 40% 96%; /* Muted bg */
- --muted-foreground: 215 16% 47%; /* Muted text */
+ --accent: 210 40% 96%; /* Accent bg */
+ --accent-foreground: 222 47% 11%; /* Accent text */
+ --muted: 210 40% 96%; /* Muted bg */
+ --muted-foreground: 215 16% 47%; /* Muted text */
/* Status Colors */
--success: 142 71% 45%;
@@ -53,7 +53,7 @@
/* Grid/Blueprint Pattern */
--grid-color: 214 32% 95%;
- --grid-size: 24px; /* Grid cell size */
+ --grid-size: 24px; /* Grid cell size */
/* Card */
--card: 0 0% 100%;
@@ -67,60 +67,60 @@
--radius: var(--radius-md);
/* Typography */
- --font-sans: 'Geist', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
- --font-mono: 'Geist Mono', 'SF Mono', Consolas, monospace;
+ --font-sans: "Geist", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
+ --font-mono: "Geist Mono", "SF Mono", Consolas, monospace;
}
/* Dark mode (opt-in) - Vercel/Cursor/Perplexity aesthetic */
.dark {
/* Backgrounds & Surfaces */
- --background: 210 10% 5%; /* #0d0d0f - Almost black */
- --surface: 210 10% 8%; /* #14141a - Slightly lighter */
- --surface-elevated: 210 10% 12%; /* #1c1c24 - Cards, modals */
- --surface-overlay: 210 10% 16%; /* #25252f - Overlays, popovers */
+ --background: 210 10% 5%; /* #0d0d0f - Almost black */
+ --surface: 210 10% 8%; /* #14141a - Slightly lighter */
+ --surface-elevated: 210 10% 12%; /* #1c1c24 - Cards, modals */
+ --surface-overlay: 210 10% 16%; /* #25252f - Overlays, popovers */
/* Foreground (Text) */
- --foreground: 210 10% 90%; /* #e3e3e8 - Primary text */
+ --foreground: 210 10% 90%; /* #e3e3e8 - Primary text */
--foreground-secondary: 210 8% 60%; /* #949499 - Secondary text */
- --foreground-tertiary: 210 6% 40%; /* #64646a - Tertiary text */
- --foreground-muted: 210 6% 30%; /* #4a4a50 - Muted text */
+ --foreground-tertiary: 210 6% 40%; /* #64646a - Tertiary text */
+ --foreground-muted: 210 6% 30%; /* #4a4a50 - Muted text */
/* Borders (1px, subtle) */
- --border: 210 10% 20%; /* #30303a - Default border */
- --border-strong: 210 12% 30%; /* #494954 - Emphasized border */
- --border-subtle: 210 8% 15%; /* #22222c - Subtle border */
+ --border: 210 10% 20%; /* #30303a - Default border */
+ --border-strong: 210 12% 30%; /* #494954 - Emphasized border */
+ --border-subtle: 210 8% 15%; /* #22222c - Subtle border */
/* Interactive States */
- --primary: 210 10% 98%; /* White-ish for dark mode */
- --primary-foreground: 210 10% 5%; /* Dark text on light bg */
- --secondary: 210 10% 15%; /* Dark secondary */
+ --primary: 210 10% 98%; /* White-ish for dark mode */
+ --primary-foreground: 210 10% 5%; /* Dark text on light bg */
+ --secondary: 210 10% 15%; /* Dark secondary */
--secondary-foreground: 210 10% 90%; /* Light text on dark bg */
- --accent: 210 10% 18%; /* Accent background */
- --accent-foreground: 210 10% 95%; /* Accent text */
- --muted: 210 10% 12%; /* Muted background */
- --muted-foreground: 210 8% 60%; /* Muted text */
+ --accent: 210 10% 18%; /* Accent background */
+ --accent-foreground: 210 10% 95%; /* Accent text */
+ --muted: 210 10% 12%; /* Muted background */
+ --muted-foreground: 210 8% 60%; /* Muted text */
/* Status Colors (desaturated for dark mode) */
- --success: 142 71% 45%; /* #10b981 - Green */
- --success-foreground: 142 76% 95%; /* Light green text */
- --error: 0 72% 51%; /* #ef4444 - Red */
- --error-foreground: 0 86% 97%; /* Light red text */
- --warning: 38 92% 50%; /* #f59e0b - Amber */
- --warning-foreground: 48 96% 89%; /* Light amber text */
- --info: 217 91% 60%; /* #3b82f6 - Blue */
- --info-foreground: 214 95% 93%; /* Light blue text */
+ --success: 142 71% 45%; /* #10b981 - Green */
+ --success-foreground: 142 76% 95%; /* Light green text */
+ --error: 0 72% 51%; /* #ef4444 - Red */
+ --error-foreground: 0 86% 97%; /* Light red text */
+ --warning: 38 92% 50%; /* #f59e0b - Amber */
+ --warning-foreground: 48 96% 89%; /* Light amber text */
+ --info: 217 91% 60%; /* #3b82f6 - Blue */
+ --info-foreground: 214 95% 93%; /* Light blue text */
/* Destructive (legacy support) */
--destructive: 0 72% 51%;
--destructive-foreground: 0 86% 97%;
/* Form Elements */
- --input: 210 10% 20%; /* Input border */
- --ring: 210 10% 90%; /* Focus ring */
- --ring-offset: 210 10% 5%; /* Focus ring offset */
+ --input: 210 10% 20%; /* Input border */
+ --ring: 210 10% 90%; /* Focus ring */
+ --ring-offset: 210 10% 5%; /* Focus ring offset */
/* Grid/Blueprint Pattern */
- --grid-color: 210 10% 15%; /* Grid line color */
+ --grid-color: 210 10% 15%; /* Grid line color */
/* Card */
--card: 210 10% 8%;
@@ -135,39 +135,34 @@
body {
@apply bg-background text-foreground;
font-family: var(--font-sans);
- font-feature-settings: 'rlig' 1, 'calt' 1;
+ font-feature-settings: "rlig" 1, "calt" 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Grid/Blueprint background patterns */
.grid-background {
- background-image:
- linear-gradient(hsl(var(--grid-color)) 1px, transparent 1px),
+ background-image: linear-gradient(hsl(var(--grid-color)) 1px, transparent 1px),
linear-gradient(90deg, hsl(var(--grid-color)) 1px, transparent 1px);
background-size: var(--grid-size) var(--grid-size);
background-position: -1px -1px;
}
.dotted-grid-background {
- background-image:
- radial-gradient(circle, hsl(var(--grid-color)) 1px, transparent 1px);
+ background-image: radial-gradient(circle, hsl(var(--grid-color)) 1px, transparent 1px);
background-size: var(--grid-size) var(--grid-size);
}
.blueprint-background {
background-color: hsl(var(--background));
- background-image:
- /* Major grid lines (4x size) */
- linear-gradient(hsl(var(--grid-color)) 1px, transparent 1px),
+ /* Major grid lines (4x size) */
+ background-image: linear-gradient(hsl(var(--grid-color)) 1px, transparent 1px),
linear-gradient(90deg, hsl(var(--grid-color)) 1px, transparent 1px),
/* Minor grid lines (1x size) */
linear-gradient(hsl(var(--grid-color) / 0.3) 1px, transparent 1px),
linear-gradient(90deg, hsl(var(--grid-color) / 0.3) 1px, transparent 1px);
- background-size:
- calc(var(--grid-size) * 4) calc(var(--grid-size) * 4),
- calc(var(--grid-size) * 4) calc(var(--grid-size) * 4),
- var(--grid-size) var(--grid-size),
+ background-size: calc(var(--grid-size) * 4) calc(var(--grid-size) * 4),
+ calc(var(--grid-size) * 4) calc(var(--grid-size) * 4), var(--grid-size) var(--grid-size),
var(--grid-size) var(--grid-size);
}
@@ -178,8 +173,13 @@
}
@keyframes grid-pulse {
- 0%, 100% { opacity: 0.4; }
- 50% { opacity: 0.6; }
+ 0%,
+ 100% {
+ opacity: 0.4;
+ }
+ 50% {
+ opacity: 0.6;
+ }
}
}
diff --git a/packages/storybook/tailwind.config.ts b/packages/storybook/tailwind.config.ts
index 25e1ee0..6a820a2 100644
--- a/packages/storybook/tailwind.config.ts
+++ b/packages/storybook/tailwind.config.ts
@@ -1,7 +1,7 @@
-import baseConfig from "@tpmjs/tailwind-config/base";
-import type { Config } from "tailwindcss";
+import baseConfig from '@tpmjs/tailwind-config/base';
+import type { Config } from 'tailwindcss';
export default {
- ...baseConfig,
- content: ["./stories/**/*.{ts,tsx}", "../ui/src/**/*.{ts,tsx}"],
+ ...baseConfig,
+ content: ['./stories/**/*.{ts,tsx}', '../ui/src/**/*.{ts,tsx}'],
} satisfies Config;
diff --git a/packages/storybook/tsconfig.json b/packages/storybook/tsconfig.json
index b5955c1..91fc724 100644
--- a/packages/storybook/tsconfig.json
+++ b/packages/storybook/tsconfig.json
@@ -1,4 +1,4 @@
{
- "extends": "@tpmjs/tsconfig/react-library.json",
- "include": [".storybook", "stories"]
+ "extends": "@tpmjs/tsconfig/react-library.json",
+ "include": [".storybook", "stories"]
}
diff --git a/packages/test/package.json b/packages/test/package.json
index d6e54b4..a07166c 100644
--- a/packages/test/package.json
+++ b/packages/test/package.json
@@ -5,9 +5,7 @@
"exports": {
"./vitest": "./vitest.config.ts"
},
- "files": [
- "vitest.config.ts"
- ],
+ "files": ["vitest.config.ts"],
"dependencies": {
"vitest": "^2.1.8"
}
diff --git a/packages/types/package.json b/packages/types/package.json
index 9e9868a..b1e9b2a 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -12,9 +12,7 @@
"default": "./dist/registry.js"
}
},
- "files": [
- "dist"
- ],
+ "files": ["dist"],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
diff --git a/packages/ui/eslint.config.mjs b/packages/ui/eslint.config.mjs
index 5f0534a..8c13a22 100644
--- a/packages/ui/eslint.config.mjs
+++ b/packages/ui/eslint.config.mjs
@@ -1,31 +1,31 @@
-import reactConfig from "@tpmjs/eslint-config/react.js";
+import reactConfig from '@tpmjs/eslint-config/react.js';
export default [
- {
- ignores: ["dist/**", "node_modules/**", "eslint.config.mjs"],
- },
- ...reactConfig,
- {
- rules: {
- "import/no-internal-modules": [
- "error",
- {
- allow: [
- "@tpmjs/ui/*/[!index]*",
- "@tpmjs/utils/*",
- "@tpmjs/types/*",
- "@tpmjs/eslint-config/*",
- "@testing-library/**",
- "react/*",
- ],
- },
- ],
- },
- },
- {
- files: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"],
- rules: {
- "jsx-a11y/no-autofocus": "off",
- },
- },
+ {
+ ignores: ['dist/**', 'node_modules/**', 'eslint.config.mjs'],
+ },
+ ...reactConfig,
+ {
+ rules: {
+ 'import/no-internal-modules': [
+ 'error',
+ {
+ allow: [
+ '@tpmjs/ui/*/[!index]*',
+ '@tpmjs/utils/*',
+ '@tpmjs/types/*',
+ '@tpmjs/eslint-config/*',
+ '@testing-library/**',
+ 'react/*',
+ ],
+ },
+ ],
+ },
+ },
+ {
+ files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'],
+ rules: {
+ 'jsx-a11y/no-autofocus': 'off',
+ },
+ },
];
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 924e754..1587a01 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,96 +1,96 @@
{
- "name": "@tpmjs/ui",
- "version": "0.1.3",
- "type": "module",
- "exports": {
- "./Button/Button": {
- "types": "./dist/Button/Button.d.ts",
- "default": "./dist/Button/Button.js"
- },
- "./Card/Card": {
- "types": "./dist/Card/Card.d.ts",
- "default": "./dist/Card/Card.js"
- },
- "./Input/Input": {
- "types": "./dist/Input/Input.d.ts",
- "default": "./dist/Input/Input.js"
- },
- "./Label/Label": {
- "types": "./dist/Label/Label.d.ts",
- "default": "./dist/Label/Label.js"
- },
- "./Badge/Badge": {
- "types": "./dist/Badge/Badge.d.ts",
- "default": "./dist/Badge/Badge.js"
- },
- "./Container/Container": {
- "types": "./dist/Container/Container.d.ts",
- "default": "./dist/Container/Container.js"
- },
- "./Icon/Icon": {
- "types": "./dist/Icon/Icon.d.ts",
- "default": "./dist/Icon/Icon.js"
- },
- "./ProgressBar/ProgressBar": {
- "types": "./dist/ProgressBar/ProgressBar.d.ts",
- "default": "./dist/ProgressBar/ProgressBar.js"
- },
- "./CodeBlock/CodeBlock": {
- "types": "./dist/CodeBlock/CodeBlock.d.ts",
- "default": "./dist/CodeBlock/CodeBlock.js"
- },
- "./Tabs/Tabs": {
- "types": "./dist/Tabs/Tabs.d.ts",
- "default": "./dist/Tabs/Tabs.js"
- },
- "./Header/Header": {
- "types": "./dist/Header/Header.d.ts",
- "default": "./dist/Header/Header.js"
- },
- "./Section/Section": {
- "types": "./dist/Section/Section.d.ts",
- "default": "./dist/Section/Section.js"
- },
- "./GridContainer/GridContainer": {
- "types": "./dist/GridContainer/GridContainer.d.ts",
- "default": "./dist/GridContainer/GridContainer.js"
- }
- },
- "files": ["dist"],
- "scripts": {
- "build": "tsup",
- "dev": "tsup --watch",
- "test": "vitest run",
- "test:ui": "vitest --ui",
- "type-check": "tsc --noEmit",
- "lint": "eslint .",
- "clean": "rm -rf dist .turbo"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- },
- "dependencies": {
- "@tpmjs/utils": "workspace:*"
- },
- "devDependencies": {
- "@testing-library/jest-dom": "^6.9.1",
- "@testing-library/react": "^16.3.0",
- "@tpmjs/eslint-config": "workspace:*",
- "@tpmjs/test": "workspace:*",
- "@tpmjs/tsconfig": "workspace:*",
- "@types/react": "^19.0.2",
- "@types/react-dom": "^19.0.2",
- "eslint": "^9.39.1",
- "glob": "^13.0.0",
- "happy-dom": "^15.11.7",
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
- "tsup": "^8.3.5",
- "typescript": "^5.9.3",
- "vitest": "^2.1.8"
- },
- "publishConfig": {
- "access": "public"
- }
+ "name": "@tpmjs/ui",
+ "version": "0.1.3",
+ "type": "module",
+ "exports": {
+ "./Button/Button": {
+ "types": "./dist/Button/Button.d.ts",
+ "default": "./dist/Button/Button.js"
+ },
+ "./Card/Card": {
+ "types": "./dist/Card/Card.d.ts",
+ "default": "./dist/Card/Card.js"
+ },
+ "./Input/Input": {
+ "types": "./dist/Input/Input.d.ts",
+ "default": "./dist/Input/Input.js"
+ },
+ "./Label/Label": {
+ "types": "./dist/Label/Label.d.ts",
+ "default": "./dist/Label/Label.js"
+ },
+ "./Badge/Badge": {
+ "types": "./dist/Badge/Badge.d.ts",
+ "default": "./dist/Badge/Badge.js"
+ },
+ "./Container/Container": {
+ "types": "./dist/Container/Container.d.ts",
+ "default": "./dist/Container/Container.js"
+ },
+ "./Icon/Icon": {
+ "types": "./dist/Icon/Icon.d.ts",
+ "default": "./dist/Icon/Icon.js"
+ },
+ "./ProgressBar/ProgressBar": {
+ "types": "./dist/ProgressBar/ProgressBar.d.ts",
+ "default": "./dist/ProgressBar/ProgressBar.js"
+ },
+ "./CodeBlock/CodeBlock": {
+ "types": "./dist/CodeBlock/CodeBlock.d.ts",
+ "default": "./dist/CodeBlock/CodeBlock.js"
+ },
+ "./Tabs/Tabs": {
+ "types": "./dist/Tabs/Tabs.d.ts",
+ "default": "./dist/Tabs/Tabs.js"
+ },
+ "./Header/Header": {
+ "types": "./dist/Header/Header.d.ts",
+ "default": "./dist/Header/Header.js"
+ },
+ "./Section/Section": {
+ "types": "./dist/Section/Section.d.ts",
+ "default": "./dist/Section/Section.js"
+ },
+ "./GridContainer/GridContainer": {
+ "types": "./dist/GridContainer/GridContainer.d.ts",
+ "default": "./dist/GridContainer/GridContainer.js"
+ }
+ },
+ "files": ["dist"],
+ "scripts": {
+ "build": "tsup",
+ "dev": "tsup --watch",
+ "test": "vitest run",
+ "test:ui": "vitest --ui",
+ "type-check": "tsc --noEmit",
+ "lint": "eslint .",
+ "clean": "rm -rf dist .turbo"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "dependencies": {
+ "@tpmjs/utils": "workspace:*"
+ },
+ "devDependencies": {
+ "@testing-library/jest-dom": "^6.9.1",
+ "@testing-library/react": "^16.3.0",
+ "@tpmjs/eslint-config": "workspace:*",
+ "@tpmjs/test": "workspace:*",
+ "@tpmjs/tsconfig": "workspace:*",
+ "@types/react": "^19.0.2",
+ "@types/react-dom": "^19.0.2",
+ "eslint": "^9.39.1",
+ "glob": "^13.0.0",
+ "happy-dom": "^15.11.7",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "tsup": "^8.3.5",
+ "typescript": "^5.9.3",
+ "vitest": "^2.1.8"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
}
diff --git a/packages/ui/src/Badge/Badge.test.tsx b/packages/ui/src/Badge/Badge.test.tsx
index 33abfdf..0a567e7 100644
--- a/packages/ui/src/Badge/Badge.test.tsx
+++ b/packages/ui/src/Badge/Badge.test.tsx
@@ -1,400 +1,386 @@
-import { render, screen } from "@testing-library/react";
-import { describe, expect, it } from "vitest";
-import { Badge } from "./Badge";
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { Badge } from './Badge';
-describe("Badge", () => {
- describe("Rendering", () => {
- it("renders a badge element", () => {
- render(Badge);
- const badge = screen.getByTestId("badge");
- expect(badge).toBeInTheDocument();
- expect(badge.tagName).toBe("DIV");
- });
+describe('Badge', () => {
+ describe('Rendering', () => {
+ it('renders a badge element', () => {
+ render(Badge);
+ const badge = screen.getByTestId('badge');
+ expect(badge).toBeInTheDocument();
+ expect(badge.tagName).toBe('DIV');
+ });
- it("renders children text", () => {
- render(Active);
- expect(screen.getByText("Active")).toBeInTheDocument();
- });
- });
+ it('renders children text', () => {
+ render(Active);
+ expect(screen.getByText('Active')).toBeInTheDocument();
+ });
+ });
- describe("Variants", () => {
- it("applies default variant classes", () => {
- render(
-
- Default
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("bg-primary");
- expect(badge.className).toContain("text-primary-foreground");
- expect(badge.className).toContain("border-primary");
- });
+ describe('Variants', () => {
+ it('applies default variant classes', () => {
+ render(
+
+ Default
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('bg-primary');
+ expect(badge.className).toContain('text-primary-foreground');
+ expect(badge.className).toContain('border-primary');
+ });
- it("applies secondary variant classes", () => {
- render(
-
- Secondary
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("bg-secondary");
- expect(badge.className).toContain("text-secondary-foreground");
- });
+ it('applies secondary variant classes', () => {
+ render(
+
+ Secondary
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('bg-secondary');
+ expect(badge.className).toContain('text-secondary-foreground');
+ });
- it("applies outline variant classes", () => {
- render(
-
- Outline
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("bg-transparent");
- expect(badge.className).toContain("text-foreground");
- expect(badge.className).toContain("border-border");
- });
+ it('applies outline variant classes', () => {
+ render(
+
+ Outline
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('bg-transparent');
+ expect(badge.className).toContain('text-foreground');
+ expect(badge.className).toContain('border-border');
+ });
- it("applies success variant classes", () => {
- render(
-
- Success
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("bg-success");
- expect(badge.className).toContain("text-success-foreground");
- });
+ it('applies success variant classes', () => {
+ render(
+
+ Success
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('bg-success');
+ expect(badge.className).toContain('text-success-foreground');
+ });
- it("applies error variant classes", () => {
- render(
-
- Error
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("bg-error");
- expect(badge.className).toContain("text-error-foreground");
- });
+ it('applies error variant classes', () => {
+ render(
+
+ Error
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('bg-error');
+ expect(badge.className).toContain('text-error-foreground');
+ });
- it("applies warning variant classes", () => {
- render(
-
- Warning
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("bg-warning");
- expect(badge.className).toContain("text-warning-foreground");
- });
+ it('applies warning variant classes', () => {
+ render(
+
+ Warning
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('bg-warning');
+ expect(badge.className).toContain('text-warning-foreground');
+ });
- it("applies info variant classes", () => {
- render(
-
- Info
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("bg-info");
- expect(badge.className).toContain("text-info-foreground");
- });
- });
+ it('applies info variant classes', () => {
+ render(
+
+ Info
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('bg-info');
+ expect(badge.className).toContain('text-info-foreground');
+ });
+ });
- describe("Sizes", () => {
- it("applies small size classes", () => {
- render(
-
- Small
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("px-2");
- expect(badge.className).toContain("py-0.5");
- expect(badge.className).toContain("text-xs");
- });
+ describe('Sizes', () => {
+ it('applies small size classes', () => {
+ render(
+
+ Small
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('px-2');
+ expect(badge.className).toContain('py-0.5');
+ expect(badge.className).toContain('text-xs');
+ });
- it("applies medium size classes (default)", () => {
- render(
-
- Medium
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("px-2.5");
- expect(badge.className).toContain("py-1");
- expect(badge.className).toContain("text-sm");
- });
+ it('applies medium size classes (default)', () => {
+ render(
+
+ Medium
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('px-2.5');
+ expect(badge.className).toContain('py-1');
+ expect(badge.className).toContain('text-sm');
+ });
- it("applies large size classes", () => {
- render(
-
- Large
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("px-3");
- expect(badge.className).toContain("py-1.5");
- expect(badge.className).toContain("text-base");
- });
+ it('applies large size classes', () => {
+ render(
+
+ Large
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('px-3');
+ expect(badge.className).toContain('py-1.5');
+ expect(badge.className).toContain('text-base');
+ });
- it("uses medium size by default", () => {
- render(Default);
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("text-sm");
- });
- });
+ it('uses medium size by default', () => {
+ render(Default);
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('text-sm');
+ });
+ });
- describe("HTML Attributes", () => {
- it("passes through id attribute", () => {
- render(
-
- Badge
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge).toHaveAttribute("id", "badge-id");
- });
+ describe('HTML Attributes', () => {
+ it('passes through id attribute', () => {
+ render(
+
+ Badge
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge).toHaveAttribute('id', 'badge-id');
+ });
- it("passes through title attribute", () => {
- render(
-
- Badge
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge).toHaveAttribute("title", "Badge tooltip");
- });
+ it('passes through title attribute', () => {
+ render(
+
+ Badge
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge).toHaveAttribute('title', 'Badge tooltip');
+ });
- it("passes through onClick handler", () => {
- let clicked = false;
- render(
- {
- clicked = true;
- }}
- data-testid="badge"
- >
- Clickable
- ,
- );
- const badge = screen.getByTestId("badge");
- badge.click();
- expect(clicked).toBe(true);
- });
+ it('passes through onClick handler', () => {
+ let clicked = false;
+ render(
+ {
+ clicked = true;
+ }}
+ data-testid="badge"
+ >
+ Clickable
+
+ );
+ const badge = screen.getByTestId('badge');
+ badge.click();
+ expect(clicked).toBe(true);
+ });
- it("passes through role attribute", () => {
- render(
-
- Status
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge).toHaveAttribute("role", "status");
- });
- });
+ it('passes through role attribute', () => {
+ render(
+
+ Status
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge).toHaveAttribute('role', 'status');
+ });
+ });
- describe("ARIA Attributes", () => {
- it("passes through aria-label", () => {
- render(
-
- Active
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge).toHaveAttribute("aria-label", "Status badge");
- });
+ describe('ARIA Attributes', () => {
+ it('passes through aria-label', () => {
+ render(
+
+ Active
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge).toHaveAttribute('aria-label', 'Status badge');
+ });
- it("passes through aria-describedby", () => {
- render(
-
- Badge
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge).toHaveAttribute("aria-describedby", "description-id");
- });
+ it('passes through aria-describedby', () => {
+ render(
+
+ Badge
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge).toHaveAttribute('aria-describedby', 'description-id');
+ });
- it("passes through aria-live for dynamic status", () => {
- render(
-
- Updating
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge).toHaveAttribute("aria-live", "polite");
- });
- });
+ it('passes through aria-live for dynamic status', () => {
+ render(
+
+ Updating
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge).toHaveAttribute('aria-live', 'polite');
+ });
+ });
- describe("Custom className", () => {
- it("merges custom className with variant classes", () => {
- render(
-
- Custom
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("custom-badge");
- expect(badge.className).toContain("rounded-full");
- expect(badge.className).toContain("bg-primary");
- });
- });
+ describe('Custom className', () => {
+ it('merges custom className with variant classes', () => {
+ render(
+
+ Custom
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('custom-badge');
+ expect(badge.className).toContain('rounded-full');
+ expect(badge.className).toContain('bg-primary');
+ });
+ });
- describe("Ref Forwarding", () => {
- it("forwards ref to badge element", () => {
- let ref: HTMLDivElement | null = null;
- render(
- {
- ref = el;
- }}
- >
- Badge
- ,
- );
- expect(ref).toBeInstanceOf(HTMLDivElement);
- expect(ref!.tagName).toBe("DIV");
- });
- });
+ describe('Ref Forwarding', () => {
+ it('forwards ref to badge element', () => {
+ let ref: HTMLDivElement | null = null;
+ render(
+ {
+ ref = el;
+ }}
+ >
+ Badge
+
+ );
+ expect(ref).toBeInstanceOf(HTMLDivElement);
+ expect(ref!.tagName).toBe('DIV');
+ });
+ });
- describe("Base Classes", () => {
- it("always includes base classes", () => {
- render(Badge);
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("inline-flex");
- expect(badge.className).toContain("items-center");
- expect(badge.className).toContain("font-semibold");
- expect(badge.className).toContain("whitespace-nowrap");
- expect(badge.className).toContain("rounded-full");
- });
+ describe('Base Classes', () => {
+ it('always includes base classes', () => {
+ render(Badge);
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('inline-flex');
+ expect(badge.className).toContain('items-center');
+ expect(badge.className).toContain('font-semibold');
+ expect(badge.className).toContain('whitespace-nowrap');
+ expect(badge.className).toContain('rounded-full');
+ });
- it("includes transition classes", () => {
- render(Badge);
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("transition-base");
- });
- });
+ it('includes transition classes', () => {
+ render(Badge);
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('transition-base');
+ });
+ });
- describe("Compound Scenarios", () => {
- it("works correctly with success variant and small size", () => {
- render(
-
- Success
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("bg-success");
- expect(badge.className).toContain("text-xs");
- });
+ describe('Compound Scenarios', () => {
+ it('works correctly with success variant and small size', () => {
+ render(
+
+ Success
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('bg-success');
+ expect(badge.className).toContain('text-xs');
+ });
- it("works correctly with error variant and large size", () => {
- render(
-
- Error
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("bg-error");
- expect(badge.className).toContain("text-base");
- });
+ it('works correctly with error variant and large size', () => {
+ render(
+
+ Error
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('bg-error');
+ expect(badge.className).toContain('text-base');
+ });
- it("works correctly with outline variant and custom className", () => {
- render(
-
- Outlined
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("bg-transparent");
- expect(badge.className).toContain("hover:bg-accent");
- });
+ it('works correctly with outline variant and custom className', () => {
+ render(
+
+ Outlined
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('bg-transparent');
+ expect(badge.className).toContain('hover:bg-accent');
+ });
- it("works correctly with warning variant and onClick", () => {
- let clicked = false;
- render(
- {
- clicked = true;
- }}
- data-testid="badge"
- >
- Warning
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("bg-warning");
- badge.click();
- expect(clicked).toBe(true);
- });
- });
+ it('works correctly with warning variant and onClick', () => {
+ let clicked = false;
+ render(
+ {
+ clicked = true;
+ }}
+ data-testid="badge"
+ >
+ Warning
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('bg-warning');
+ badge.click();
+ expect(clicked).toBe(true);
+ });
+ });
- describe("Content Types", () => {
- it("renders with text content", () => {
- render(Text);
- expect(screen.getByText("Text")).toBeInTheDocument();
- });
+ describe('Content Types', () => {
+ it('renders with text content', () => {
+ render(Text);
+ expect(screen.getByText('Text')).toBeInTheDocument();
+ });
- it("renders with numeric content", () => {
- render(42);
- expect(screen.getByText("42")).toBeInTheDocument();
- });
+ it('renders with numeric content', () => {
+ render(42);
+ expect(screen.getByText('42')).toBeInTheDocument();
+ });
- it("renders with multiple children", () => {
- render(
-
- Count: 5
- ,
- );
- expect(screen.getByText("Count:")).toBeInTheDocument();
- expect(screen.getByText("5")).toBeInTheDocument();
- });
- });
+ it('renders with multiple children', () => {
+ render(
+
+ Count: 5
+
+ );
+ expect(screen.getByText('Count:')).toBeInTheDocument();
+ expect(screen.getByText('5')).toBeInTheDocument();
+ });
+ });
- describe("Semantic Usage", () => {
- it("can be used as status indicator with role", () => {
- render(
-
- Online
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge).toHaveAttribute("role", "status");
- expect(badge).toHaveAttribute("aria-label", "Online status");
- expect(badge.className).toContain("bg-success");
- });
+ describe('Semantic Usage', () => {
+ it('can be used as status indicator with role', () => {
+ render(
+
+ Online
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge).toHaveAttribute('role', 'status');
+ expect(badge).toHaveAttribute('aria-label', 'Online status');
+ expect(badge.className).toContain('bg-success');
+ });
- it("can be used as notification count", () => {
- render(
-
- 3
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge).toHaveAttribute("aria-label", "3 unread messages");
- expect(screen.getByText("3")).toBeInTheDocument();
- });
+ it('can be used as notification count', () => {
+ render(
+
+ 3
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge).toHaveAttribute('aria-label', '3 unread messages');
+ expect(screen.getByText('3')).toBeInTheDocument();
+ });
- it("can be used as tag/label", () => {
- render(
-
- TypeScript
- ,
- );
- const badge = screen.getByTestId("badge");
- expect(badge.className).toContain("bg-transparent");
- expect(screen.getByText("TypeScript")).toBeInTheDocument();
- });
- });
+ it('can be used as tag/label', () => {
+ render(
+
+ TypeScript
+
+ );
+ const badge = screen.getByTestId('badge');
+ expect(badge.className).toContain('bg-transparent');
+ expect(screen.getByText('TypeScript')).toBeInTheDocument();
+ });
+ });
});
diff --git a/packages/ui/src/Badge/Badge.tsx b/packages/ui/src/Badge/Badge.tsx
index 0775d47..25d3662 100644
--- a/packages/ui/src/Badge/Badge.tsx
+++ b/packages/ui/src/Badge/Badge.tsx
@@ -1,7 +1,7 @@
-import { cn } from "@tpmjs/utils/cn";
-import { forwardRef } from "react";
-import type { BadgeProps } from "./types";
-import { badgeVariants } from "./variants";
+import { cn } from '@tpmjs/utils/cn';
+import { forwardRef } from 'react';
+import type { BadgeProps } from './types';
+import { badgeVariants } from './variants';
/**
* Badge component
@@ -18,21 +18,21 @@ import { badgeVariants } from "./variants";
* ```
*/
export const Badge = forwardRef(
- ({ className, variant = "default", size = "md", ...props }, ref) => {
- return (
-
- );
- },
+ ({ className, variant = 'default', size = 'md', ...props }, ref) => {
+ return (
+
+ );
+ }
);
-Badge.displayName = "Badge";
+Badge.displayName = 'Badge';
diff --git a/packages/ui/src/Badge/tokens.ts b/packages/ui/src/Badge/tokens.ts
index 219ab17..5af4003 100644
--- a/packages/ui/src/Badge/tokens.ts
+++ b/packages/ui/src/Badge/tokens.ts
@@ -1,41 +1,41 @@
-import { borderRadius, spacing } from "../tokens";
+import { borderRadius, spacing } from '../tokens';
/**
* Badge component design tokens
* Maps global tokens to badge-specific semantics
*/
export const badgeTokens = {
- /** Horizontal padding for each size */
- padding: {
- x: {
- sm: spacing[2], // 0.5rem (8px)
- md: spacing[2.5], // 0.625rem (10px)
- lg: spacing[3], // 0.75rem (12px)
- },
- y: {
- sm: spacing[0.5], // 0.125rem (2px)
- md: spacing[1], // 0.25rem (4px)
- lg: spacing[1.5], // 0.375rem (6px)
- },
- },
+ /** Horizontal padding for each size */
+ padding: {
+ x: {
+ sm: spacing[2], // 0.5rem (8px)
+ md: spacing[2.5], // 0.625rem (10px)
+ lg: spacing[3], // 0.75rem (12px)
+ },
+ y: {
+ sm: spacing[0.5], // 0.125rem (2px)
+ md: spacing[1], // 0.25rem (4px)
+ lg: spacing[1.5], // 0.375rem (6px)
+ },
+ },
- /** Font sizes for each size */
- fontSize: {
- sm: "0.75rem", // 12px
- md: "0.875rem", // 14px
- lg: "1rem", // 16px
- },
+ /** Font sizes for each size */
+ fontSize: {
+ sm: '0.75rem', // 12px
+ md: '0.875rem', // 14px
+ lg: '1rem', // 16px
+ },
- /** Line heights for each size */
- lineHeight: {
- sm: "1rem", // 16px
- md: "1.25rem", // 20px
- lg: "1.5rem", // 24px
- },
+ /** Line heights for each size */
+ lineHeight: {
+ sm: '1rem', // 16px
+ md: '1.25rem', // 20px
+ lg: '1.5rem', // 24px
+ },
- /** Border radius */
- borderRadius: borderRadius.full,
+ /** Border radius */
+ borderRadius: borderRadius.full,
- /** Border width */
- borderWidth: "1px",
+ /** Border width */
+ borderWidth: '1px',
} as const;
diff --git a/packages/ui/src/Badge/types.ts b/packages/ui/src/Badge/types.ts
index 0264f6f..c2f0418 100644
--- a/packages/ui/src/Badge/types.ts
+++ b/packages/ui/src/Badge/types.ts
@@ -1,27 +1,20 @@
-import type { HTMLAttributes } from "react";
+import type { HTMLAttributes } from 'react';
/**
* Badge component props
*/
export interface BadgeProps extends HTMLAttributes {
- /**
- * Visual variant of the badge
- * @default 'default'
- */
- variant?:
- | "default"
- | "secondary"
- | "outline"
- | "success"
- | "error"
- | "warning"
- | "info";
+ /**
+ * Visual variant of the badge
+ * @default 'default'
+ */
+ variant?: 'default' | 'secondary' | 'outline' | 'success' | 'error' | 'warning' | 'info';
- /**
- * Size of the badge
- * @default 'md'
- */
- size?: "sm" | "md" | "lg";
+ /**
+ * Size of the badge
+ * @default 'md'
+ */
+ size?: 'sm' | 'md' | 'lg';
}
/**
diff --git a/packages/ui/src/Badge/variants.ts b/packages/ui/src/Badge/variants.ts
index 35dff41..cebbdb7 100644
--- a/packages/ui/src/Badge/variants.ts
+++ b/packages/ui/src/Badge/variants.ts
@@ -1,66 +1,50 @@
-import { createVariants } from "../system/variants";
+import { createVariants } from '../system/variants';
/**
* Badge variant definitions
* Uses custom variant system for type-safe class composition
*/
export const badgeVariants = createVariants({
- base: [
- // Layout
- "inline-flex items-center",
- // Typography
- "font-semibold",
- "whitespace-nowrap",
- // Borders & Radius
- "rounded-full",
- // Transitions
- "transition-base",
- ].join(" "),
+ base: [
+ // Layout
+ 'inline-flex items-center',
+ // Typography
+ 'font-semibold',
+ 'whitespace-nowrap',
+ // Borders & Radius
+ 'rounded-full',
+ // Transitions
+ 'transition-base',
+ ].join(' '),
- variants: {
- variant: {
- default: [
- "bg-primary text-primary-foreground",
- "border border-primary",
- ].join(" "),
+ variants: {
+ variant: {
+ default: ['bg-primary text-primary-foreground', 'border border-primary'].join(' '),
- secondary: [
- "bg-secondary text-secondary-foreground",
- "border border-secondary",
- ].join(" "),
+ secondary: ['bg-secondary text-secondary-foreground', 'border border-secondary'].join(' '),
- outline: ["bg-transparent text-foreground", "border border-border"].join(
- " ",
- ),
+ outline: ['bg-transparent text-foreground', 'border border-border'].join(' '),
- success: [
- "bg-success text-success-foreground",
- "border border-success",
- ].join(" "),
+ success: ['bg-success text-success-foreground', 'border border-success'].join(' '),
- error: ["bg-error text-error-foreground", "border border-error"].join(
- " ",
- ),
+ error: ['bg-error text-error-foreground', 'border border-error'].join(' '),
- warning: [
- "bg-warning text-warning-foreground",
- "border border-warning",
- ].join(" "),
+ warning: ['bg-warning text-warning-foreground', 'border border-warning'].join(' '),
- info: ["bg-info text-info-foreground", "border border-info"].join(" "),
- },
+ info: ['bg-info text-info-foreground', 'border border-info'].join(' '),
+ },
- size: {
- sm: "px-2 py-0.5 text-xs",
- md: "px-2.5 py-1 text-sm",
- lg: "px-3 py-1.5 text-base",
- },
- },
+ size: {
+ sm: 'px-2 py-0.5 text-xs',
+ md: 'px-2.5 py-1 text-sm',
+ lg: 'px-3 py-1.5 text-base',
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- variant: "default",
- size: "md",
- },
+ defaultVariants: {
+ variant: 'default',
+ size: 'md',
+ },
});
diff --git a/packages/ui/src/Button/Button.test.tsx b/packages/ui/src/Button/Button.test.tsx
index 147832d..264d10d 100644
--- a/packages/ui/src/Button/Button.test.tsx
+++ b/packages/ui/src/Button/Button.test.tsx
@@ -1,338 +1,331 @@
-import { render, screen } from "@testing-library/react";
-import { describe, expect, it } from "vitest";
-import { Button } from "./Button";
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { Button } from './Button';
-describe("Button", () => {
- describe("Rendering", () => {
- it("renders with default variant and size", () => {
- render();
- const button = screen.getByRole("button");
- expect(button).toBeInTheDocument();
- expect(button).toHaveTextContent("Click me");
- });
+describe('Button', () => {
+ describe('Rendering', () => {
+ it('renders with default variant and size', () => {
+ render();
+ const button = screen.getByRole('button');
+ expect(button).toBeInTheDocument();
+ expect(button).toHaveTextContent('Click me');
+ });
- it("renders as a button element", () => {
- render();
- expect(screen.getByRole("button").tagName).toBe("BUTTON");
- });
- });
+ it('renders as a button element', () => {
+ render();
+ expect(screen.getByRole('button').tagName).toBe('BUTTON');
+ });
+ });
- describe("Variants", () => {
- it("applies default variant classes", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button.className).toContain("bg-primary");
- expect(button.className).toContain("text-primary-foreground");
- });
+ describe('Variants', () => {
+ it('applies default variant classes', () => {
+ render(
+
+ );
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('bg-primary');
+ expect(button.className).toContain('text-primary-foreground');
+ });
- it("applies destructive variant classes", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button.className).toContain("bg-error");
- expect(button.className).toContain("text-error-foreground");
- });
+ it('applies destructive variant classes', () => {
+ render(
+
+ );
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('bg-error');
+ expect(button.className).toContain('text-error-foreground');
+ });
- it("applies outline variant classes", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button.className).toContain("border");
- expect(button.className).toContain("bg-transparent");
- });
+ it('applies outline variant classes', () => {
+ render(
+
+ );
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('border');
+ expect(button.className).toContain('bg-transparent');
+ });
- it("applies secondary variant classes", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button.className).toContain("bg-secondary");
- expect(button.className).toContain("text-secondary-foreground");
- });
+ it('applies secondary variant classes', () => {
+ render(
+
+ );
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('bg-secondary');
+ expect(button.className).toContain('text-secondary-foreground');
+ });
- it("applies ghost variant classes", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button.className).toContain("hover:bg-accent");
- });
+ it('applies ghost variant classes', () => {
+ render(
+
+ );
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('hover:bg-accent');
+ });
- it("applies link variant classes", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button.className).toContain("text-primary");
- expect(button.className).toContain("underline-offset-4");
- });
- });
+ it('applies link variant classes', () => {
+ render(
+
+ );
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('text-primary');
+ expect(button.className).toContain('underline-offset-4');
+ });
+ });
- describe("Sizes", () => {
- it("applies small size classes", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button.className).toContain("h-9");
- expect(button.className).toContain("px-3");
- });
+ describe('Sizes', () => {
+ it('applies small size classes', () => {
+ render(
+
+ );
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('h-9');
+ expect(button.className).toContain('px-3');
+ });
- it("applies medium size classes (default)", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button.className).toContain("h-10");
- expect(button.className).toContain("px-4");
- });
+ it('applies medium size classes (default)', () => {
+ render(
+
+ );
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('h-10');
+ expect(button.className).toContain('px-4');
+ });
- it("applies large size classes", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button.className).toContain("h-11");
- expect(button.className).toContain("px-8");
- });
+ it('applies large size classes', () => {
+ render(
+
+ );
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('h-11');
+ expect(button.className).toContain('px-8');
+ });
- it("applies icon size classes", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button.className).toContain("h-10");
- expect(button.className).toContain("w-10");
- });
- });
+ it('applies icon size classes', () => {
+ render();
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('h-10');
+ expect(button.className).toContain('w-10');
+ });
+ });
- describe("Compound Variants", () => {
- it("applies compound variant for outline + small", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button.className).toContain("border");
- expect(button.className).toContain("h-9");
- });
+ describe('Compound Variants', () => {
+ it('applies compound variant for outline + small', () => {
+ render(
+
+ );
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('border');
+ expect(button.className).toContain('h-9');
+ });
- it("applies compound variant for link variants (removes padding)", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button.className).toContain("px-0");
- });
- });
+ it('applies compound variant for link variants (removes padding)', () => {
+ render(
+
+ );
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('px-0');
+ });
+ });
- describe("Loading State", () => {
- it("shows loading spinner when loading is true", () => {
- render();
- const button = screen.getByRole("button");
+ describe('Loading State', () => {
+ it('shows loading spinner when loading is true', () => {
+ render();
+ const button = screen.getByRole('button');
- // Should have loading indicator
- const spinner = button.querySelector('[aria-hidden="true"]');
- expect(spinner).toBeInTheDocument();
- expect(spinner?.className).toContain("animate-spin");
- });
+ // Should have loading indicator
+ const spinner = button.querySelector('[aria-hidden="true"]');
+ expect(spinner).toBeInTheDocument();
+ expect(spinner?.className).toContain('animate-spin');
+ });
- it("disables button when loading", () => {
- render();
- const button = screen.getByRole("button");
- expect(button).toBeDisabled();
- expect(button).toHaveAttribute("aria-busy", "true");
- });
+ it('disables button when loading', () => {
+ render();
+ const button = screen.getByRole('button');
+ expect(button).toBeDisabled();
+ expect(button).toHaveAttribute('aria-busy', 'true');
+ });
- it("applies cursor-wait class when loading", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button.className).toContain("cursor-wait");
- });
+ it('applies cursor-wait class when loading', () => {
+ render(
+
+ );
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('cursor-wait');
+ });
- it("still shows children text when loading", () => {
- render();
- expect(screen.getByText("Processing")).toBeInTheDocument();
- });
- });
+ it('still shows children text when loading', () => {
+ render();
+ expect(screen.getByText('Processing')).toBeInTheDocument();
+ });
+ });
- describe("Disabled State", () => {
- it("disables button when disabled prop is true", () => {
- render();
- const button = screen.getByRole("button");
- expect(button).toBeDisabled();
- expect(button).toHaveAttribute("aria-disabled", "true");
- });
+ describe('Disabled State', () => {
+ it('disables button when disabled prop is true', () => {
+ render();
+ const button = screen.getByRole('button');
+ expect(button).toBeDisabled();
+ expect(button).toHaveAttribute('aria-disabled', 'true');
+ });
- it("applies disabled opacity class", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button.className).toContain("disabled:opacity-50");
- expect(button.className).toContain("disabled:pointer-events-none");
- });
+ it('applies disabled opacity class', () => {
+ render(
+
+ );
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('disabled:opacity-50');
+ expect(button.className).toContain('disabled:pointer-events-none');
+ });
- it("is disabled when both disabled and loading are true", () => {
- render(
- ,
- );
- const button = screen.getByRole("button");
- expect(button).toBeDisabled();
- });
- });
+ it('is disabled when both disabled and loading are true', () => {
+ render(
+
+ );
+ const button = screen.getByRole('button');
+ expect(button).toBeDisabled();
+ });
+ });
- describe("Custom className", () => {
- it("merges custom className with variant classes", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button.className).toContain("custom-class");
- expect(button.className).toContain("bg-primary");
- });
- });
+ describe('Custom className', () => {
+ it('merges custom className with variant classes', () => {
+ render(
+
+ );
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('custom-class');
+ expect(button.className).toContain('bg-primary');
+ });
+ });
- describe("Ref Forwarding", () => {
- it("forwards ref to button element", () => {
- let ref: HTMLButtonElement | null = null;
- render(
- ,
- );
- expect(ref).toBeInstanceOf(HTMLButtonElement);
- expect(ref!.tagName).toBe("BUTTON");
- });
- });
+ describe('Ref Forwarding', () => {
+ it('forwards ref to button element', () => {
+ let ref: HTMLButtonElement | null = null;
+ render(
+
+ );
+ expect(ref).toBeInstanceOf(HTMLButtonElement);
+ expect(ref!.tagName).toBe('BUTTON');
+ });
+ });
- describe("HTML Attributes", () => {
- it("passes through HTML button attributes", () => {
- render(
- ,
- );
- const button = screen.getByTestId("button");
- expect(button).toHaveAttribute("type", "submit");
- expect(button).toHaveAttribute("name", "test-button");
- expect(button).toHaveAttribute("value", "test-value");
- });
+ describe('HTML Attributes', () => {
+ it('passes through HTML button attributes', () => {
+ render(
+
+ );
+ const button = screen.getByTestId('button');
+ expect(button).toHaveAttribute('type', 'submit');
+ expect(button).toHaveAttribute('name', 'test-button');
+ expect(button).toHaveAttribute('value', 'test-value');
+ });
- it("supports onClick handler", () => {
- let clicked = false;
- render(
- ,
- );
- const button = screen.getByRole("button");
- button.click();
- expect(clicked).toBe(true);
- });
- });
+ it('supports onClick handler', () => {
+ let clicked = false;
+ render(
+
+ );
+ const button = screen.getByRole('button');
+ button.click();
+ expect(clicked).toBe(true);
+ });
+ });
- describe("Accessibility", () => {
- it("has proper ARIA attributes for loading state", () => {
- render();
- const button = screen.getByRole("button");
- expect(button).toHaveAttribute("aria-busy", "true");
- expect(button).toHaveAttribute("aria-disabled", "true");
- });
+ describe('Accessibility', () => {
+ it('has proper ARIA attributes for loading state', () => {
+ render();
+ const button = screen.getByRole('button');
+ expect(button).toHaveAttribute('aria-busy', 'true');
+ expect(button).toHaveAttribute('aria-disabled', 'true');
+ });
- it("has proper ARIA attributes for disabled state", () => {
- render();
- const button = screen.getByRole("button");
- expect(button).toHaveAttribute("aria-disabled", "true");
- });
+ it('has proper ARIA attributes for disabled state', () => {
+ render();
+ const button = screen.getByRole('button');
+ expect(button).toHaveAttribute('aria-disabled', 'true');
+ });
- it("hides loading spinner from screen readers", () => {
- render();
- const button = screen.getByRole("button");
- const spinner = button.querySelector('[aria-hidden="true"]');
- expect(spinner).toHaveAttribute("aria-hidden", "true");
- });
+ it('hides loading spinner from screen readers', () => {
+ render();
+ const button = screen.getByRole('button');
+ const spinner = button.querySelector('[aria-hidden="true"]');
+ expect(spinner).toHaveAttribute('aria-hidden', 'true');
+ });
- it("supports aria-label for icon buttons", () => {
- render(
- ,
- );
- const button = screen.getByRole("button");
- expect(button).toHaveAccessibleName("Close dialog");
- });
- });
+ it('supports aria-label for icon buttons', () => {
+ render(
+
+ );
+ const button = screen.getByRole('button');
+ expect(button).toHaveAccessibleName('Close dialog');
+ });
+ });
- describe("Base Classes", () => {
- it("always includes base classes", () => {
- render();
- const button = screen.getByTestId("button");
- expect(button.className).toContain("inline-flex");
- expect(button.className).toContain("items-center");
- expect(button.className).toContain("justify-center");
- expect(button.className).toContain("font-medium");
- expect(button.className).toContain("rounded-md");
- });
+ describe('Base Classes', () => {
+ it('always includes base classes', () => {
+ render();
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('inline-flex');
+ expect(button.className).toContain('items-center');
+ expect(button.className).toContain('justify-center');
+ expect(button.className).toContain('font-medium');
+ expect(button.className).toContain('rounded-md');
+ });
- it("includes focus ring classes", () => {
- render();
- const button = screen.getByTestId("button");
- expect(button.className).toContain("focus-ring");
- });
+ it('includes focus ring classes', () => {
+ render();
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('focus-ring');
+ });
- it("includes transition classes", () => {
- render();
- const button = screen.getByTestId("button");
- expect(button.className).toContain("transition-base");
- });
- });
+ it('includes transition classes', () => {
+ render();
+ const button = screen.getByTestId('button');
+ expect(button.className).toContain('transition-base');
+ });
+ });
});
diff --git a/packages/ui/src/Button/Button.tsx b/packages/ui/src/Button/Button.tsx
index c38ca9c..da7b85d 100644
--- a/packages/ui/src/Button/Button.tsx
+++ b/packages/ui/src/Button/Button.tsx
@@ -1,7 +1,7 @@
-import { cn } from "@tpmjs/utils/cn";
-import { forwardRef } from "react";
-import type { ButtonProps } from "./types";
-import { buttonVariants } from "./variants";
+import { cn } from '@tpmjs/utils/cn';
+import { forwardRef } from 'react';
+import type { ButtonProps } from './types';
+import { buttonVariants } from './variants';
/**
* Button component
@@ -26,49 +26,49 @@ import { buttonVariants } from "./variants";
* ```
*/
export const Button = forwardRef(
- (
- {
- className,
- variant = "default",
- size = "md",
- loading = false,
- disabled = false,
- children,
- ...props
- },
- ref,
- ) => {
- return (
-
- );
- },
+ (
+ {
+ className,
+ variant = 'default',
+ size = 'md',
+ loading = false,
+ disabled = false,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ return (
+
+ );
+ }
);
-Button.displayName = "Button";
+Button.displayName = 'Button';
diff --git a/packages/ui/src/Button/tokens.ts b/packages/ui/src/Button/tokens.ts
index 151b915..079c60b 100644
--- a/packages/ui/src/Button/tokens.ts
+++ b/packages/ui/src/Button/tokens.ts
@@ -1,33 +1,33 @@
-import { borderRadius, spacing } from "../tokens";
+import { borderRadius, spacing } from '../tokens';
/**
* Button component design tokens
* Maps global tokens to button-specific semantics
*/
export const buttonTokens = {
- /** Button heights for each size */
- height: {
- sm: spacing[9], // 2.25rem (36px)
- md: spacing[10], // 2.5rem (40px)
- lg: spacing[11], // 2.75rem (44px)
- icon: spacing[10], // 2.5rem (40px) - square
- },
+ /** Button heights for each size */
+ height: {
+ sm: spacing[9], // 2.25rem (36px)
+ md: spacing[10], // 2.5rem (40px)
+ lg: spacing[11], // 2.75rem (44px)
+ icon: spacing[10], // 2.5rem (40px) - square
+ },
- /** Horizontal padding for each size */
- padding: {
- x: {
- sm: spacing[3], // 0.75rem (12px)
- md: spacing[4], // 1rem (16px)
- lg: spacing[8], // 2rem (32px)
- },
- },
+ /** Horizontal padding for each size */
+ padding: {
+ x: {
+ sm: spacing[3], // 0.75rem (12px)
+ md: spacing[4], // 1rem (16px)
+ lg: spacing[8], // 2rem (32px)
+ },
+ },
- /** Gap between icon and text */
- gap: spacing[2], // 0.5rem (8px)
+ /** Gap between icon and text */
+ gap: spacing[2], // 0.5rem (8px)
- /** Border radius */
- borderRadius: borderRadius.md,
+ /** Border radius */
+ borderRadius: borderRadius.md,
- /** Transition duration */
- transitionDuration: "200ms",
+ /** Transition duration */
+ transitionDuration: '200ms',
} as const;
diff --git a/packages/ui/src/Button/types.ts b/packages/ui/src/Button/types.ts
index 79da1bf..b06988e 100644
--- a/packages/ui/src/Button/types.ts
+++ b/packages/ui/src/Button/types.ts
@@ -1,34 +1,34 @@
-import type { ButtonHTMLAttributes } from "react";
+import type { ButtonHTMLAttributes } from 'react';
/**
* Button component props
*/
export interface ButtonProps extends ButtonHTMLAttributes {
- /**
- * Visual variant of the button
- * @default 'default'
- */
- variant?:
- | "default"
- | "destructive"
- | "outline"
- | "outline-dotted"
- | "secondary"
- | "ghost"
- | "link"
- | "blueprint";
+ /**
+ * Visual variant of the button
+ * @default 'default'
+ */
+ variant?:
+ | 'default'
+ | 'destructive'
+ | 'outline'
+ | 'outline-dotted'
+ | 'secondary'
+ | 'ghost'
+ | 'link'
+ | 'blueprint';
- /**
- * Size of the button
- * @default 'md'
- */
- size?: "sm" | "md" | "lg" | "icon";
+ /**
+ * Size of the button
+ * @default 'md'
+ */
+ size?: 'sm' | 'md' | 'lg' | 'icon';
- /**
- * Loading state - disables button and shows loading indicator
- * @default false
- */
- loading?: boolean;
+ /**
+ * Loading state - disables button and shows loading indicator
+ * @default false
+ */
+ loading?: boolean;
}
/**
diff --git a/packages/ui/src/Button/variants.ts b/packages/ui/src/Button/variants.ts
index 3fa6a7e..45d66d9 100644
--- a/packages/ui/src/Button/variants.ts
+++ b/packages/ui/src/Button/variants.ts
@@ -1,112 +1,110 @@
-import { createVariants } from "../system/variants";
+import { createVariants } from '../system/variants';
/**
* Button variant definitions
* Uses custom variant system for type-safe class composition
*/
export const buttonVariants = createVariants({
- base: [
- // Layout
- "inline-flex items-center justify-center gap-2",
- // Typography
- "font-medium text-sm",
- "whitespace-nowrap",
- // Borders & Radius
- "rounded-md",
- // Transitions
- "transition-base",
- // Focus
- "focus-ring",
- // Disabled state
- "disabled:pointer-events-none disabled:opacity-50",
- ].join(" "),
+ base: [
+ // Layout
+ 'inline-flex items-center justify-center gap-2',
+ // Typography
+ 'font-medium text-sm',
+ 'whitespace-nowrap',
+ // Borders & Radius
+ 'rounded-md',
+ // Transitions
+ 'transition-base',
+ // Focus
+ 'focus-ring',
+ // Disabled state
+ 'disabled:pointer-events-none disabled:opacity-50',
+ ].join(' '),
- variants: {
- variant: {
- default: [
- "bg-primary text-primary-foreground",
- "hover:bg-primary/90",
- "active:bg-primary/80",
- ].join(" "),
+ variants: {
+ variant: {
+ default: [
+ 'bg-primary text-primary-foreground',
+ 'hover:bg-primary/90',
+ 'active:bg-primary/80',
+ ].join(' '),
- destructive: [
- "bg-error text-error-foreground",
- "hover:bg-error/90",
- "active:bg-error/80",
- ].join(" "),
+ destructive: [
+ 'bg-error text-error-foreground',
+ 'hover:bg-error/90',
+ 'active:bg-error/80',
+ ].join(' '),
- outline: [
- "border border-border bg-transparent",
- "hover:bg-accent hover:text-accent-foreground hover:border-border-strong",
- "active:bg-accent/80",
- ].join(" "),
+ outline: [
+ 'border border-border bg-transparent',
+ 'hover:bg-accent hover:text-accent-foreground hover:border-border-strong',
+ 'active:bg-accent/80',
+ ].join(' '),
- "outline-dotted": [
- "border border-dotted border-border bg-transparent",
- "hover:bg-accent hover:text-accent-foreground hover:border-border-strong",
- "active:bg-accent/80",
- ].join(" "),
+ 'outline-dotted': [
+ 'border border-dotted border-border bg-transparent',
+ 'hover:bg-accent hover:text-accent-foreground hover:border-border-strong',
+ 'active:bg-accent/80',
+ ].join(' '),
- blueprint: [
- "border border-dotted border-border bg-card",
- "hover:shadow-blueprint-hover",
- "active:bg-card/90",
- ].join(" "),
+ blueprint: [
+ 'border border-dotted border-border bg-card',
+ 'hover:shadow-blueprint-hover',
+ 'active:bg-card/90',
+ ].join(' '),
- secondary: [
- "bg-secondary text-secondary-foreground",
- "hover:bg-secondary/80",
- "active:bg-secondary/70",
- ].join(" "),
+ secondary: [
+ 'bg-secondary text-secondary-foreground',
+ 'hover:bg-secondary/80',
+ 'active:bg-secondary/70',
+ ].join(' '),
- ghost: [
- "text-foreground",
- "hover:bg-accent hover:text-accent-foreground",
- "active:bg-accent/80",
- ].join(" "),
+ ghost: [
+ 'text-foreground',
+ 'hover:bg-accent hover:text-accent-foreground',
+ 'active:bg-accent/80',
+ ].join(' '),
- link: [
- "text-primary underline-offset-4",
- "hover:underline",
- "active:text-primary/80",
- ].join(" "),
- },
+ link: ['text-primary underline-offset-4', 'hover:underline', 'active:text-primary/80'].join(
+ ' '
+ ),
+ },
- size: {
- sm: "h-9 px-3 text-sm",
- md: "h-10 px-4 text-base",
- lg: "h-11 px-8 text-lg",
- icon: "h-10 w-10 p-0",
- },
+ size: {
+ sm: 'h-9 px-3 text-sm',
+ md: 'h-10 px-4 text-base',
+ lg: 'h-11 px-8 text-lg',
+ icon: 'h-10 w-10 p-0',
+ },
- loading: {
- true: "cursor-wait",
- false: "",
- },
- },
+ loading: {
+ true: 'cursor-wait',
+ false: '',
+ },
+ },
- compoundVariants: [
- {
- conditions: { variant: "outline", size: "sm" },
- className: "border-1",
- },
- {
- conditions: { variant: "link", size: "sm" },
- className: "px-0",
- },
- {
- conditions: { variant: "link", size: "md" },
- className: "px-0",
- },
- {
- conditions: { variant: "link", size: "lg" },
- className: "px-0",
- },
- ],
+ compoundVariants: [
+ {
+ conditions: { variant: 'outline', size: 'sm' },
+ className: 'border-1',
+ },
+ {
+ conditions: { variant: 'link', size: 'sm' },
+ className: 'px-0',
+ },
+ {
+ conditions: { variant: 'link', size: 'md' },
+ className: 'px-0',
+ },
+ {
+ conditions: { variant: 'link', size: 'lg' },
+ className: 'px-0',
+ },
+ ],
- defaultVariants: {
- variant: "default",
- size: "md",
- loading: "false",
- },
+ defaultVariants: {
+ variant: 'default',
+ size: 'md',
+ loading: 'false',
+ },
});
diff --git a/packages/ui/src/Card/Card.test.tsx b/packages/ui/src/Card/Card.test.tsx
index 3696eae..fce388a 100644
--- a/packages/ui/src/Card/Card.test.tsx
+++ b/packages/ui/src/Card/Card.test.tsx
@@ -1,644 +1,625 @@
-import { render, screen } from "@testing-library/react";
-import { describe, expect, it } from "vitest";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "./Card";
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './Card';
-describe("Card", () => {
- describe("Rendering", () => {
- it("renders a card element", () => {
- render(Card content);
- const card = screen.getByTestId("card");
- expect(card).toBeInTheDocument();
- expect(card.tagName).toBe("DIV");
- });
+describe('Card', () => {
+ describe('Rendering', () => {
+ it('renders a card element', () => {
+ render(Card content);
+ const card = screen.getByTestId('card');
+ expect(card).toBeInTheDocument();
+ expect(card.tagName).toBe('DIV');
+ });
- it("renders with all sub-components", () => {
- render(
-
-
- Title
-
- Description
-
-
- Content
- Footer
- ,
- );
+ it('renders with all sub-components', () => {
+ render(
+
+
+ Title
+ Description
+
+ Content
+ Footer
+
+ );
- expect(screen.getByTestId("card")).toBeInTheDocument();
- expect(screen.getByTestId("header")).toBeInTheDocument();
- expect(screen.getByTestId("title")).toBeInTheDocument();
- expect(screen.getByTestId("description")).toBeInTheDocument();
- expect(screen.getByTestId("content")).toBeInTheDocument();
- expect(screen.getByTestId("footer")).toBeInTheDocument();
- });
- });
+ expect(screen.getByTestId('card')).toBeInTheDocument();
+ expect(screen.getByTestId('header')).toBeInTheDocument();
+ expect(screen.getByTestId('title')).toBeInTheDocument();
+ expect(screen.getByTestId('description')).toBeInTheDocument();
+ expect(screen.getByTestId('content')).toBeInTheDocument();
+ expect(screen.getByTestId('footer')).toBeInTheDocument();
+ });
+ });
- describe("Card Variants", () => {
- it("applies default variant classes", () => {
- render(
-
- Default
- ,
- );
- const card = screen.getByTestId("card");
- expect(card.className).toContain("border-dotted");
- expect(card.className).toContain("bg-card");
- expect(card.className).toContain("shadow-sm");
- });
+ describe('Card Variants', () => {
+ it('applies default variant classes', () => {
+ render(
+
+ Default
+
+ );
+ const card = screen.getByTestId('card');
+ expect(card.className).toContain('border-dotted');
+ expect(card.className).toContain('bg-card');
+ expect(card.className).toContain('shadow-sm');
+ });
- it("applies elevated variant classes", () => {
- render(
-
- Elevated
- ,
- );
- const card = screen.getByTestId("card");
- expect(card.className).toContain("border-dotted");
- expect(card.className).toContain("bg-surface-elevated");
- expect(card.className).toContain("shadow-md");
- });
+ it('applies elevated variant classes', () => {
+ render(
+
+ Elevated
+
+ );
+ const card = screen.getByTestId('card');
+ expect(card.className).toContain('border-dotted');
+ expect(card.className).toContain('bg-surface-elevated');
+ expect(card.className).toContain('shadow-md');
+ });
- it("applies outline variant classes", () => {
- render(
-
- Outline
- ,
- );
- const card = screen.getByTestId("card");
- expect(card.className).toContain("border-dotted");
- expect(card.className).toContain("border-2");
- expect(card.className).toContain("bg-transparent");
- });
+ it('applies outline variant classes', () => {
+ render(
+
+ Outline
+
+ );
+ const card = screen.getByTestId('card');
+ expect(card.className).toContain('border-dotted');
+ expect(card.className).toContain('border-2');
+ expect(card.className).toContain('bg-transparent');
+ });
- it("applies ghost variant classes", () => {
- render(
-
- Ghost
- ,
- );
- const card = screen.getByTestId("card");
- expect(card.className).toContain("bg-transparent");
- });
- });
+ it('applies ghost variant classes', () => {
+ render(
+
+ Ghost
+
+ );
+ const card = screen.getByTestId('card');
+ expect(card.className).toContain('bg-transparent');
+ });
+ });
- describe("Card Padding", () => {
- it("applies no padding by default", () => {
- render(No padding);
- const card = screen.getByTestId("card");
- expect(card.className).toContain("p-0");
- });
+ describe('Card Padding', () => {
+ it('applies no padding by default', () => {
+ render(No padding);
+ const card = screen.getByTestId('card');
+ expect(card.className).toContain('p-0');
+ });
- it("applies small padding", () => {
- render(
-
- Small padding
- ,
- );
- const card = screen.getByTestId("card");
- expect(card.className).toContain("p-4");
- });
+ it('applies small padding', () => {
+ render(
+
+ Small padding
+
+ );
+ const card = screen.getByTestId('card');
+ expect(card.className).toContain('p-4');
+ });
- it("applies medium padding", () => {
- render(
-
- Medium padding
- ,
- );
- const card = screen.getByTestId("card");
- expect(card.className).toContain("p-6");
- });
+ it('applies medium padding', () => {
+ render(
+
+ Medium padding
+
+ );
+ const card = screen.getByTestId('card');
+ expect(card.className).toContain('p-6');
+ });
- it("applies large padding", () => {
- render(
-
- Large padding
- ,
- );
- const card = screen.getByTestId("card");
- expect(card.className).toContain("p-8");
- });
- });
+ it('applies large padding', () => {
+ render(
+
+ Large padding
+
+ );
+ const card = screen.getByTestId('card');
+ expect(card.className).toContain('p-8');
+ });
+ });
- describe("CardHeader", () => {
- it("renders with default medium padding", () => {
- render(
-
- Header
- ,
- );
- const header = screen.getByTestId("header");
- expect(header.className).toContain("p-6");
- });
+ describe('CardHeader', () => {
+ it('renders with default medium padding', () => {
+ render(
+
+ Header
+
+ );
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('p-6');
+ });
- it("applies small padding", () => {
- render(
-
-
- Header
-
- ,
- );
- const header = screen.getByTestId("header");
- expect(header.className).toContain("p-4");
- });
+ it('applies small padding', () => {
+ render(
+
+
+ Header
+
+
+ );
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('p-4');
+ });
- it("applies large padding", () => {
- render(
-
-
- Header
-
- ,
- );
- const header = screen.getByTestId("header");
- expect(header.className).toContain("p-8");
- });
+ it('applies large padding', () => {
+ render(
+
+
+ Header
+
+
+ );
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('p-8');
+ });
- it("applies no padding", () => {
- render(
-
-
- Header
-
- ,
- );
- const header = screen.getByTestId("header");
- expect(header.className).toContain("p-0");
- });
- });
+ it('applies no padding', () => {
+ render(
+
+
+ Header
+
+
+ );
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('p-0');
+ });
+ });
- describe("CardTitle", () => {
- it("renders as h3 by default", () => {
- render(
-
-
- Title
-
- ,
- );
- const title = screen.getByTestId("title");
- expect(title.tagName).toBe("H3");
- });
+ describe('CardTitle', () => {
+ it('renders as h3 by default', () => {
+ render(
+
+
+ Title
+
+
+ );
+ const title = screen.getByTestId('title');
+ expect(title.tagName).toBe('H3');
+ });
- it("renders as h1 when specified", () => {
- render(
-
-
-
- Title
-
-
- ,
- );
- const title = screen.getByTestId("title");
- expect(title.tagName).toBe("H1");
- });
+ it('renders as h1 when specified', () => {
+ render(
+
+
+
+ Title
+
+
+
+ );
+ const title = screen.getByTestId('title');
+ expect(title.tagName).toBe('H1');
+ });
- it("renders as h2 when specified", () => {
- render(
-
-
-
- Title
-
-
- ,
- );
- const title = screen.getByTestId("title");
- expect(title.tagName).toBe("H2");
- });
+ it('renders as h2 when specified', () => {
+ render(
+
+
+
+ Title
+
+
+
+ );
+ const title = screen.getByTestId('title');
+ expect(title.tagName).toBe('H2');
+ });
- it("applies title classes", () => {
- render(
-
-
- Title
-
- ,
- );
- const title = screen.getByTestId("title");
- expect(title.className).toContain("text-2xl");
- expect(title.className).toContain("font-semibold");
- expect(title.className).toContain("leading-none");
- });
- });
+ it('applies title classes', () => {
+ render(
+
+
+ Title
+
+
+ );
+ const title = screen.getByTestId('title');
+ expect(title.className).toContain('text-2xl');
+ expect(title.className).toContain('font-semibold');
+ expect(title.className).toContain('leading-none');
+ });
+ });
- describe("CardDescription", () => {
- it("renders as a paragraph", () => {
- render(
-
-
-
- Description text
-
-
- ,
- );
- const description = screen.getByTestId("description");
- expect(description.tagName).toBe("P");
- });
+ describe('CardDescription', () => {
+ it('renders as a paragraph', () => {
+ render(
+
+
+ Description text
+
+
+ );
+ const description = screen.getByTestId('description');
+ expect(description.tagName).toBe('P');
+ });
- it("applies description classes", () => {
- render(
-
-
-
- Description
-
-
- ,
- );
- const description = screen.getByTestId("description");
- expect(description.className).toContain("text-sm");
- expect(description.className).toContain("text-foreground-secondary");
- });
- });
+ it('applies description classes', () => {
+ render(
+
+
+ Description
+
+
+ );
+ const description = screen.getByTestId('description');
+ expect(description.className).toContain('text-sm');
+ expect(description.className).toContain('text-foreground-secondary');
+ });
+ });
- describe("CardContent", () => {
- it("renders with default medium padding", () => {
- render(
-
- Content
- ,
- );
- const content = screen.getByTestId("content");
- expect(content.className).toContain("p-6");
- expect(content.className).toContain("pt-0");
- });
+ describe('CardContent', () => {
+ it('renders with default medium padding', () => {
+ render(
+
+ Content
+
+ );
+ const content = screen.getByTestId('content');
+ expect(content.className).toContain('p-6');
+ expect(content.className).toContain('pt-0');
+ });
- it("applies small padding", () => {
- render(
-
-
- Content
-
- ,
- );
- const content = screen.getByTestId("content");
- expect(content.className).toContain("p-4");
- expect(content.className).toContain("pt-0");
- });
+ it('applies small padding', () => {
+ render(
+
+
+ Content
+
+
+ );
+ const content = screen.getByTestId('content');
+ expect(content.className).toContain('p-4');
+ expect(content.className).toContain('pt-0');
+ });
- it("applies large padding", () => {
- render(
-
-
- Content
-
- ,
- );
- const content = screen.getByTestId("content");
- expect(content.className).toContain("p-8");
- expect(content.className).toContain("pt-0");
- });
+ it('applies large padding', () => {
+ render(
+
+
+ Content
+
+
+ );
+ const content = screen.getByTestId('content');
+ expect(content.className).toContain('p-8');
+ expect(content.className).toContain('pt-0');
+ });
- it("applies no padding", () => {
- render(
-
-
- Content
-
- ,
- );
- const content = screen.getByTestId("content");
- expect(content.className).toContain("p-0");
- });
- });
+ it('applies no padding', () => {
+ render(
+
+
+ Content
+
+
+ );
+ const content = screen.getByTestId('content');
+ expect(content.className).toContain('p-0');
+ });
+ });
- describe("CardFooter", () => {
- it("renders with default medium padding", () => {
- render(
-
- Footer
- ,
- );
- const footer = screen.getByTestId("footer");
- expect(footer.className).toContain("p-6");
- expect(footer.className).toContain("pt-0");
- });
+ describe('CardFooter', () => {
+ it('renders with default medium padding', () => {
+ render(
+
+ Footer
+
+ );
+ const footer = screen.getByTestId('footer');
+ expect(footer.className).toContain('p-6');
+ expect(footer.className).toContain('pt-0');
+ });
- it("applies small padding", () => {
- render(
-
-
- Footer
-
- ,
- );
- const footer = screen.getByTestId("footer");
- expect(footer.className).toContain("p-4");
- expect(footer.className).toContain("pt-0");
- });
+ it('applies small padding', () => {
+ render(
+
+
+ Footer
+
+
+ );
+ const footer = screen.getByTestId('footer');
+ expect(footer.className).toContain('p-4');
+ expect(footer.className).toContain('pt-0');
+ });
- it("applies large padding", () => {
- render(
-
-
- Footer
-
- ,
- );
- const footer = screen.getByTestId("footer");
- expect(footer.className).toContain("p-8");
- expect(footer.className).toContain("pt-0");
- });
+ it('applies large padding', () => {
+ render(
+
+
+ Footer
+
+
+ );
+ const footer = screen.getByTestId('footer');
+ expect(footer.className).toContain('p-8');
+ expect(footer.className).toContain('pt-0');
+ });
- it("applies flex layout classes", () => {
- render(
-
- Footer
- ,
- );
- const footer = screen.getByTestId("footer");
- expect(footer.className).toContain("flex");
- expect(footer.className).toContain("items-center");
- });
- });
+ it('applies flex layout classes', () => {
+ render(
+
+ Footer
+
+ );
+ const footer = screen.getByTestId('footer');
+ expect(footer.className).toContain('flex');
+ expect(footer.className).toContain('items-center');
+ });
+ });
- describe("Custom className", () => {
- it("merges custom className with Card variant classes", () => {
- render(
-
- Custom
- ,
- );
- const card = screen.getByTestId("card");
- expect(card.className).toContain("custom-card");
- expect(card.className).toContain("rounded-lg");
- });
+ describe('Custom className', () => {
+ it('merges custom className with Card variant classes', () => {
+ render(
+
+ Custom
+
+ );
+ const card = screen.getByTestId('card');
+ expect(card.className).toContain('custom-card');
+ expect(card.className).toContain('rounded-lg');
+ });
- it("merges custom className with CardHeader", () => {
- render(
-
-
- Header
-
- ,
- );
- const header = screen.getByTestId("header");
- expect(header.className).toContain("custom-header");
- expect(header.className).toContain("flex");
- });
+ it('merges custom className with CardHeader', () => {
+ render(
+
+
+ Header
+
+
+ );
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('custom-header');
+ expect(header.className).toContain('flex');
+ });
- it("merges custom className with CardTitle", () => {
- render(
-
-
-
- Title
-
-
- ,
- );
- const title = screen.getByTestId("title");
- expect(title.className).toContain("custom-title");
- expect(title.className).toContain("font-semibold");
- });
- });
+ it('merges custom className with CardTitle', () => {
+ render(
+
+
+
+ Title
+
+
+
+ );
+ const title = screen.getByTestId('title');
+ expect(title.className).toContain('custom-title');
+ expect(title.className).toContain('font-semibold');
+ });
+ });
- describe("Ref Forwarding", () => {
- it("forwards ref to Card element", () => {
- let ref: HTMLDivElement | null = null;
- render(
- {
- ref = el;
- }}
- >
- Card
- ,
- );
- expect(ref).toBeInstanceOf(HTMLDivElement);
- expect(ref!.tagName).toBe("DIV");
- });
+ describe('Ref Forwarding', () => {
+ it('forwards ref to Card element', () => {
+ let ref: HTMLDivElement | null = null;
+ render(
+ {
+ ref = el;
+ }}
+ >
+ Card
+
+ );
+ expect(ref).toBeInstanceOf(HTMLDivElement);
+ expect(ref!.tagName).toBe('DIV');
+ });
- it("forwards ref to CardHeader element", () => {
- let ref: HTMLDivElement | null = null;
- render(
-
- {
- ref = el;
- }}
- >
- Header
-
- ,
- );
- expect(ref).toBeInstanceOf(HTMLDivElement);
- });
+ it('forwards ref to CardHeader element', () => {
+ let ref: HTMLDivElement | null = null;
+ render(
+
+ {
+ ref = el;
+ }}
+ >
+ Header
+
+
+ );
+ expect(ref).toBeInstanceOf(HTMLDivElement);
+ });
- it("forwards ref to CardTitle element", () => {
- let ref: HTMLHeadingElement | null = null;
- render(
-
-
- {
- ref = el;
- }}
- >
- Title
-
-
- ,
- );
- expect(ref).toBeInstanceOf(HTMLHeadingElement);
- expect(ref!.tagName).toBe("H3");
- });
+ it('forwards ref to CardTitle element', () => {
+ let ref: HTMLHeadingElement | null = null;
+ render(
+
+
+ {
+ ref = el;
+ }}
+ >
+ Title
+
+
+
+ );
+ expect(ref).toBeInstanceOf(HTMLHeadingElement);
+ expect(ref!.tagName).toBe('H3');
+ });
- it("forwards ref to CardDescription element", () => {
- let ref: HTMLParagraphElement | null = null;
- render(
-
-
- {
- ref = el;
- }}
- >
- Description
-
-
- ,
- );
- expect(ref).toBeInstanceOf(HTMLParagraphElement);
- });
+ it('forwards ref to CardDescription element', () => {
+ let ref: HTMLParagraphElement | null = null;
+ render(
+
+
+ {
+ ref = el;
+ }}
+ >
+ Description
+
+
+
+ );
+ expect(ref).toBeInstanceOf(HTMLParagraphElement);
+ });
- it("forwards ref to CardContent element", () => {
- let ref: HTMLDivElement | null = null;
- render(
-
- {
- ref = el;
- }}
- >
- Content
-
- ,
- );
- expect(ref).toBeInstanceOf(HTMLDivElement);
- });
+ it('forwards ref to CardContent element', () => {
+ let ref: HTMLDivElement | null = null;
+ render(
+
+ {
+ ref = el;
+ }}
+ >
+ Content
+
+
+ );
+ expect(ref).toBeInstanceOf(HTMLDivElement);
+ });
- it("forwards ref to CardFooter element", () => {
- let ref: HTMLDivElement | null = null;
- render(
-
- {
- ref = el;
- }}
- >
- Footer
-
- ,
- );
- expect(ref).toBeInstanceOf(HTMLDivElement);
- });
- });
+ it('forwards ref to CardFooter element', () => {
+ let ref: HTMLDivElement | null = null;
+ render(
+
+ {
+ ref = el;
+ }}
+ >
+ Footer
+
+
+ );
+ expect(ref).toBeInstanceOf(HTMLDivElement);
+ });
+ });
- describe("HTML Attributes", () => {
- it("passes through HTML attributes to Card", () => {
- render(
-
- Card
- ,
- );
- const card = screen.getByTestId("card");
- expect(card).toHaveAttribute("id", "test-card");
- });
+ describe('HTML Attributes', () => {
+ it('passes through HTML attributes to Card', () => {
+ render(
+
+ Card
+
+ );
+ const card = screen.getByTestId('card');
+ expect(card).toHaveAttribute('id', 'test-card');
+ });
- it("passes through onClick handler to Card", () => {
- let clicked = false;
- render(
- {
- clicked = true;
- }}
- data-testid="card"
- >
- Clickable Card
- ,
- );
- const card = screen.getByTestId("card");
- card.click();
- expect(clicked).toBe(true);
- });
- });
+ it('passes through onClick handler to Card', () => {
+ let clicked = false;
+ render(
+ {
+ clicked = true;
+ }}
+ data-testid="card"
+ >
+ Clickable Card
+
+ );
+ const card = screen.getByTestId('card');
+ card.click();
+ expect(clicked).toBe(true);
+ });
+ });
- describe("Base Classes", () => {
- it("Card always includes base classes", () => {
- render(Card);
- const card = screen.getByTestId("card");
- expect(card.className).toContain("relative");
- expect(card.className).toContain("rounded-lg");
- expect(card.className).toContain("transition-base");
- });
+ describe('Base Classes', () => {
+ it('Card always includes base classes', () => {
+ render(Card);
+ const card = screen.getByTestId('card');
+ expect(card.className).toContain('relative');
+ expect(card.className).toContain('rounded-lg');
+ expect(card.className).toContain('transition-base');
+ });
- it("CardHeader always includes base classes", () => {
- render(
-
- Header
- ,
- );
- const header = screen.getByTestId("header");
- expect(header.className).toContain("flex");
- expect(header.className).toContain("flex-col");
- });
+ it('CardHeader always includes base classes', () => {
+ render(
+
+ Header
+
+ );
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('flex');
+ expect(header.className).toContain('flex-col');
+ });
- it("CardTitle always includes base classes", () => {
- render(
-
-
- Title
-
- ,
- );
- const title = screen.getByTestId("title");
- expect(title.className).toContain("text-2xl");
- expect(title.className).toContain("font-semibold");
- expect(title.className).toContain("leading-none");
- expect(title.className).toContain("tracking-tight");
- });
+ it('CardTitle always includes base classes', () => {
+ render(
+
+
+ Title
+
+
+ );
+ const title = screen.getByTestId('title');
+ expect(title.className).toContain('text-2xl');
+ expect(title.className).toContain('font-semibold');
+ expect(title.className).toContain('leading-none');
+ expect(title.className).toContain('tracking-tight');
+ });
- it("CardDescription always includes base classes", () => {
- render(
-
-
-
- Description
-
-
- ,
- );
- const description = screen.getByTestId("description");
- expect(description.className).toContain("text-sm");
- expect(description.className).toContain("text-foreground-secondary");
- expect(description.className).toContain("mt-1.5");
- });
+ it('CardDescription always includes base classes', () => {
+ render(
+
+
+ Description
+
+
+ );
+ const description = screen.getByTestId('description');
+ expect(description.className).toContain('text-sm');
+ expect(description.className).toContain('text-foreground-secondary');
+ expect(description.className).toContain('mt-1.5');
+ });
- it("CardFooter always includes base classes", () => {
- render(
-
- Footer
- ,
- );
- const footer = screen.getByTestId("footer");
- expect(footer.className).toContain("flex");
- expect(footer.className).toContain("items-center");
- });
- });
+ it('CardFooter always includes base classes', () => {
+ render(
+
+ Footer
+
+ );
+ const footer = screen.getByTestId('footer');
+ expect(footer.className).toContain('flex');
+ expect(footer.className).toContain('items-center');
+ });
+ });
- describe("Complex Composition", () => {
- it("renders a complete card with all sections", () => {
- render(
-
-
- Card Title
- This is a card description
-
-
- Card content goes here
-
- Footer actions
- ,
- );
+ describe('Complex Composition', () => {
+ it('renders a complete card with all sections', () => {
+ render(
+
+
+ Card Title
+ This is a card description
+
+ Card content goes here
+ Footer actions
+
+ );
- const card = screen.getByTestId("card");
- expect(card).toBeInTheDocument();
- expect(screen.getByText("Card Title")).toBeInTheDocument();
- expect(
- screen.getByText("This is a card description"),
- ).toBeInTheDocument();
- expect(screen.getByText("Card content goes here")).toBeInTheDocument();
- expect(screen.getByText("Footer actions")).toBeInTheDocument();
- });
+ const card = screen.getByTestId('card');
+ expect(card).toBeInTheDocument();
+ expect(screen.getByText('Card Title')).toBeInTheDocument();
+ expect(screen.getByText('This is a card description')).toBeInTheDocument();
+ expect(screen.getByText('Card content goes here')).toBeInTheDocument();
+ expect(screen.getByText('Footer actions')).toBeInTheDocument();
+ });
- it("maintains proper structure hierarchy", () => {
- render(
-
-
- Title
-
- Content
- ,
- );
+ it('maintains proper structure hierarchy', () => {
+ render(
+
+
+ Title
+
+ Content
+
+ );
- const card = screen.getByTestId("card");
- const header = screen.getByTestId("header");
- const content = screen.getByTestId("content");
+ const card = screen.getByTestId('card');
+ const header = screen.getByTestId('header');
+ const content = screen.getByTestId('content');
- expect(card.contains(header)).toBe(true);
- expect(card.contains(content)).toBe(true);
- });
- });
+ expect(card.contains(header)).toBe(true);
+ expect(card.contains(content)).toBe(true);
+ });
+ });
});
diff --git a/packages/ui/src/Card/Card.tsx b/packages/ui/src/Card/Card.tsx
index d458f9f..7ad8ba0 100644
--- a/packages/ui/src/Card/Card.tsx
+++ b/packages/ui/src/Card/Card.tsx
@@ -1,21 +1,21 @@
-import { cn } from "@tpmjs/utils/cn";
-import { forwardRef } from "react";
+import { cn } from '@tpmjs/utils/cn';
+import { forwardRef } from 'react';
import type {
- CardContentProps,
- CardDescriptionProps,
- CardFooterProps,
- CardHeaderProps,
- CardProps,
- CardTitleProps,
-} from "./types";
+ CardContentProps,
+ CardDescriptionProps,
+ CardFooterProps,
+ CardHeaderProps,
+ CardProps,
+ CardTitleProps,
+} from './types';
import {
- cardContentVariants,
- cardDescriptionVariants,
- cardFooterVariants,
- cardHeaderVariants,
- cardTitleVariants,
- cardVariants,
-} from "./variants";
+ cardContentVariants,
+ cardDescriptionVariants,
+ cardFooterVariants,
+ cardHeaderVariants,
+ cardTitleVariants,
+ cardVariants,
+} from './variants';
/**
* Card component
@@ -42,24 +42,24 @@ import {
* ```
*/
export const Card = forwardRef(
- ({ className, variant = "default", padding = "none", ...props }, ref) => {
- return (
-
- );
- },
+ ({ className, variant = 'default', padding = 'none', ...props }, ref) => {
+ return (
+
+ );
+ }
);
-Card.displayName = "Card";
+Card.displayName = 'Card';
/**
* CardHeader component
@@ -67,23 +67,23 @@ Card.displayName = "Card";
* Header section of a card, typically contains title and description.
*/
export const CardHeader = forwardRef(
- ({ className, padding = "md", ...props }, ref) => {
- return (
-
- );
- },
+ ({ className, padding = 'md', ...props }, ref) => {
+ return (
+
+ );
+ }
);
-CardHeader.displayName = "CardHeader";
+CardHeader.displayName = 'CardHeader';
/**
* CardTitle component
@@ -91,39 +91,26 @@ CardHeader.displayName = "CardHeader";
* Title heading for a card.
*/
export const CardTitle = forwardRef(
- ({ className, as = "h3", ...props }, ref) => {
- const Component = as;
- return (
-
- );
- },
+ ({ className, as = 'h3', ...props }, ref) => {
+ const Component = as;
+ return ;
+ }
);
-CardTitle.displayName = "CardTitle";
+CardTitle.displayName = 'CardTitle';
/**
* CardDescription component
*
* Description text for a card, typically placed below the title.
*/
-export const CardDescription = forwardRef<
- HTMLParagraphElement,
- CardDescriptionProps
->(({ className, ...props }, ref) => {
- return (
-
- );
-});
+export const CardDescription = forwardRef(
+ ({ className, ...props }, ref) => {
+ return ;
+ }
+);
-CardDescription.displayName = "CardDescription";
+CardDescription.displayName = 'CardDescription';
/**
* CardContent component
@@ -131,23 +118,23 @@ CardDescription.displayName = "CardDescription";
* Main content area of a card.
*/
export const CardContent = forwardRef(
- ({ className, padding = "md", ...props }, ref) => {
- return (
-
- );
- },
+ ({ className, padding = 'md', ...props }, ref) => {
+ return (
+
+ );
+ }
);
-CardContent.displayName = "CardContent";
+CardContent.displayName = 'CardContent';
/**
* CardFooter component
@@ -155,20 +142,20 @@ CardContent.displayName = "CardContent";
* Footer section of a card, typically contains actions.
*/
export const CardFooter = forwardRef(
- ({ className, padding = "md", ...props }, ref) => {
- return (
-
- );
- },
+ ({ className, padding = 'md', ...props }, ref) => {
+ return (
+
+ );
+ }
);
-CardFooter.displayName = "CardFooter";
+CardFooter.displayName = 'CardFooter';
diff --git a/packages/ui/src/Card/tokens.ts b/packages/ui/src/Card/tokens.ts
index 979f6b9..0f9960f 100644
--- a/packages/ui/src/Card/tokens.ts
+++ b/packages/ui/src/Card/tokens.ts
@@ -1,45 +1,45 @@
-import { borderRadius, spacing } from "../tokens";
+import { borderRadius, spacing } from '../tokens';
/**
* Card component design tokens
* Maps global tokens to card-specific semantics
*/
export const cardTokens = {
- /** Card padding for each size */
- padding: {
- none: spacing[0], // 0
- sm: spacing[4], // 1rem (16px)
- md: spacing[6], // 1.5rem (24px)
- lg: spacing[8], // 2rem (32px)
- },
+ /** Card padding for each size */
+ padding: {
+ none: spacing[0], // 0
+ sm: spacing[4], // 1rem (16px)
+ md: spacing[6], // 1.5rem (24px)
+ lg: spacing[8], // 2rem (32px)
+ },
- /** Header/Content/Footer padding */
- section: {
- padding: {
- none: spacing[0], // 0
- sm: spacing[4], // 1rem (16px)
- md: spacing[6], // 1.5rem (24px)
- lg: spacing[8], // 2rem (32px)
- },
- gap: spacing[6], // 1.5rem (24px) - space between sections
- },
+ /** Header/Content/Footer padding */
+ section: {
+ padding: {
+ none: spacing[0], // 0
+ sm: spacing[4], // 1rem (16px)
+ md: spacing[6], // 1.5rem (24px)
+ lg: spacing[8], // 2rem (32px)
+ },
+ gap: spacing[6], // 1.5rem (24px) - space between sections
+ },
- /** Title spacing */
- title: {
- marginBottom: spacing[1.5], // 0.375rem (6px)
- },
+ /** Title spacing */
+ title: {
+ marginBottom: spacing[1.5], // 0.375rem (6px)
+ },
- /** Description spacing */
- description: {
- marginTop: spacing[2], // 0.5rem (8px)
- },
+ /** Description spacing */
+ description: {
+ marginTop: spacing[2], // 0.5rem (8px)
+ },
- /** Border radius */
- borderRadius: borderRadius.lg,
+ /** Border radius */
+ borderRadius: borderRadius.lg,
- /** Elevation shadows */
- elevation: {
- default: "shadow-sm",
- elevated: "shadow-md",
- },
+ /** Elevation shadows */
+ elevation: {
+ default: 'shadow-sm',
+ elevated: 'shadow-md',
+ },
} as const;
diff --git a/packages/ui/src/Card/types.ts b/packages/ui/src/Card/types.ts
index 833f422..14da7ab 100644
--- a/packages/ui/src/Card/types.ts
+++ b/packages/ui/src/Card/types.ts
@@ -1,42 +1,42 @@
-import type { HTMLAttributes } from "react";
+import type { HTMLAttributes } from 'react';
/**
* Card component props
*/
export interface CardProps extends HTMLAttributes {
- /**
- * Visual variant of the card
- * @default 'default'
- */
- variant?: "default" | "elevated" | "outline" | "blueprint" | "ghost";
+ /**
+ * Visual variant of the card
+ * @default 'default'
+ */
+ variant?: 'default' | 'elevated' | 'outline' | 'blueprint' | 'ghost';
- /**
- * Padding size for the card
- * @default 'md'
- */
- padding?: "none" | "sm" | "md" | "lg";
+ /**
+ * Padding size for the card
+ * @default 'md'
+ */
+ padding?: 'none' | 'sm' | 'md' | 'lg';
}
/**
* CardHeader component props
*/
export interface CardHeaderProps extends HTMLAttributes {
- /**
- * Padding size for the header
- * @default 'md'
- */
- padding?: "none" | "sm" | "md" | "lg";
+ /**
+ * Padding size for the header
+ * @default 'md'
+ */
+ padding?: 'none' | 'sm' | 'md' | 'lg';
}
/**
* CardTitle component props
*/
export interface CardTitleProps extends HTMLAttributes {
- /**
- * Heading level
- * @default 'h3'
- */
- as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
+ /**
+ * Heading level
+ * @default 'h3'
+ */
+ as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
}
/**
@@ -48,22 +48,22 @@ export type CardDescriptionProps = HTMLAttributes;
* CardContent component props
*/
export interface CardContentProps extends HTMLAttributes {
- /**
- * Padding size for the content
- * @default 'md'
- */
- padding?: "none" | "sm" | "md" | "lg";
+ /**
+ * Padding size for the content
+ * @default 'md'
+ */
+ padding?: 'none' | 'sm' | 'md' | 'lg';
}
/**
* CardFooter component props
*/
export interface CardFooterProps extends HTMLAttributes {
- /**
- * Padding size for the footer
- * @default 'md'
- */
- padding?: "none" | "sm" | "md" | "lg";
+ /**
+ * Padding size for the footer
+ * @default 'md'
+ */
+ padding?: 'none' | 'sm' | 'md' | 'lg';
}
/**
diff --git a/packages/ui/src/Card/variants.ts b/packages/ui/src/Card/variants.ts
index 115e98b..b3ff618 100644
--- a/packages/ui/src/Card/variants.ts
+++ b/packages/ui/src/Card/variants.ts
@@ -1,157 +1,151 @@
-import { createVariants } from "../system/variants";
+import { createVariants } from '../system/variants';
/**
* Card variant definitions
* Uses custom variant system for type-safe class composition
*/
export const cardVariants = createVariants({
- base: [
- // Layout
- "relative",
- // Borders & Radius
- "rounded-lg",
- // Transitions
- "transition-base",
- ].join(" "),
+ base: [
+ // Layout
+ 'relative',
+ // Borders & Radius
+ 'rounded-lg',
+ // Transitions
+ 'transition-base',
+ ].join(' '),
- variants: {
- variant: {
- default: [
- "border border-dotted border-border",
- "bg-card text-card-foreground",
- "shadow-sm",
- ].join(" "),
+ variants: {
+ variant: {
+ default: [
+ 'border border-dotted border-border',
+ 'bg-card text-card-foreground',
+ 'shadow-sm',
+ ].join(' '),
- elevated: [
- "border border-dotted border-border",
- "bg-surface-elevated text-card-foreground",
- "shadow-md",
- ].join(" "),
+ elevated: [
+ 'border border-dotted border-border',
+ 'bg-surface-elevated text-card-foreground',
+ 'shadow-md',
+ ].join(' '),
- outline: [
- "border-2 border-dotted border-border",
- "bg-transparent text-foreground",
- ].join(" "),
+ outline: ['border-2 border-dotted border-border', 'bg-transparent text-foreground'].join(' '),
- blueprint: [
- "border border-dotted border-border",
- "bg-card text-card-foreground",
- "shadow-blueprint",
- "hover:shadow-blueprint-hover",
- ].join(" "),
+ blueprint: [
+ 'border border-dotted border-border',
+ 'bg-card text-card-foreground',
+ 'shadow-blueprint',
+ 'hover:shadow-blueprint-hover',
+ ].join(' '),
- ghost: ["bg-transparent text-foreground"].join(" "),
- },
+ ghost: ['bg-transparent text-foreground'].join(' '),
+ },
- padding: {
- none: "p-0",
- sm: "p-4",
- md: "p-6",
- lg: "p-8",
- },
- },
+ padding: {
+ none: 'p-0',
+ sm: 'p-4',
+ md: 'p-6',
+ lg: 'p-8',
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- variant: "default",
- padding: "none", // Card itself has no padding, sections do
- },
+ defaultVariants: {
+ variant: 'default',
+ padding: 'none', // Card itself has no padding, sections do
+ },
});
/**
* CardHeader variant definitions
*/
export const cardHeaderVariants = createVariants({
- base: ["flex flex-col"].join(" "),
+ base: ['flex flex-col'].join(' '),
- variants: {
- padding: {
- none: "p-0",
- sm: "p-4",
- md: "p-6",
- lg: "p-8",
- },
- },
+ variants: {
+ padding: {
+ none: 'p-0',
+ sm: 'p-4',
+ md: 'p-6',
+ lg: 'p-8',
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- padding: "md",
- },
+ defaultVariants: {
+ padding: 'md',
+ },
});
/**
* CardTitle variant definitions
*/
export const cardTitleVariants = createVariants({
- base: [
- "text-2xl font-semibold leading-none tracking-tight",
- "text-foreground",
- ].join(" "),
+ base: ['text-2xl font-semibold leading-none tracking-tight', 'text-foreground'].join(' '),
- variants: {},
+ variants: {},
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {},
+ defaultVariants: {},
});
/**
* CardDescription variant definitions
*/
export const cardDescriptionVariants = createVariants({
- base: ["text-sm text-foreground-secondary", "mt-1.5"].join(" "),
+ base: ['text-sm text-foreground-secondary', 'mt-1.5'].join(' '),
- variants: {},
+ variants: {},
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {},
+ defaultVariants: {},
});
/**
* CardContent variant definitions
*/
export const cardContentVariants = createVariants({
- base: [
- // Base content styles
- ].join(" "),
+ base: [
+ // Base content styles
+ ].join(' '),
- variants: {
- padding: {
- none: "p-0",
- sm: "p-4 pt-0",
- md: "p-6 pt-0",
- lg: "p-8 pt-0",
- },
- },
+ variants: {
+ padding: {
+ none: 'p-0',
+ sm: 'p-4 pt-0',
+ md: 'p-6 pt-0',
+ lg: 'p-8 pt-0',
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- padding: "md",
- },
+ defaultVariants: {
+ padding: 'md',
+ },
});
/**
* CardFooter variant definitions
*/
export const cardFooterVariants = createVariants({
- base: ["flex items-center"].join(" "),
+ base: ['flex items-center'].join(' '),
- variants: {
- padding: {
- none: "p-0",
- sm: "p-4 pt-0",
- md: "p-6 pt-0",
- lg: "p-8 pt-0",
- },
- },
+ variants: {
+ padding: {
+ none: 'p-0',
+ sm: 'p-4 pt-0',
+ md: 'p-6 pt-0',
+ lg: 'p-8 pt-0',
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- padding: "md",
- },
+ defaultVariants: {
+ padding: 'md',
+ },
});
diff --git a/packages/ui/src/CodeBlock/CodeBlock.test.tsx b/packages/ui/src/CodeBlock/CodeBlock.test.tsx
index d6297cd..913a8c0 100644
--- a/packages/ui/src/CodeBlock/CodeBlock.test.tsx
+++ b/packages/ui/src/CodeBlock/CodeBlock.test.tsx
@@ -1,353 +1,331 @@
-import { render, screen, waitFor } from "@testing-library/react";
-import { beforeEach, describe, expect, it, vi } from "vitest";
-import { CodeBlock } from "./CodeBlock";
+import { render, screen, waitFor } from '@testing-library/react';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { CodeBlock } from './CodeBlock';
// Mock clipboard API
const mockClipboard = {
- writeText: vi.fn(),
+ writeText: vi.fn(),
};
-Object.defineProperty(navigator, "clipboard", {
- value: mockClipboard,
- writable: true,
- configurable: true,
+Object.defineProperty(navigator, 'clipboard', {
+ value: mockClipboard,
+ writable: true,
+ configurable: true,
});
-describe("CodeBlock", () => {
- beforeEach(() => {
- mockClipboard.writeText.mockClear();
- });
+describe('CodeBlock', () => {
+ beforeEach(() => {
+ mockClipboard.writeText.mockClear();
+ });
- describe("Rendering", () => {
- it("renders a code block element", () => {
- render();
- const codeblock = screen.getByTestId("codeblock");
- expect(codeblock).toBeInTheDocument();
- expect(codeblock.tagName).toBe("DIV");
- });
+ describe('Rendering', () => {
+ it('renders a code block element', () => {
+ render();
+ const codeblock = screen.getByTestId('codeblock');
+ expect(codeblock).toBeInTheDocument();
+ expect(codeblock.tagName).toBe('DIV');
+ });
- it("renders code content", () => {
- render();
- expect(screen.getByText("npm install package")).toBeInTheDocument();
- });
+ it('renders code content', () => {
+ render();
+ expect(screen.getByText('npm install package')).toBeInTheDocument();
+ });
- it("renders code in a code element", () => {
- render();
- const codeblock = screen.getByTestId("codeblock");
- const code = codeblock.querySelector("code");
- expect(code).toBeInTheDocument();
- expect(code?.textContent).toBe("test code");
- });
+ it('renders code in a code element', () => {
+ render();
+ const codeblock = screen.getByTestId('codeblock');
+ const code = codeblock.querySelector('code');
+ expect(code).toBeInTheDocument();
+ expect(code?.textContent).toBe('test code');
+ });
- it("preserves whitespace in code", () => {
- const multilineCode = "line 1\n line 2\n line 3";
- render();
- const codeblock = screen.getByTestId("codeblock");
- const code = codeblock.querySelector("code");
- expect(code?.textContent).toBe(multilineCode);
- });
- });
+ it('preserves whitespace in code', () => {
+ const multilineCode = 'line 1\n line 2\n line 3';
+ render();
+ const codeblock = screen.getByTestId('codeblock');
+ const code = codeblock.querySelector('code');
+ expect(code?.textContent).toBe(multilineCode);
+ });
+ });
- describe("Language", () => {
- it("sets data-language attribute", () => {
- render(
- ,
- );
- const codeblock = screen.getByTestId("codeblock");
- const code = codeblock.querySelector("code");
- expect(code).toHaveAttribute("data-language", "javascript");
- });
+ describe('Language', () => {
+ it('sets data-language attribute', () => {
+ render();
+ const codeblock = screen.getByTestId('codeblock');
+ const code = codeblock.querySelector('code');
+ expect(code).toHaveAttribute('data-language', 'javascript');
+ });
- it("defaults to text language", () => {
- render();
- const codeblock = screen.getByTestId("codeblock");
- const code = codeblock.querySelector("code");
- expect(code).toHaveAttribute("data-language", "text");
- });
+ it('defaults to text language', () => {
+ render();
+ const codeblock = screen.getByTestId('codeblock');
+ const code = codeblock.querySelector('code');
+ expect(code).toHaveAttribute('data-language', 'text');
+ });
- it("supports different languages", () => {
- const { rerender } = render(
- ,
- );
- let codeblock = screen.getByTestId("codeblock");
- let code = codeblock.querySelector("code");
- expect(code).toHaveAttribute("data-language", "python");
+ it('supports different languages', () => {
+ const { rerender } = render(
+
+ );
+ let codeblock = screen.getByTestId('codeblock');
+ let code = codeblock.querySelector('code');
+ expect(code).toHaveAttribute('data-language', 'python');
- rerender(
- ,
- );
- codeblock = screen.getByTestId("codeblock");
- code = codeblock.querySelector("code");
- expect(code).toHaveAttribute("data-language", "bash");
- });
- });
+ rerender();
+ codeblock = screen.getByTestId('codeblock');
+ code = codeblock.querySelector('code');
+ expect(code).toHaveAttribute('data-language', 'bash');
+ });
+ });
- describe("Size Variants", () => {
- it("applies small size", () => {
- render();
- const codeblock = screen.getByTestId("codeblock");
- const code = codeblock.querySelector("code");
- expect(code?.className).toContain("text-xs");
- expect(code?.className).toContain("p-3");
- });
+ describe('Size Variants', () => {
+ it('applies small size', () => {
+ render();
+ const codeblock = screen.getByTestId('codeblock');
+ const code = codeblock.querySelector('code');
+ expect(code?.className).toContain('text-xs');
+ expect(code?.className).toContain('p-3');
+ });
- it("applies medium size", () => {
- render();
- const codeblock = screen.getByTestId("codeblock");
- const code = codeblock.querySelector("code");
- expect(code?.className).toContain("text-sm");
- expect(code?.className).toContain("p-4");
- });
+ it('applies medium size', () => {
+ render();
+ const codeblock = screen.getByTestId('codeblock');
+ const code = codeblock.querySelector('code');
+ expect(code?.className).toContain('text-sm');
+ expect(code?.className).toContain('p-4');
+ });
- it("applies large size", () => {
- render();
- const codeblock = screen.getByTestId("codeblock");
- const code = codeblock.querySelector("code");
- expect(code?.className).toContain("text-base");
- expect(code?.className).toContain("p-6");
- });
+ it('applies large size', () => {
+ render();
+ const codeblock = screen.getByTestId('codeblock');
+ const code = codeblock.querySelector('code');
+ expect(code?.className).toContain('text-base');
+ expect(code?.className).toContain('p-6');
+ });
- it("uses md size by default", () => {
- render();
- const codeblock = screen.getByTestId("codeblock");
- const code = codeblock.querySelector("code");
- expect(code?.className).toContain("text-sm");
- expect(code?.className).toContain("p-4");
- });
- });
+ it('uses md size by default', () => {
+ render();
+ const codeblock = screen.getByTestId('codeblock');
+ const code = codeblock.querySelector('code');
+ expect(code?.className).toContain('text-sm');
+ expect(code?.className).toContain('p-4');
+ });
+ });
- describe("Copy Button", () => {
- it("shows copy button by default", () => {
- render();
- expect(screen.getByTestId("copy-button")).toBeInTheDocument();
- });
+ describe('Copy Button', () => {
+ it('shows copy button by default', () => {
+ render();
+ expect(screen.getByTestId('copy-button')).toBeInTheDocument();
+ });
- it("hides copy button when showCopy is false", () => {
- render();
- expect(screen.queryByTestId("copy-button")).not.toBeInTheDocument();
- });
+ it('hides copy button when showCopy is false', () => {
+ render();
+ expect(screen.queryByTestId('copy-button')).not.toBeInTheDocument();
+ });
- it("copy button has correct aria-label", () => {
- render();
- const button = screen.getByTestId("copy-button");
- expect(button).toHaveAttribute("aria-label", "Copy code");
- });
+ it('copy button has correct aria-label', () => {
+ render();
+ const button = screen.getByTestId('copy-button');
+ expect(button).toHaveAttribute('aria-label', 'Copy code');
+ });
- it("copy button is a button element", () => {
- render();
- const button = screen.getByTestId("copy-button");
- expect(button.tagName).toBe("BUTTON");
- expect(button).toHaveAttribute("type", "button");
- });
- });
+ it('copy button is a button element', () => {
+ render();
+ const button = screen.getByTestId('copy-button');
+ expect(button.tagName).toBe('BUTTON');
+ expect(button).toHaveAttribute('type', 'button');
+ });
+ });
- describe("Copy Functionality", () => {
- it("copies code to clipboard when button clicked", async () => {
- render();
- const button = screen.getByTestId("copy-button");
- button.click();
+ describe('Copy Functionality', () => {
+ it('copies code to clipboard when button clicked', async () => {
+ render();
+ const button = screen.getByTestId('copy-button');
+ button.click();
- await waitFor(() => {
- expect(mockClipboard.writeText).toHaveBeenCalledWith("test code");
- });
- });
+ await waitFor(() => {
+ expect(mockClipboard.writeText).toHaveBeenCalledWith('test code');
+ });
+ });
- it("shows check icon after successful copy", async () => {
- render();
- const button = screen.getByTestId("copy-button");
- button.click();
+ it('shows check icon after successful copy', async () => {
+ render();
+ const button = screen.getByTestId('copy-button');
+ button.click();
- await waitFor(() => {
- const icon = button.querySelector("svg");
- // Check icon has a different path than copy icon
- const path = icon?.querySelector("path");
- expect(path).toHaveAttribute(
- "d",
- "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
- );
- });
- });
+ await waitFor(() => {
+ const icon = button.querySelector('svg');
+ // Check icon has a different path than copy icon
+ const path = icon?.querySelector('path');
+ expect(path).toHaveAttribute(
+ 'd',
+ 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z'
+ );
+ });
+ });
- it('changes aria-label to "Copied!" after copy', async () => {
- render();
- const button = screen.getByTestId("copy-button");
- button.click();
+ it('changes aria-label to "Copied!" after copy', async () => {
+ render();
+ const button = screen.getByTestId('copy-button');
+ button.click();
- await waitFor(() => {
- expect(button).toHaveAttribute("aria-label", "Copied!");
- });
- });
+ await waitFor(() => {
+ expect(button).toHaveAttribute('aria-label', 'Copied!');
+ });
+ });
- it("reverts to copy icon after 2 seconds", async () => {
- vi.useFakeTimers();
+ it('reverts to copy icon after 2 seconds', async () => {
+ vi.useFakeTimers();
- try {
- render();
- const button = screen.getByTestId("copy-button");
- button.click();
+ try {
+ render();
+ const button = screen.getByTestId('copy-button');
+ button.click();
- // Wait for copy to complete
- await vi.waitFor(() => {
- expect(mockClipboard.writeText).toHaveBeenCalled();
- });
+ // Wait for copy to complete
+ await vi.waitFor(() => {
+ expect(mockClipboard.writeText).toHaveBeenCalled();
+ });
- // Verify check icon appears
- await vi.waitFor(() => {
- const icon = button.querySelector("svg");
- const path = icon?.querySelector("path");
- expect(path).toHaveAttribute(
- "d",
- "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
- );
- });
+ // Verify check icon appears
+ await vi.waitFor(() => {
+ const icon = button.querySelector('svg');
+ const path = icon?.querySelector('path');
+ expect(path).toHaveAttribute(
+ 'd',
+ 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z'
+ );
+ });
- // Fast forward 2 seconds
- vi.advanceTimersByTime(2000);
+ // Fast forward 2 seconds
+ vi.advanceTimersByTime(2000);
- // Verify copy icon is back
- await vi.waitFor(() => {
- const icon = button.querySelector("svg");
- const path = icon?.querySelector("path");
- expect(path).toHaveAttribute(
- "d",
- "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z",
- );
- });
- } finally {
- vi.useRealTimers();
- }
- });
+ // Verify copy icon is back
+ await vi.waitFor(() => {
+ const icon = button.querySelector('svg');
+ const path = icon?.querySelector('path');
+ expect(path).toHaveAttribute(
+ 'd',
+ 'M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z'
+ );
+ });
+ } finally {
+ vi.useRealTimers();
+ }
+ });
- it("handles clipboard API errors gracefully", async () => {
- const consoleErrorSpy = vi
- .spyOn(console, "error")
- .mockImplementation(() => {});
- mockClipboard.writeText.mockRejectedValueOnce(
- new Error("Clipboard not available"),
- );
+ it('handles clipboard API errors gracefully', async () => {
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
+ mockClipboard.writeText.mockRejectedValueOnce(new Error('Clipboard not available'));
- try {
- render();
- const button = screen.getByTestId("copy-button");
- button.click();
+ try {
+ render();
+ const button = screen.getByTestId('copy-button');
+ button.click();
- await waitFor(
- () => {
- expect(consoleErrorSpy).toHaveBeenCalled();
- },
- { timeout: 1000 },
- );
- } finally {
- consoleErrorSpy.mockRestore();
- }
- });
- });
+ await waitFor(
+ () => {
+ expect(consoleErrorSpy).toHaveBeenCalled();
+ },
+ { timeout: 1000 }
+ );
+ } finally {
+ consoleErrorSpy.mockRestore();
+ }
+ });
+ });
- describe("HTML Attributes", () => {
- it("passes through id attribute", () => {
- render(
- ,
- );
- const codeblock = screen.getByTestId("codeblock");
- expect(codeblock).toHaveAttribute("id", "codeblock-id");
- });
+ describe('HTML Attributes', () => {
+ it('passes through id attribute', () => {
+ render();
+ const codeblock = screen.getByTestId('codeblock');
+ expect(codeblock).toHaveAttribute('id', 'codeblock-id');
+ });
- it("passes through data attributes", () => {
- render(
- ,
- );
- const codeblock = screen.getByTestId("codeblock");
- expect(codeblock).toHaveAttribute("data-custom", "test");
- });
- });
+ it('passes through data attributes', () => {
+ render();
+ const codeblock = screen.getByTestId('codeblock');
+ expect(codeblock).toHaveAttribute('data-custom', 'test');
+ });
+ });
- describe("Custom className", () => {
- it("merges custom className with variant classes", () => {
- render(
- ,
- );
- const codeblock = screen.getByTestId("codeblock");
- expect(codeblock.className).toContain("custom-class");
- expect(codeblock.className).toContain("bg-background");
- expect(codeblock.className).toContain("border");
- });
- });
+ describe('Custom className', () => {
+ it('merges custom className with variant classes', () => {
+ render();
+ const codeblock = screen.getByTestId('codeblock');
+ expect(codeblock.className).toContain('custom-class');
+ expect(codeblock.className).toContain('bg-background');
+ expect(codeblock.className).toContain('border');
+ });
+ });
- describe("Ref Forwarding", () => {
- it("forwards ref to container element", () => {
- let ref: HTMLDivElement | null = null;
- render(
- {
- ref = el;
- }}
- />,
- );
- expect(ref).toBeInstanceOf(HTMLDivElement);
- expect(ref!.querySelector("code")).toBeInTheDocument();
- });
- });
+ describe('Ref Forwarding', () => {
+ it('forwards ref to container element', () => {
+ let ref: HTMLDivElement | null = null;
+ render(
+ {
+ ref = el;
+ }}
+ />
+ );
+ expect(ref).toBeInstanceOf(HTMLDivElement);
+ expect(ref!.querySelector('code')).toBeInTheDocument();
+ });
+ });
- describe("Base Classes", () => {
- it("container includes base classes", () => {
- render();
- const codeblock = screen.getByTestId("codeblock");
- expect(codeblock.className).toContain("relative");
- expect(codeblock.className).toContain("bg-background");
- expect(codeblock.className).toContain("border");
- expect(codeblock.className).toContain("overflow-hidden");
- });
+ describe('Base Classes', () => {
+ it('container includes base classes', () => {
+ render();
+ const codeblock = screen.getByTestId('codeblock');
+ expect(codeblock.className).toContain('relative');
+ expect(codeblock.className).toContain('bg-background');
+ expect(codeblock.className).toContain('border');
+ expect(codeblock.className).toContain('overflow-hidden');
+ });
- it("code element includes base classes", () => {
- render();
- const codeblock = screen.getByTestId("codeblock");
- const code = codeblock.querySelector("code");
- expect(code?.className).toContain("block");
- expect(code?.className).toContain("font-mono");
- expect(code?.className).toContain("text-foreground-secondary");
- expect(code?.className).toContain("overflow-x-auto");
- expect(code?.className).toContain("whitespace-pre");
- });
- });
+ it('code element includes base classes', () => {
+ render();
+ const codeblock = screen.getByTestId('codeblock');
+ const code = codeblock.querySelector('code');
+ expect(code?.className).toContain('block');
+ expect(code?.className).toContain('font-mono');
+ expect(code?.className).toContain('text-foreground-secondary');
+ expect(code?.className).toContain('overflow-x-auto');
+ expect(code?.className).toContain('whitespace-pre');
+ });
+ });
- describe("Compound Scenarios", () => {
- it("works correctly with large size and custom language", () => {
- render(
- ,
- );
- const codeblock = screen.getByTestId("codeblock");
- const code = codeblock.querySelector("code");
- expect(code?.className).toContain("text-base");
- expect(code?.className).toContain("p-6");
- expect(code).toHaveAttribute("data-language", "python");
- });
+ describe('Compound Scenarios', () => {
+ it('works correctly with large size and custom language', () => {
+ render(
+
+ );
+ const codeblock = screen.getByTestId('codeblock');
+ const code = codeblock.querySelector('code');
+ expect(code?.className).toContain('text-base');
+ expect(code?.className).toContain('p-6');
+ expect(code).toHaveAttribute('data-language', 'python');
+ });
- it("works correctly with no copy button and custom className", () => {
- render(
- ,
- );
- const codeblock = screen.getByTestId("codeblock");
- expect(codeblock.className).toContain("my-custom-class");
- expect(screen.queryByTestId("copy-button")).not.toBeInTheDocument();
- });
- });
+ it('works correctly with no copy button and custom className', () => {
+ render(
+
+ );
+ const codeblock = screen.getByTestId('codeblock');
+ expect(codeblock.className).toContain('my-custom-class');
+ expect(screen.queryByTestId('copy-button')).not.toBeInTheDocument();
+ });
+ });
});
diff --git a/packages/ui/src/CodeBlock/CodeBlock.tsx b/packages/ui/src/CodeBlock/CodeBlock.tsx
index 50dc9e8..e274b92 100644
--- a/packages/ui/src/CodeBlock/CodeBlock.tsx
+++ b/packages/ui/src/CodeBlock/CodeBlock.tsx
@@ -1,12 +1,12 @@
-import { cn } from "@tpmjs/utils/cn";
-import { forwardRef, useState } from "react";
-import { Icon } from "../Icon/Icon";
-import type { CodeBlockProps } from "./types";
+import { cn } from '@tpmjs/utils/cn';
+import { forwardRef, useState } from 'react';
+import { Icon } from '../Icon/Icon';
+import type { CodeBlockProps } from './types';
import {
- codeBlockCodeVariants,
- codeBlockContainerVariants,
- codeBlockCopyButtonVariants,
-} from "./variants";
+ codeBlockCodeVariants,
+ codeBlockContainerVariants,
+ codeBlockCopyButtonVariants,
+} from './variants';
/**
* CodeBlock component
@@ -32,58 +32,44 @@ import {
* ```
*/
export const CodeBlock = forwardRef(
- (
- {
- className,
- code,
- language = "text",
- size = "md",
- showCopy = true,
- ...props
- },
- ref,
- ) => {
- const [copied, setCopied] = useState(false);
+ ({ className, code, language = 'text', size = 'md', showCopy = true, ...props }, ref) => {
+ const [copied, setCopied] = useState(false);
- const handleCopy = async () => {
- try {
- await navigator.clipboard.writeText(code);
- setCopied(true);
- setTimeout(() => setCopied(false), 2000);
- } catch (err) {
- // Silently fail if clipboard API is not available
- console.error("Failed to copy code:", err);
- }
- };
+ const handleCopy = async () => {
+ try {
+ await navigator.clipboard.writeText(code);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch (err) {
+ // Silently fail if clipboard API is not available
+ console.error('Failed to copy code:', err);
+ }
+ };
- return (
-
-
- {code}
-
- {showCopy && (
-
- )}
-
- );
- },
+ return (
+
+
+ {code}
+
+ {showCopy && (
+
+ )}
+
+ );
+ }
);
-CodeBlock.displayName = "CodeBlock";
+CodeBlock.displayName = 'CodeBlock';
diff --git a/packages/ui/src/CodeBlock/tokens.ts b/packages/ui/src/CodeBlock/tokens.ts
index 66fa425..b1886ca 100644
--- a/packages/ui/src/CodeBlock/tokens.ts
+++ b/packages/ui/src/CodeBlock/tokens.ts
@@ -1,24 +1,24 @@
-import { fontSize, spacing } from "../tokens";
+import { fontSize, spacing } from '../tokens';
/**
* CodeBlock component design tokens
* Maps global tokens to code block-specific semantics
*/
export const codeBlockTokens = {
- /** Padding for each size */
- padding: {
- sm: spacing[3], // 0.75rem (12px)
- md: spacing[4], // 1rem (16px)
- lg: spacing[6], // 1.5rem (24px)
- },
+ /** Padding for each size */
+ padding: {
+ sm: spacing[3], // 0.75rem (12px)
+ md: spacing[4], // 1rem (16px)
+ lg: spacing[6], // 1.5rem (24px)
+ },
- /** Font size for each size */
- fontSize: {
- sm: fontSize.xs, // 0.75rem (12px)
- md: fontSize.sm, // 0.875rem (14px)
- lg: fontSize.base, // 1rem (16px)
- },
+ /** Font size for each size */
+ fontSize: {
+ sm: fontSize.xs, // 0.75rem (12px)
+ md: fontSize.sm, // 0.875rem (14px)
+ lg: fontSize.base, // 1rem (16px)
+ },
- /** Border radius */
- radius: spacing[2], // 0.5rem (8px)
+ /** Border radius */
+ radius: spacing[2], // 0.5rem (8px)
} as const;
diff --git a/packages/ui/src/CodeBlock/types.ts b/packages/ui/src/CodeBlock/types.ts
index 10d041f..5e962a1 100644
--- a/packages/ui/src/CodeBlock/types.ts
+++ b/packages/ui/src/CodeBlock/types.ts
@@ -1,32 +1,31 @@
-import type { HTMLAttributes } from "react";
+import type { HTMLAttributes } from 'react';
/**
* CodeBlock component props
*/
-export interface CodeBlockProps
- extends Omit, "children"> {
- /**
- * Code content to display
- */
- code: string;
+export interface CodeBlockProps extends Omit, 'children'> {
+ /**
+ * Code content to display
+ */
+ code: string;
- /**
- * Programming language (for display/metadata only)
- * @default 'text'
- */
- language?: string;
+ /**
+ * Programming language (for display/metadata only)
+ * @default 'text'
+ */
+ language?: string;
- /**
- * Size variant
- * @default 'md'
- */
- size?: "sm" | "md" | "lg";
+ /**
+ * Size variant
+ * @default 'md'
+ */
+ size?: 'sm' | 'md' | 'lg';
- /**
- * Whether to show copy button
- * @default true
- */
- showCopy?: boolean;
+ /**
+ * Whether to show copy button
+ * @default true
+ */
+ showCopy?: boolean;
}
/**
diff --git a/packages/ui/src/CodeBlock/variants.ts b/packages/ui/src/CodeBlock/variants.ts
index c45166c..f7a2808 100644
--- a/packages/ui/src/CodeBlock/variants.ts
+++ b/packages/ui/src/CodeBlock/variants.ts
@@ -1,85 +1,85 @@
-import { createVariants } from "../system/variants";
+import { createVariants } from '../system/variants';
/**
* CodeBlock container variant definitions
*/
export const codeBlockContainerVariants = createVariants({
- base: [
- // Layout
- "relative",
- // Background
- "bg-background",
- // Border
- "border border-border rounded-lg",
- // Overflow
- "overflow-hidden",
- ].join(" "),
+ base: [
+ // Layout
+ 'relative',
+ // Background
+ 'bg-background',
+ // Border
+ 'border border-border rounded-lg',
+ // Overflow
+ 'overflow-hidden',
+ ].join(' '),
- variants: {},
+ variants: {},
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {},
+ defaultVariants: {},
});
/**
* CodeBlock code element variant definitions
*/
export const codeBlockCodeVariants = createVariants({
- base: [
- // Display
- "block",
- // Font
- "font-mono",
- // Color
- "text-foreground-secondary",
- // Overflow
- "overflow-x-auto",
- // Whitespace
- "whitespace-pre",
- ].join(" "),
+ base: [
+ // Display
+ 'block',
+ // Font
+ 'font-mono',
+ // Color
+ 'text-foreground-secondary',
+ // Overflow
+ 'overflow-x-auto',
+ // Whitespace
+ 'whitespace-pre',
+ ].join(' '),
- variants: {
- size: {
- sm: "text-xs p-3", // 12px font, 12px padding
- md: "text-sm p-4", // 14px font, 16px padding
- lg: "text-base p-6", // 16px font, 24px padding
- },
- },
+ variants: {
+ size: {
+ sm: 'text-xs p-3', // 12px font, 12px padding
+ md: 'text-sm p-4', // 14px font, 16px padding
+ lg: 'text-base p-6', // 16px font, 24px padding
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- size: "md",
- },
+ defaultVariants: {
+ size: 'md',
+ },
});
/**
* CodeBlock copy button variant definitions
*/
export const codeBlockCopyButtonVariants = createVariants({
- base: [
- // Position
- "absolute top-2 right-2",
- // Display
- "flex items-center justify-center",
- // Size
- "w-8 h-8",
- // Background
- "bg-surface-elevated hover:bg-accent",
- // Border
- "border border-border rounded",
- // Color
- "text-foreground-secondary hover:text-foreground",
- // Cursor
- "cursor-pointer",
- // Transition
- "transition-colors duration-200",
- ].join(" "),
+ base: [
+ // Position
+ 'absolute top-2 right-2',
+ // Display
+ 'flex items-center justify-center',
+ // Size
+ 'w-8 h-8',
+ // Background
+ 'bg-surface-elevated hover:bg-accent',
+ // Border
+ 'border border-border rounded',
+ // Color
+ 'text-foreground-secondary hover:text-foreground',
+ // Cursor
+ 'cursor-pointer',
+ // Transition
+ 'transition-colors duration-200',
+ ].join(' '),
- variants: {},
+ variants: {},
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {},
+ defaultVariants: {},
});
diff --git a/packages/ui/src/Container/Container.test.tsx b/packages/ui/src/Container/Container.test.tsx
index 997bc64..f566d1e 100644
--- a/packages/ui/src/Container/Container.test.tsx
+++ b/packages/ui/src/Container/Container.test.tsx
@@ -1,238 +1,238 @@
-import { render, screen } from "@testing-library/react";
-import { describe, expect, it } from "vitest";
-import { Container } from "./Container";
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { Container } from './Container';
-describe("Container", () => {
- describe("Rendering", () => {
- it("renders a container element", () => {
- render(Content);
- const container = screen.getByTestId("container");
- expect(container).toBeInTheDocument();
- expect(container.tagName).toBe("DIV");
- });
+describe('Container', () => {
+ describe('Rendering', () => {
+ it('renders a container element', () => {
+ render(Content);
+ const container = screen.getByTestId('container');
+ expect(container).toBeInTheDocument();
+ expect(container.tagName).toBe('DIV');
+ });
- it("renders children content", () => {
- render(Container content);
- expect(screen.getByText("Container content")).toBeInTheDocument();
- });
- });
+ it('renders children content', () => {
+ render(Container content);
+ expect(screen.getByText('Container content')).toBeInTheDocument();
+ });
+ });
- describe("Size Variants", () => {
- it("applies small size max-width", () => {
- render(
-
- Small
- ,
- );
- const container = screen.getByTestId("container");
- expect(container.className).toContain("max-w-screen-sm");
- });
+ describe('Size Variants', () => {
+ it('applies small size max-width', () => {
+ render(
+
+ Small
+
+ );
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('max-w-screen-sm');
+ });
- it("applies medium size max-width", () => {
- render(
-
- Medium
- ,
- );
- const container = screen.getByTestId("container");
- expect(container.className).toContain("max-w-screen-md");
- });
+ it('applies medium size max-width', () => {
+ render(
+
+ Medium
+
+ );
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('max-w-screen-md');
+ });
- it("applies large size max-width", () => {
- render(
-
- Large
- ,
- );
- const container = screen.getByTestId("container");
- expect(container.className).toContain("max-w-screen-lg");
- });
+ it('applies large size max-width', () => {
+ render(
+
+ Large
+
+ );
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('max-w-screen-lg');
+ });
- it("applies extra-large size max-width (default)", () => {
- render(
-
- XLarge
- ,
- );
- const container = screen.getByTestId("container");
- expect(container.className).toContain("max-w-screen-xl");
- });
+ it('applies extra-large size max-width (default)', () => {
+ render(
+
+ XLarge
+
+ );
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('max-w-screen-xl');
+ });
- it("applies 2xl size max-width", () => {
- render(
-
- 2XLarge
- ,
- );
- const container = screen.getByTestId("container");
- expect(container.className).toContain("max-w-screen-2xl");
- });
+ it('applies 2xl size max-width', () => {
+ render(
+
+ 2XLarge
+
+ );
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('max-w-screen-2xl');
+ });
- it("applies full width", () => {
- render(
-
- Full
- ,
- );
- const container = screen.getByTestId("container");
- expect(container.className).toContain("max-w-full");
- });
+ it('applies full width', () => {
+ render(
+
+ Full
+
+ );
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('max-w-full');
+ });
- it("uses xl size by default", () => {
- render(Default);
- const container = screen.getByTestId("container");
- expect(container.className).toContain("max-w-screen-xl");
- });
- });
+ it('uses xl size by default', () => {
+ render(Default);
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('max-w-screen-xl');
+ });
+ });
- describe("Padding Variants", () => {
- it("applies no padding", () => {
- render(
-
- None
- ,
- );
- const container = screen.getByTestId("container");
- expect(container.className).toContain("px-0");
- });
+ describe('Padding Variants', () => {
+ it('applies no padding', () => {
+ render(
+
+ None
+
+ );
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('px-0');
+ });
- it("applies small padding", () => {
- render(
-
- Small
- ,
- );
- const container = screen.getByTestId("container");
- expect(container.className).toContain("px-4");
- });
+ it('applies small padding', () => {
+ render(
+
+ Small
+
+ );
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('px-4');
+ });
- it("applies medium padding (default)", () => {
- render(
-
- Medium
- ,
- );
- const container = screen.getByTestId("container");
- expect(container.className).toContain("px-6");
- });
+ it('applies medium padding (default)', () => {
+ render(
+
+ Medium
+
+ );
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('px-6');
+ });
- it("applies large padding", () => {
- render(
-
- Large
- ,
- );
- const container = screen.getByTestId("container");
- expect(container.className).toContain("px-8");
- });
+ it('applies large padding', () => {
+ render(
+
+ Large
+
+ );
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('px-8');
+ });
- it("uses md padding by default", () => {
- render(Default);
- const container = screen.getByTestId("container");
- expect(container.className).toContain("px-6");
- });
- });
+ it('uses md padding by default', () => {
+ render(Default);
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('px-6');
+ });
+ });
- describe("Compound Scenarios", () => {
- it("works correctly with small size and no padding", () => {
- render(
-
- Small no padding
- ,
- );
- const container = screen.getByTestId("container");
- expect(container.className).toContain("max-w-screen-sm");
- expect(container.className).toContain("px-0");
- });
+ describe('Compound Scenarios', () => {
+ it('works correctly with small size and no padding', () => {
+ render(
+
+ Small no padding
+
+ );
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('max-w-screen-sm');
+ expect(container.className).toContain('px-0');
+ });
- it("works correctly with full width and large padding", () => {
- render(
-
- Full large padding
- ,
- );
- const container = screen.getByTestId("container");
- expect(container.className).toContain("max-w-full");
- expect(container.className).toContain("px-8");
- });
- });
+ it('works correctly with full width and large padding', () => {
+ render(
+
+ Full large padding
+
+ );
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('max-w-full');
+ expect(container.className).toContain('px-8');
+ });
+ });
- describe("HTML Attributes", () => {
- it("passes through id attribute", () => {
- render(
-
- Container
- ,
- );
- const container = screen.getByTestId("container");
- expect(container).toHaveAttribute("id", "container-id");
- });
+ describe('HTML Attributes', () => {
+ it('passes through id attribute', () => {
+ render(
+
+ Container
+
+ );
+ const container = screen.getByTestId('container');
+ expect(container).toHaveAttribute('id', 'container-id');
+ });
- it("passes through onClick handler", () => {
- let clicked = false;
- render(
- {
- clicked = true;
- }}
- data-testid="container"
- >
- Clickable
- ,
- );
- const container = screen.getByTestId("container");
- container.click();
- expect(clicked).toBe(true);
- });
+ it('passes through onClick handler', () => {
+ let clicked = false;
+ render(
+ {
+ clicked = true;
+ }}
+ data-testid="container"
+ >
+ Clickable
+
+ );
+ const container = screen.getByTestId('container');
+ container.click();
+ expect(clicked).toBe(true);
+ });
- it("passes through aria attributes", () => {
- render(
-
- Container
- ,
- );
- const container = screen.getByTestId("container");
- expect(container).toHaveAttribute("aria-label", "Main container");
- });
- });
+ it('passes through aria attributes', () => {
+ render(
+
+ Container
+
+ );
+ const container = screen.getByTestId('container');
+ expect(container).toHaveAttribute('aria-label', 'Main container');
+ });
+ });
- describe("Custom className", () => {
- it("merges custom className with variant classes", () => {
- render(
-
- Custom
- ,
- );
- const container = screen.getByTestId("container");
- expect(container.className).toContain("custom-class");
- expect(container.className).toContain("mx-auto");
- expect(container.className).toContain("w-full");
- });
- });
+ describe('Custom className', () => {
+ it('merges custom className with variant classes', () => {
+ render(
+
+ Custom
+
+ );
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('custom-class');
+ expect(container.className).toContain('mx-auto');
+ expect(container.className).toContain('w-full');
+ });
+ });
- describe("Ref Forwarding", () => {
- it("forwards ref to container element", () => {
- let ref: HTMLDivElement | null = null;
- render(
- {
- ref = el;
- }}
- >
- Container
- ,
- );
- expect(ref).toBeInstanceOf(HTMLDivElement);
- expect(ref!.tagName).toBe("DIV");
- });
- });
+ describe('Ref Forwarding', () => {
+ it('forwards ref to container element', () => {
+ let ref: HTMLDivElement | null = null;
+ render(
+ {
+ ref = el;
+ }}
+ >
+ Container
+
+ );
+ expect(ref).toBeInstanceOf(HTMLDivElement);
+ expect(ref!.tagName).toBe('DIV');
+ });
+ });
- describe("Base Classes", () => {
- it("always includes base classes", () => {
- render(Container);
- const container = screen.getByTestId("container");
- expect(container.className).toContain("mx-auto");
- expect(container.className).toContain("w-full");
- });
- });
+ describe('Base Classes', () => {
+ it('always includes base classes', () => {
+ render(Container);
+ const container = screen.getByTestId('container');
+ expect(container.className).toContain('mx-auto');
+ expect(container.className).toContain('w-full');
+ });
+ });
});
diff --git a/packages/ui/src/Container/Container.tsx b/packages/ui/src/Container/Container.tsx
index b99190e..a5d4ca2 100644
--- a/packages/ui/src/Container/Container.tsx
+++ b/packages/ui/src/Container/Container.tsx
@@ -1,7 +1,7 @@
-import { cn } from "@tpmjs/utils/cn";
-import { forwardRef } from "react";
-import type { ContainerProps } from "./types";
-import { containerVariants } from "./variants";
+import { cn } from '@tpmjs/utils/cn';
+import { forwardRef } from 'react';
+import type { ContainerProps } from './types';
+import { containerVariants } from './variants';
/**
* Container component
@@ -23,23 +23,23 @@ import { containerVariants } from "./variants";
* ```
*/
export const Container = forwardRef(
- ({ className, size = "xl", padding = "md", children, ...props }, ref) => {
- return (
-
- {children}
-
- );
- },
+ ({ className, size = 'xl', padding = 'md', children, ...props }, ref) => {
+ return (
+
+ {children}
+
+ );
+ }
);
-Container.displayName = "Container";
+Container.displayName = 'Container';
diff --git a/packages/ui/src/Container/tokens.ts b/packages/ui/src/Container/tokens.ts
index 2a37414..a3123e7 100644
--- a/packages/ui/src/Container/tokens.ts
+++ b/packages/ui/src/Container/tokens.ts
@@ -1,25 +1,25 @@
-import { spacing } from "../tokens";
+import { spacing } from '../tokens';
/**
* Container component design tokens
* Maps global tokens to container-specific semantics
*/
export const containerTokens = {
- /** Maximum widths for each size */
- maxWidth: {
- sm: "640px", // max-w-screen-sm
- md: "768px", // max-w-screen-md
- lg: "1024px", // max-w-screen-lg
- xl: "1280px", // max-w-screen-xl
- "2xl": "1536px", // max-w-screen-2xl
- full: "100%", // max-w-full
- },
+ /** Maximum widths for each size */
+ maxWidth: {
+ sm: '640px', // max-w-screen-sm
+ md: '768px', // max-w-screen-md
+ lg: '1024px', // max-w-screen-lg
+ xl: '1280px', // max-w-screen-xl
+ '2xl': '1536px', // max-w-screen-2xl
+ full: '100%', // max-w-full
+ },
- /** Horizontal padding for each size */
- padding: {
- none: spacing[0], // 0
- sm: spacing[4], // 1rem (16px)
- md: spacing[6], // 1.5rem (24px)
- lg: spacing[8], // 2rem (32px)
- },
+ /** Horizontal padding for each size */
+ padding: {
+ none: spacing[0], // 0
+ sm: spacing[4], // 1rem (16px)
+ md: spacing[6], // 1.5rem (24px)
+ lg: spacing[8], // 2rem (32px)
+ },
} as const;
diff --git a/packages/ui/src/Container/types.ts b/packages/ui/src/Container/types.ts
index e3cdb35..97c9b90 100644
--- a/packages/ui/src/Container/types.ts
+++ b/packages/ui/src/Container/types.ts
@@ -1,20 +1,20 @@
-import type { HTMLAttributes } from "react";
+import type { HTMLAttributes } from 'react';
/**
* Container component props
*/
export interface ContainerProps extends HTMLAttributes {
- /**
- * Maximum width of the container
- * @default 'xl'
- */
- size?: "sm" | "md" | "lg" | "xl" | "2xl" | "full";
+ /**
+ * Maximum width of the container
+ * @default 'xl'
+ */
+ size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full';
- /**
- * Horizontal padding
- * @default 'md'
- */
- padding?: "none" | "sm" | "md" | "lg";
+ /**
+ * Horizontal padding
+ * @default 'md'
+ */
+ padding?: 'none' | 'sm' | 'md' | 'lg';
}
/**
diff --git a/packages/ui/src/Container/variants.ts b/packages/ui/src/Container/variants.ts
index a013904..27482bc 100644
--- a/packages/ui/src/Container/variants.ts
+++ b/packages/ui/src/Container/variants.ts
@@ -1,37 +1,37 @@
-import { createVariants } from "../system/variants";
+import { createVariants } from '../system/variants';
/**
* Container variant definitions
* Uses custom variant system for type-safe class composition
*/
export const containerVariants = createVariants({
- base: [
- // Layout
- "mx-auto w-full",
- ].join(" "),
+ base: [
+ // Layout
+ 'mx-auto w-full',
+ ].join(' '),
- variants: {
- size: {
- sm: "max-w-screen-sm",
- md: "max-w-screen-md",
- lg: "max-w-screen-lg",
- xl: "max-w-screen-xl",
- "2xl": "max-w-screen-2xl",
- full: "max-w-full",
- },
+ variants: {
+ size: {
+ sm: 'max-w-screen-sm',
+ md: 'max-w-screen-md',
+ lg: 'max-w-screen-lg',
+ xl: 'max-w-screen-xl',
+ '2xl': 'max-w-screen-2xl',
+ full: 'max-w-full',
+ },
- padding: {
- none: "px-0",
- sm: "px-4",
- md: "px-6",
- lg: "px-8",
- },
- },
+ padding: {
+ none: 'px-0',
+ sm: 'px-4',
+ md: 'px-6',
+ lg: 'px-8',
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- size: "xl",
- padding: "md",
- },
+ defaultVariants: {
+ size: 'xl',
+ padding: 'md',
+ },
});
diff --git a/packages/ui/src/GridContainer/GridContainer.test.tsx b/packages/ui/src/GridContainer/GridContainer.test.tsx
index b863d15..478eb2e 100644
--- a/packages/ui/src/GridContainer/GridContainer.test.tsx
+++ b/packages/ui/src/GridContainer/GridContainer.test.tsx
@@ -1,317 +1,306 @@
-import { render, screen } from "@testing-library/react";
-import { describe, expect, it } from "vitest";
-import { GridContainer } from "./GridContainer";
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { GridContainer } from './GridContainer';
-describe("GridContainer", () => {
- describe("Rendering", () => {
- it("renders a div element", () => {
- render(Content);
- const grid = screen.getByTestId("grid");
- expect(grid).toBeInTheDocument();
- expect(grid.tagName).toBe("DIV");
- });
+describe('GridContainer', () => {
+ describe('Rendering', () => {
+ it('renders a div element', () => {
+ render(Content);
+ const grid = screen.getByTestId('grid');
+ expect(grid).toBeInTheDocument();
+ expect(grid.tagName).toBe('DIV');
+ });
- it("renders content", () => {
- render(Test content);
- expect(screen.getByText("Test content")).toBeInTheDocument();
- });
+ it('renders content', () => {
+ render(Test content);
+ expect(screen.getByText('Test content')).toBeInTheDocument();
+ });
- it("renders multiple children", () => {
- render(
-
- Child 1
- Child 2
- Child 3
- ,
- );
- expect(screen.getByText("Child 1")).toBeInTheDocument();
- expect(screen.getByText("Child 2")).toBeInTheDocument();
- expect(screen.getByText("Child 3")).toBeInTheDocument();
- });
- });
+ it('renders multiple children', () => {
+ render(
+
+ Child 1
+ Child 2
+ Child 3
+
+ );
+ expect(screen.getByText('Child 1')).toBeInTheDocument();
+ expect(screen.getByText('Child 2')).toBeInTheDocument();
+ expect(screen.getByText('Child 3')).toBeInTheDocument();
+ });
+ });
- describe("Column Variants", () => {
- it("applies auto columns by default", () => {
- render(Content);
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("grid-cols-[repeat(auto-fit");
- });
+ describe('Column Variants', () => {
+ it('applies auto columns by default', () => {
+ render(Content);
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('grid-cols-[repeat(auto-fit');
+ });
- it("applies single column", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("grid-cols-1");
- });
+ it('applies single column', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('grid-cols-1');
+ });
- it("applies responsive 2 columns", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("grid-cols-1");
- expect(grid.className).toContain("md:grid-cols-2");
- });
+ it('applies responsive 2 columns', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('grid-cols-1');
+ expect(grid.className).toContain('md:grid-cols-2');
+ });
- it("applies responsive 3 columns", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("lg:grid-cols-3");
- });
+ it('applies responsive 3 columns', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('lg:grid-cols-3');
+ });
- it("applies responsive 4 columns", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("lg:grid-cols-4");
- });
- });
+ it('applies responsive 4 columns', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('lg:grid-cols-4');
+ });
+ });
- describe("Gap Variants", () => {
- it("applies no gap", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("gap-0");
- });
+ describe('Gap Variants', () => {
+ it('applies no gap', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('gap-0');
+ });
- it("applies small gap", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("gap-4");
- });
+ it('applies small gap', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('gap-4');
+ });
- it("applies medium gap by default", () => {
- render(Content);
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("gap-6");
- });
+ it('applies medium gap by default', () => {
+ render(Content);
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('gap-6');
+ });
- it("applies large gap", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("gap-8");
- });
+ it('applies large gap', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('gap-8');
+ });
- it("applies extra large gap", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("gap-12");
- });
- });
+ it('applies extra large gap', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('gap-12');
+ });
+ });
- describe("Responsive Behavior", () => {
- it("uses responsive by default", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("md:grid-cols");
- });
+ describe('Responsive Behavior', () => {
+ it('uses responsive by default', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('md:grid-cols');
+ });
- it("applies fixed columns when responsive is false", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- // When responsive="fixed", compound variant adds grid-cols-3
- // Base variant still adds responsive classes, but compound variant takes precedence
- expect(grid.className).toContain("grid-cols-3");
- });
- });
+ it('applies fixed columns when responsive is false', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ // When responsive="fixed", compound variant adds grid-cols-3
+ // Base variant still adds responsive classes, but compound variant takes precedence
+ expect(grid.className).toContain('grid-cols-3');
+ });
+ });
- describe("Alignment", () => {
- it("applies start alignment", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("items-start");
- });
+ describe('Alignment', () => {
+ it('applies start alignment', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('items-start');
+ });
- it("applies center alignment", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("items-center");
- });
+ it('applies center alignment', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('items-center');
+ });
- it("applies end alignment", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("items-end");
- });
+ it('applies end alignment', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('items-end');
+ });
- it("applies stretch alignment by default", () => {
- render(Content);
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("items-stretch");
- });
- });
+ it('applies stretch alignment by default', () => {
+ render(Content);
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('items-stretch');
+ });
+ });
- describe("Justification", () => {
- it("applies start justify by default", () => {
- render(Content);
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("justify-items-start");
- });
+ describe('Justification', () => {
+ it('applies start justify by default', () => {
+ render(Content);
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('justify-items-start');
+ });
- it("applies center justify", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("justify-items-center");
- });
+ it('applies center justify', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('justify-items-center');
+ });
- it("applies end justify", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("justify-items-end");
- });
- });
+ it('applies end justify', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('justify-items-end');
+ });
+ });
- describe("HTML Attributes", () => {
- it("passes through id attribute", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid).toHaveAttribute("id", "grid-id");
- });
+ describe('HTML Attributes', () => {
+ it('passes through id attribute', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid).toHaveAttribute('id', 'grid-id');
+ });
- it("passes through data attributes", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid).toHaveAttribute("data-custom", "test");
- });
- });
+ it('passes through data attributes', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid).toHaveAttribute('data-custom', 'test');
+ });
+ });
- describe("Custom className", () => {
- it("merges custom className with variant classes", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("custom-class");
- expect(grid.className).toContain("grid");
- expect(grid.className).toContain("w-full");
- });
- });
+ describe('Custom className', () => {
+ it('merges custom className with variant classes', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('custom-class');
+ expect(grid.className).toContain('grid');
+ expect(grid.className).toContain('w-full');
+ });
+ });
- describe("Ref Forwarding", () => {
- it("forwards ref to div element", () => {
- let ref: HTMLDivElement | null = null;
- render(
- {
- ref = el;
- }}
- >
- Content
- ,
- );
- expect(ref).toBeInstanceOf(HTMLDivElement);
- });
- });
+ describe('Ref Forwarding', () => {
+ it('forwards ref to div element', () => {
+ let ref: HTMLDivElement | null = null;
+ render(
+ {
+ ref = el;
+ }}
+ >
+ Content
+
+ );
+ expect(ref).toBeInstanceOf(HTMLDivElement);
+ });
+ });
- describe("Base Classes", () => {
- it("always includes base classes", () => {
- render(Content);
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("grid");
- expect(grid.className).toContain("w-full");
- });
- });
+ describe('Base Classes', () => {
+ it('always includes base classes', () => {
+ render(Content);
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('grid');
+ expect(grid.className).toContain('w-full');
+ });
+ });
- describe("Compound Scenarios", () => {
- it("works with multiple variants combined", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("lg:grid-cols-4");
- expect(grid.className).toContain("gap-8");
- expect(grid.className).toContain("items-center");
- expect(grid.className).toContain("justify-items-center");
- });
+ describe('Compound Scenarios', () => {
+ it('works with multiple variants combined', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('lg:grid-cols-4');
+ expect(grid.className).toContain('gap-8');
+ expect(grid.className).toContain('items-center');
+ expect(grid.className).toContain('justify-items-center');
+ });
- it("works with fixed columns and custom className", () => {
- render(
-
- Content
- ,
- );
- const grid = screen.getByTestId("grid");
- expect(grid.className).toContain("grid-cols-3");
- expect(grid.className).toContain("my-custom-grid");
- });
- });
+ it('works with fixed columns and custom className', () => {
+ render(
+
+ Content
+
+ );
+ const grid = screen.getByTestId('grid');
+ expect(grid.className).toContain('grid-cols-3');
+ expect(grid.className).toContain('my-custom-grid');
+ });
+ });
});
diff --git a/packages/ui/src/GridContainer/GridContainer.tsx b/packages/ui/src/GridContainer/GridContainer.tsx
index 5ebafd6..c25af7f 100644
--- a/packages/ui/src/GridContainer/GridContainer.tsx
+++ b/packages/ui/src/GridContainer/GridContainer.tsx
@@ -1,7 +1,7 @@
-import { cn } from "@tpmjs/utils/cn";
-import { forwardRef } from "react";
-import type { GridContainerProps } from "./types";
-import { gridContainerVariants } from "./variants";
+import { cn } from '@tpmjs/utils/cn';
+import { forwardRef } from 'react';
+import type { GridContainerProps } from './types';
+import { gridContainerVariants } from './variants';
/**
* GridContainer component
@@ -25,38 +25,38 @@ import { gridContainerVariants } from "./variants";
* ```
*/
export const GridContainer = forwardRef(
- (
- {
- className,
- columns = "auto",
- gap = "md",
- responsive = "responsive",
- align = "stretch",
- justify = "start",
- children,
- ...props
- },
- ref,
- ) => {
- return (
-
- {children}
-
- );
- },
+ (
+ {
+ className,
+ columns = 'auto',
+ gap = 'md',
+ responsive = 'responsive',
+ align = 'stretch',
+ justify = 'start',
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ return (
+
+ {children}
+
+ );
+ }
);
-GridContainer.displayName = "GridContainer";
+GridContainer.displayName = 'GridContainer';
diff --git a/packages/ui/src/GridContainer/types.ts b/packages/ui/src/GridContainer/types.ts
index 01a22a6..0bef467 100644
--- a/packages/ui/src/GridContainer/types.ts
+++ b/packages/ui/src/GridContainer/types.ts
@@ -1,38 +1,38 @@
-import type { HTMLAttributes } from "react";
+import type { HTMLAttributes } from 'react';
/**
* GridContainer component props
*/
export interface GridContainerProps extends HTMLAttributes {
- /**
- * Number of columns in the grid
- * @default 'auto'
- */
- columns?: "auto" | 1 | 2 | 3 | 4 | 5 | 6 | 12;
+ /**
+ * Number of columns in the grid
+ * @default 'auto'
+ */
+ columns?: 'auto' | 1 | 2 | 3 | 4 | 5 | 6 | 12;
- /**
- * Gap between grid items
- * @default 'md'
- */
- gap?: "none" | "sm" | "md" | "lg" | "xl";
+ /**
+ * Gap between grid items
+ * @default 'md'
+ */
+ gap?: 'none' | 'sm' | 'md' | 'lg' | 'xl';
- /**
- * Responsive breakpoint behavior
- * @default 'responsive'
- */
- responsive?: "responsive" | "fixed";
+ /**
+ * Responsive breakpoint behavior
+ * @default 'responsive'
+ */
+ responsive?: 'responsive' | 'fixed';
- /**
- * Align items vertically
- * @default 'stretch'
- */
- align?: "start" | "center" | "end" | "stretch";
+ /**
+ * Align items vertically
+ * @default 'stretch'
+ */
+ align?: 'start' | 'center' | 'end' | 'stretch';
- /**
- * Justify items horizontally
- * @default 'start'
- */
- justify?: "start" | "center" | "end" | "between" | "around" | "evenly";
+ /**
+ * Justify items horizontally
+ * @default 'start'
+ */
+ justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
}
/**
diff --git a/packages/ui/src/GridContainer/variants.ts b/packages/ui/src/GridContainer/variants.ts
index f1836c9..376eae3 100644
--- a/packages/ui/src/GridContainer/variants.ts
+++ b/packages/ui/src/GridContainer/variants.ts
@@ -1,118 +1,118 @@
-import { createVariants } from "../system/variants";
+import { createVariants } from '../system/variants';
/**
* GridContainer variant definitions
*/
export const gridContainerVariants = createVariants({
- base: ["grid w-full"].join(" "),
+ base: ['grid w-full'].join(' '),
- variants: {
- columns: {
- auto: "",
- 1: "grid-cols-1",
- 2: "grid-cols-1 md:grid-cols-2",
- 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3",
- 4: "grid-cols-1 md:grid-cols-2 lg:grid-cols-4",
- 5: "grid-cols-2 md:grid-cols-3 lg:grid-cols-5",
- 6: "grid-cols-2 md:grid-cols-3 lg:grid-cols-6",
- 12: "grid-cols-4 md:grid-cols-6 lg:grid-cols-12",
- },
+ variants: {
+ columns: {
+ auto: '',
+ 1: 'grid-cols-1',
+ 2: 'grid-cols-1 md:grid-cols-2',
+ 3: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
+ 4: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4',
+ 5: 'grid-cols-2 md:grid-cols-3 lg:grid-cols-5',
+ 6: 'grid-cols-2 md:grid-cols-3 lg:grid-cols-6',
+ 12: 'grid-cols-4 md:grid-cols-6 lg:grid-cols-12',
+ },
- gap: {
- none: "gap-0",
- sm: "gap-4",
- md: "gap-6",
- lg: "gap-8",
- xl: "gap-12",
- },
+ gap: {
+ none: 'gap-0',
+ sm: 'gap-4',
+ md: 'gap-6',
+ lg: 'gap-8',
+ xl: 'gap-12',
+ },
- responsive: {
- responsive: "",
- fixed: "",
- },
+ responsive: {
+ responsive: '',
+ fixed: '',
+ },
- align: {
- start: "items-start",
- center: "items-center",
- end: "items-end",
- stretch: "items-stretch",
- },
+ align: {
+ start: 'items-start',
+ center: 'items-center',
+ end: 'items-end',
+ stretch: 'items-stretch',
+ },
- justify: {
- start: "justify-items-start",
- center: "justify-items-center",
- end: "justify-items-end",
- between: "justify-items-between",
- around: "justify-items-around",
- evenly: "justify-items-evenly",
- },
- },
+ justify: {
+ start: 'justify-items-start',
+ center: 'justify-items-center',
+ end: 'justify-items-end',
+ between: 'justify-items-between',
+ around: 'justify-items-around',
+ evenly: 'justify-items-evenly',
+ },
+ },
- compoundVariants: [
- {
- conditions: {
- columns: "auto",
- responsive: "responsive",
- },
- className: "grid-cols-[repeat(auto-fit,minmax(250px,1fr))]",
- },
- {
- conditions: {
- columns: "auto",
- responsive: "fixed",
- },
- className: "grid-cols-[repeat(auto-fill,minmax(250px,1fr))]",
- },
- // Fixed columns don't use responsive breakpoints
- {
- conditions: {
- columns: 2,
- responsive: "fixed",
- },
- className: "grid-cols-2",
- },
- {
- conditions: {
- columns: 3,
- responsive: "fixed",
- },
- className: "grid-cols-3",
- },
- {
- conditions: {
- columns: 4,
- responsive: "fixed",
- },
- className: "grid-cols-4",
- },
- {
- conditions: {
- columns: 5,
- responsive: "fixed",
- },
- className: "grid-cols-5",
- },
- {
- conditions: {
- columns: 6,
- responsive: "fixed",
- },
- className: "grid-cols-6",
- },
- {
- conditions: {
- columns: 12,
- responsive: "fixed",
- },
- className: "grid-cols-12",
- },
- ],
+ compoundVariants: [
+ {
+ conditions: {
+ columns: 'auto',
+ responsive: 'responsive',
+ },
+ className: 'grid-cols-[repeat(auto-fit,minmax(250px,1fr))]',
+ },
+ {
+ conditions: {
+ columns: 'auto',
+ responsive: 'fixed',
+ },
+ className: 'grid-cols-[repeat(auto-fill,minmax(250px,1fr))]',
+ },
+ // Fixed columns don't use responsive breakpoints
+ {
+ conditions: {
+ columns: 2,
+ responsive: 'fixed',
+ },
+ className: 'grid-cols-2',
+ },
+ {
+ conditions: {
+ columns: 3,
+ responsive: 'fixed',
+ },
+ className: 'grid-cols-3',
+ },
+ {
+ conditions: {
+ columns: 4,
+ responsive: 'fixed',
+ },
+ className: 'grid-cols-4',
+ },
+ {
+ conditions: {
+ columns: 5,
+ responsive: 'fixed',
+ },
+ className: 'grid-cols-5',
+ },
+ {
+ conditions: {
+ columns: 6,
+ responsive: 'fixed',
+ },
+ className: 'grid-cols-6',
+ },
+ {
+ conditions: {
+ columns: 12,
+ responsive: 'fixed',
+ },
+ className: 'grid-cols-12',
+ },
+ ],
- defaultVariants: {
- columns: "auto",
- gap: "md",
- responsive: "responsive",
- align: "stretch",
- justify: "start",
- },
+ defaultVariants: {
+ columns: 'auto',
+ gap: 'md',
+ responsive: 'responsive',
+ align: 'stretch',
+ justify: 'start',
+ },
});
diff --git a/packages/ui/src/Header/Header.test.tsx b/packages/ui/src/Header/Header.test.tsx
index b73fca7..747a6e2 100644
--- a/packages/ui/src/Header/Header.test.tsx
+++ b/packages/ui/src/Header/Header.test.tsx
@@ -1,337 +1,325 @@
-import { render, screen } from "@testing-library/react";
-import { describe, expect, it } from "vitest";
-import { Header } from "./Header";
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { Header } from './Header';
-describe("Header", () => {
- describe("Rendering", () => {
- it("renders a header element", () => {
- render();
- const header = screen.getByTestId("header");
- expect(header).toBeInTheDocument();
- expect(header.tagName).toBe("HEADER");
- });
+describe('Header', () => {
+ describe('Rendering', () => {
+ it('renders a header element', () => {
+ render();
+ const header = screen.getByTestId('header');
+ expect(header).toBeInTheDocument();
+ expect(header.tagName).toBe('HEADER');
+ });
- it("renders without title or actions", () => {
- render();
- const header = screen.getByTestId("header");
- expect(header).toBeInTheDocument();
- expect(screen.queryByTestId("header-title")).not.toBeInTheDocument();
- expect(screen.queryByTestId("header-actions")).not.toBeInTheDocument();
- });
- });
+ it('renders without title or actions', () => {
+ render();
+ const header = screen.getByTestId('header');
+ expect(header).toBeInTheDocument();
+ expect(screen.queryByTestId('header-title')).not.toBeInTheDocument();
+ expect(screen.queryByTestId('header-actions')).not.toBeInTheDocument();
+ });
+ });
- describe("Title", () => {
- it("renders title when provided", () => {
- render();
- expect(screen.getByText("TPMJS Registry")).toBeInTheDocument();
- });
+ describe('Title', () => {
+ it('renders title when provided', () => {
+ render();
+ expect(screen.getByText('TPMJS Registry')).toBeInTheDocument();
+ });
- it("renders title in title container", () => {
- render();
- const titleContainer = screen.getByTestId("header-title");
- expect(titleContainer).toBeInTheDocument();
- expect(titleContainer.textContent).toBe("My App");
- });
+ it('renders title in title container', () => {
+ render();
+ const titleContainer = screen.getByTestId('header-title');
+ expect(titleContainer).toBeInTheDocument();
+ expect(titleContainer.textContent).toBe('My App');
+ });
- it("title container has correct base classes", () => {
- render();
- const titleContainer = screen.getByTestId("header-title");
- expect(titleContainer.className).toContain("flex");
- expect(titleContainer.className).toContain("items-center");
- expect(titleContainer.className).toContain("font-semibold");
- expect(titleContainer.className).toContain("text-foreground");
- });
+ it('title container has correct base classes', () => {
+ render();
+ const titleContainer = screen.getByTestId('header-title');
+ expect(titleContainer.className).toContain('flex');
+ expect(titleContainer.className).toContain('items-center');
+ expect(titleContainer.className).toContain('font-semibold');
+ expect(titleContainer.className).toContain('text-foreground');
+ });
- it("renders ReactNode as title", () => {
- render(Custom} />);
- expect(screen.getByTestId("custom-title")).toBeInTheDocument();
- });
- });
+ it('renders ReactNode as title', () => {
+ render(Custom} />);
+ expect(screen.getByTestId('custom-title')).toBeInTheDocument();
+ });
+ });
- describe("Actions", () => {
- it("renders actions when provided", () => {
- render(
-
- Sign In
-
- }
- />,
- );
- expect(screen.getByTestId("action-btn")).toBeInTheDocument();
- });
+ describe('Actions', () => {
+ it('renders actions when provided', () => {
+ render(
+
+ Sign In
+
+ }
+ />
+ );
+ expect(screen.getByTestId('action-btn')).toBeInTheDocument();
+ });
- it("renders actions in actions container", () => {
- render();
- const actionsContainer = screen.getByTestId("header-actions");
- expect(actionsContainer).toBeInTheDocument();
- expect(actionsContainer.textContent).toBe("Actions content");
- });
+ it('renders actions in actions container', () => {
+ render();
+ const actionsContainer = screen.getByTestId('header-actions');
+ expect(actionsContainer).toBeInTheDocument();
+ expect(actionsContainer.textContent).toBe('Actions content');
+ });
- it("actions container has correct base classes", () => {
- render();
- const actionsContainer = screen.getByTestId("header-actions");
- expect(actionsContainer.className).toContain("flex");
- expect(actionsContainer.className).toContain("items-center");
- });
+ it('actions container has correct base classes', () => {
+ render();
+ const actionsContainer = screen.getByTestId('header-actions');
+ expect(actionsContainer.className).toContain('flex');
+ expect(actionsContainer.className).toContain('items-center');
+ });
- it("renders ReactNode as actions", () => {
- render(
- Custom Actions}
- />,
- );
- expect(screen.getByTestId("custom-actions")).toBeInTheDocument();
- });
- });
+ it('renders ReactNode as actions', () => {
+ render(Custom Actions} />);
+ expect(screen.getByTestId('custom-actions')).toBeInTheDocument();
+ });
+ });
- describe("Children", () => {
- it("renders children when provided", () => {
- render();
- expect(screen.getByText("Header content")).toBeInTheDocument();
- });
+ describe('Children', () => {
+ it('renders children when provided', () => {
+ render();
+ expect(screen.getByText('Header content')).toBeInTheDocument();
+ });
- it("renders children in children container", () => {
- render();
- const childrenContainer = screen.getByTestId("header-children");
- expect(childrenContainer).toBeInTheDocument();
- expect(childrenContainer.textContent).toBe("Center content");
- });
+ it('renders children in children container', () => {
+ render();
+ const childrenContainer = screen.getByTestId('header-children');
+ expect(childrenContainer).toBeInTheDocument();
+ expect(childrenContainer.textContent).toBe('Center content');
+ });
- it("children container has centering classes", () => {
- render();
- const childrenContainer = screen.getByTestId("header-children");
- expect(childrenContainer.className).toContain("flex-1");
- expect(childrenContainer.className).toContain("flex");
- expect(childrenContainer.className).toContain("items-center");
- expect(childrenContainer.className).toContain("justify-center");
- });
+ it('children container has centering classes', () => {
+ render();
+ const childrenContainer = screen.getByTestId('header-children');
+ expect(childrenContainer.className).toContain('flex-1');
+ expect(childrenContainer.className).toContain('flex');
+ expect(childrenContainer.className).toContain('items-center');
+ expect(childrenContainer.className).toContain('justify-center');
+ });
- it("renders ReactNode as children", () => {
- render(
- ,
- );
- expect(screen.getByTestId("custom-nav")).toBeInTheDocument();
- });
- });
+ it('renders ReactNode as children', () => {
+ render(
+
+ );
+ expect(screen.getByTestId('custom-nav')).toBeInTheDocument();
+ });
+ });
- describe("Combined Content", () => {
- it("renders title and actions together", () => {
- render();
- expect(screen.getByText("Title")).toBeInTheDocument();
- expect(screen.getByText("Actions")).toBeInTheDocument();
- });
+ describe('Combined Content', () => {
+ it('renders title and actions together', () => {
+ render();
+ expect(screen.getByText('Title')).toBeInTheDocument();
+ expect(screen.getByText('Actions')).toBeInTheDocument();
+ });
- it("renders title, children, and actions together", () => {
- render(
- ,
- );
- expect(screen.getByText("Title")).toBeInTheDocument();
- expect(screen.getByText("Center")).toBeInTheDocument();
- expect(screen.getByText("Actions")).toBeInTheDocument();
- });
+ it('renders title, children, and actions together', () => {
+ render(
+
+ );
+ expect(screen.getByText('Title')).toBeInTheDocument();
+ expect(screen.getByText('Center')).toBeInTheDocument();
+ expect(screen.getByText('Actions')).toBeInTheDocument();
+ });
- it("maintains correct layout with all content", () => {
- render(
- ,
- );
- const header = screen.getByTestId("header");
- expect(header.className).toContain("justify-between");
- });
- });
+ it('maintains correct layout with all content', () => {
+ render(
+
+ );
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('justify-between');
+ });
+ });
- describe("Size Variants", () => {
- it("applies small size to header", () => {
- render();
- const header = screen.getByTestId("header");
- expect(header.className).toContain("h-12");
- expect(header.className).toContain("px-4");
- });
+ describe('Size Variants', () => {
+ it('applies small size to header', () => {
+ render();
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('h-12');
+ expect(header.className).toContain('px-4');
+ });
- it("applies medium size to header", () => {
- render();
- const header = screen.getByTestId("header");
- expect(header.className).toContain("h-16");
- expect(header.className).toContain("px-6");
- });
+ it('applies medium size to header', () => {
+ render();
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('h-16');
+ expect(header.className).toContain('px-6');
+ });
- it("applies large size to header", () => {
- render();
- const header = screen.getByTestId("header");
- expect(header.className).toContain("h-20");
- expect(header.className).toContain("px-8");
- });
+ it('applies large size to header', () => {
+ render();
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('h-20');
+ expect(header.className).toContain('px-8');
+ });
- it("uses md size by default", () => {
- render();
- const header = screen.getByTestId("header");
- expect(header.className).toContain("h-16");
- expect(header.className).toContain("px-6");
- });
+ it('uses md size by default', () => {
+ render();
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('h-16');
+ expect(header.className).toContain('px-6');
+ });
- it("applies size to title", () => {
- render();
- const titleContainer = screen.getByTestId("header-title");
- expect(titleContainer.className).toContain("text-xl");
- expect(titleContainer.className).toContain("gap-4");
- });
+ it('applies size to title', () => {
+ render();
+ const titleContainer = screen.getByTestId('header-title');
+ expect(titleContainer.className).toContain('text-xl');
+ expect(titleContainer.className).toContain('gap-4');
+ });
- it("applies size to actions", () => {
- render();
- const actionsContainer = screen.getByTestId("header-actions");
- expect(actionsContainer.className).toContain("gap-2");
- });
- });
+ it('applies size to actions', () => {
+ render();
+ const actionsContainer = screen.getByTestId('header-actions');
+ expect(actionsContainer.className).toContain('gap-2');
+ });
+ });
- describe("Sticky Positioning", () => {
- it("applies sticky classes when sticky is true", () => {
- render();
- const header = screen.getByTestId("header");
- expect(header.className).toContain("sticky");
- expect(header.className).toContain("top-0");
- expect(header.className).toContain("z-50");
- });
+ describe('Sticky Positioning', () => {
+ it('applies sticky classes when sticky is true', () => {
+ render();
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('sticky');
+ expect(header.className).toContain('top-0');
+ expect(header.className).toContain('z-50');
+ });
- it("does not apply sticky classes when sticky is false", () => {
- render();
- const header = screen.getByTestId("header");
- expect(header.className).not.toContain("sticky");
- expect(header.className).not.toContain("top-0");
- expect(header.className).not.toContain("z-50");
- });
+ it('does not apply sticky classes when sticky is false', () => {
+ render();
+ const header = screen.getByTestId('header');
+ expect(header.className).not.toContain('sticky');
+ expect(header.className).not.toContain('top-0');
+ expect(header.className).not.toContain('z-50');
+ });
- it("is not sticky by default", () => {
- render();
- const header = screen.getByTestId("header");
- expect(header.className).not.toContain("sticky");
- });
- });
+ it('is not sticky by default', () => {
+ render();
+ const header = screen.getByTestId('header');
+ expect(header.className).not.toContain('sticky');
+ });
+ });
- describe("HTML Attributes", () => {
- it("passes through id attribute", () => {
- render();
- const header = screen.getByTestId("header");
- expect(header).toHaveAttribute("id", "header-id");
- });
+ describe('HTML Attributes', () => {
+ it('passes through id attribute', () => {
+ render();
+ const header = screen.getByTestId('header');
+ expect(header).toHaveAttribute('id', 'header-id');
+ });
- it("passes through data attributes", () => {
- render();
- const header = screen.getByTestId("header");
- expect(header).toHaveAttribute("data-custom", "test");
- });
+ it('passes through data attributes', () => {
+ render();
+ const header = screen.getByTestId('header');
+ expect(header).toHaveAttribute('data-custom', 'test');
+ });
- it("passes through aria attributes", () => {
- render();
- const header = screen.getByTestId("header");
- expect(header).toHaveAttribute("aria-label", "Main header");
- });
- });
+ it('passes through aria attributes', () => {
+ render();
+ const header = screen.getByTestId('header');
+ expect(header).toHaveAttribute('aria-label', 'Main header');
+ });
+ });
- describe("Custom className", () => {
- it("merges custom className with variant classes", () => {
- render();
- const header = screen.getByTestId("header");
- expect(header.className).toContain("custom-class");
- expect(header.className).toContain("flex");
- expect(header.className).toContain("bg-surface");
- });
- });
+ describe('Custom className', () => {
+ it('merges custom className with variant classes', () => {
+ render();
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('custom-class');
+ expect(header.className).toContain('flex');
+ expect(header.className).toContain('bg-surface');
+ });
+ });
- describe("Ref Forwarding", () => {
- it("forwards ref to header element", () => {
- let ref: HTMLElement | null = null;
- render(
- {
- ref = el;
- }}
- />,
- );
- expect(ref).toBeInstanceOf(HTMLElement);
- expect(ref!.tagName).toBe("HEADER");
- });
- });
+ describe('Ref Forwarding', () => {
+ it('forwards ref to header element', () => {
+ let ref: HTMLElement | null = null;
+ render(
+ {
+ ref = el;
+ }}
+ />
+ );
+ expect(ref).toBeInstanceOf(HTMLElement);
+ expect(ref!.tagName).toBe('HEADER');
+ });
+ });
- describe("Base Classes", () => {
- it("always includes base classes", () => {
- render();
- const header = screen.getByTestId("header");
- expect(header.className).toContain("flex");
- expect(header.className).toContain("items-center");
- expect(header.className).toContain("justify-between");
- expect(header.className).toContain("w-full");
- expect(header.className).toContain("bg-surface");
- expect(header.className).toContain("border-b");
- expect(header.className).toContain("border-border");
- });
- });
+ describe('Base Classes', () => {
+ it('always includes base classes', () => {
+ render();
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('flex');
+ expect(header.className).toContain('items-center');
+ expect(header.className).toContain('justify-between');
+ expect(header.className).toContain('w-full');
+ expect(header.className).toContain('bg-surface');
+ expect(header.className).toContain('border-b');
+ expect(header.className).toContain('border-border');
+ });
+ });
- describe("Compound Scenarios", () => {
- it("works correctly with large size and sticky", () => {
- render(
- ,
- );
- const header = screen.getByTestId("header");
- expect(header.className).toContain("h-20");
- expect(header.className).toContain("px-8");
- expect(header.className).toContain("sticky");
- expect(header.className).toContain("top-0");
- expect(header.className).toContain("z-50");
- });
+ describe('Compound Scenarios', () => {
+ it('works correctly with large size and sticky', () => {
+ render();
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('h-20');
+ expect(header.className).toContain('px-8');
+ expect(header.className).toContain('sticky');
+ expect(header.className).toContain('top-0');
+ expect(header.className).toContain('z-50');
+ });
- it("works correctly with all content and custom className", () => {
- render(
- ,
- );
- const header = screen.getByTestId("header");
- expect(header.className).toContain("my-header");
- expect(header.className).toContain("h-12");
- expect(screen.getByText("Title")).toBeInTheDocument();
- expect(screen.getByText("Center")).toBeInTheDocument();
- expect(screen.getByText("Actions")).toBeInTheDocument();
- });
+ it('works correctly with all content and custom className', () => {
+ render(
+
+ );
+ const header = screen.getByTestId('header');
+ expect(header.className).toContain('my-header');
+ expect(header.className).toContain('h-12');
+ expect(screen.getByText('Title')).toBeInTheDocument();
+ expect(screen.getByText('Center')).toBeInTheDocument();
+ expect(screen.getByText('Actions')).toBeInTheDocument();
+ });
- it("works with complex ReactNode content", () => {
- render(
-
-
- TPMJS
-
- }
- actions={
-
-
-
-
- }
- />,
- );
- expect(screen.getByText("TPMJS")).toBeInTheDocument();
- expect(screen.getByText("Docs")).toBeInTheDocument();
- expect(screen.getByText("GitHub")).toBeInTheDocument();
- });
- });
+ it('works with complex ReactNode content', () => {
+ render(
+
+
+ TPMJS
+
+ }
+ actions={
+
+
+
+
+ }
+ />
+ );
+ expect(screen.getByText('TPMJS')).toBeInTheDocument();
+ expect(screen.getByText('Docs')).toBeInTheDocument();
+ expect(screen.getByText('GitHub')).toBeInTheDocument();
+ });
+ });
});
diff --git a/packages/ui/src/Header/Header.tsx b/packages/ui/src/Header/Header.tsx
index 3acc0d7..346db28 100644
--- a/packages/ui/src/Header/Header.tsx
+++ b/packages/ui/src/Header/Header.tsx
@@ -1,11 +1,7 @@
-import { cn } from "@tpmjs/utils/cn";
-import { forwardRef } from "react";
-import type { HeaderProps } from "./types";
-import {
- headerActionsVariants,
- headerTitleVariants,
- headerVariants,
-} from "./variants";
+import { cn } from '@tpmjs/utils/cn';
+import { forwardRef } from 'react';
+import type { HeaderProps } from './types';
+import { headerActionsVariants, headerTitleVariants, headerVariants } from './variants';
/**
* Header component
@@ -31,61 +27,47 @@ import {
* ```
*/
export const Header = forwardRef(
- (
- {
- className,
- title,
- actions,
- size = "md",
- sticky = false,
- children,
- ...props
- },
- ref,
- ) => {
- return (
-
- {title && (
-
- {title}
-
- )}
- {children && (
-
- {children}
-
- )}
- {actions && (
-
- {actions}
-
- )}
-
- );
- },
+ ({ className, title, actions, size = 'md', sticky = false, children, ...props }, ref) => {
+ return (
+
+ {title && (
+
+ {title}
+
+ )}
+ {children && (
+
+ {children}
+
+ )}
+ {actions && (
+
+ {actions}
+
+ )}
+
+ );
+ }
);
-Header.displayName = "Header";
+Header.displayName = 'Header';
diff --git a/packages/ui/src/Header/tokens.ts b/packages/ui/src/Header/tokens.ts
index 6c2e02c..164f799 100644
--- a/packages/ui/src/Header/tokens.ts
+++ b/packages/ui/src/Header/tokens.ts
@@ -1,21 +1,21 @@
-import { spacing } from "../tokens";
+import { spacing } from '../tokens';
/**
* Header component design tokens
* Maps global tokens to header-specific semantics
*/
export const headerTokens = {
- /** Height for each size */
- height: {
- sm: "3rem", // 48px
- md: "4rem", // 64px
- lg: "5rem", // 80px
- },
+ /** Height for each size */
+ height: {
+ sm: '3rem', // 48px
+ md: '4rem', // 64px
+ lg: '5rem', // 80px
+ },
- /** Horizontal padding for each size */
- padding: {
- sm: spacing[4], // 1rem (16px)
- md: spacing[6], // 1.5rem (24px)
- lg: spacing[8], // 2rem (32px)
- },
+ /** Horizontal padding for each size */
+ padding: {
+ sm: spacing[4], // 1rem (16px)
+ md: spacing[6], // 1.5rem (24px)
+ lg: spacing[8], // 2rem (32px)
+ },
} as const;
diff --git a/packages/ui/src/Header/types.ts b/packages/ui/src/Header/types.ts
index 7816818..885e411 100644
--- a/packages/ui/src/Header/types.ts
+++ b/packages/ui/src/Header/types.ts
@@ -1,31 +1,30 @@
-import type { HTMLAttributes, ReactNode } from "react";
+import type { HTMLAttributes, ReactNode } from 'react';
/**
* Header component props
*/
-export interface HeaderProps
- extends Omit, "title"> {
- /**
- * Title/logo content for the left side
- */
- title?: ReactNode;
+export interface HeaderProps extends Omit, 'title'> {
+ /**
+ * Title/logo content for the left side
+ */
+ title?: ReactNode;
- /**
- * Actions/navigation for the right side
- */
- actions?: ReactNode;
+ /**
+ * Actions/navigation for the right side
+ */
+ actions?: ReactNode;
- /**
- * Size variant
- * @default 'md'
- */
- size?: "sm" | "md" | "lg";
+ /**
+ * Size variant
+ * @default 'md'
+ */
+ size?: 'sm' | 'md' | 'lg';
- /**
- * Whether header is sticky
- * @default false
- */
- sticky?: boolean;
+ /**
+ * Whether header is sticky
+ * @default false
+ */
+ sticky?: boolean;
}
/**
diff --git a/packages/ui/src/Header/variants.ts b/packages/ui/src/Header/variants.ts
index 4f0dcdf..84d7234 100644
--- a/packages/ui/src/Header/variants.ts
+++ b/packages/ui/src/Header/variants.ts
@@ -1,88 +1,88 @@
-import { createVariants } from "../system/variants";
+import { createVariants } from '../system/variants';
/**
* Header variant definitions
*/
export const headerVariants = createVariants({
- base: [
- // Display
- "flex items-center justify-between",
- // Width
- "w-full",
- // Background
- "bg-surface",
- // Border
- "border-b border-border",
- ].join(" "),
+ base: [
+ // Display
+ 'flex items-center justify-between',
+ // Width
+ 'w-full',
+ // Background
+ 'bg-surface',
+ // Border
+ 'border-b border-border',
+ ].join(' '),
- variants: {
- size: {
- sm: "h-12 px-4", // 48px height, 16px padding
- md: "h-16 px-6", // 64px height, 24px padding
- lg: "h-20 px-8", // 80px height, 32px padding
- },
- sticky: {
- true: "sticky top-0 z-50",
- false: "",
- },
- },
+ variants: {
+ size: {
+ sm: 'h-12 px-4', // 48px height, 16px padding
+ md: 'h-16 px-6', // 64px height, 24px padding
+ lg: 'h-20 px-8', // 80px height, 32px padding
+ },
+ sticky: {
+ true: 'sticky top-0 z-50',
+ false: '',
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- size: "md",
- sticky: "false",
- },
+ defaultVariants: {
+ size: 'md',
+ sticky: 'false',
+ },
});
/**
* Header title variant definitions
*/
export const headerTitleVariants = createVariants({
- base: [
- // Display
- "flex items-center",
- // Font
- "font-semibold",
- // Color
- "text-foreground",
- ].join(" "),
+ base: [
+ // Display
+ 'flex items-center',
+ // Font
+ 'font-semibold',
+ // Color
+ 'text-foreground',
+ ].join(' '),
- variants: {
- size: {
- sm: "text-base gap-2", // 16px
- md: "text-lg gap-3", // 18px
- lg: "text-xl gap-4", // 20px
- },
- },
+ variants: {
+ size: {
+ sm: 'text-base gap-2', // 16px
+ md: 'text-lg gap-3', // 18px
+ lg: 'text-xl gap-4', // 20px
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- size: "md",
- },
+ defaultVariants: {
+ size: 'md',
+ },
});
/**
* Header actions variant definitions
*/
export const headerActionsVariants = createVariants({
- base: [
- // Display
- "flex items-center",
- ].join(" "),
+ base: [
+ // Display
+ 'flex items-center',
+ ].join(' '),
- variants: {
- size: {
- sm: "gap-2", // 8px
- md: "gap-3", // 12px
- lg: "gap-4", // 16px
- },
- },
+ variants: {
+ size: {
+ sm: 'gap-2', // 8px
+ md: 'gap-3', // 12px
+ lg: 'gap-4', // 16px
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- size: "md",
- },
+ defaultVariants: {
+ size: 'md',
+ },
});
diff --git a/packages/ui/src/Icon/Icon.test.tsx b/packages/ui/src/Icon/Icon.test.tsx
index aab4a9b..6cc7757 100644
--- a/packages/ui/src/Icon/Icon.test.tsx
+++ b/packages/ui/src/Icon/Icon.test.tsx
@@ -1,229 +1,219 @@
-import { render, screen } from "@testing-library/react";
-import { describe, expect, it } from "vitest";
-import { Icon } from "./Icon";
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { Icon } from './Icon';
-describe("Icon", () => {
- describe("Rendering", () => {
- it("renders an SVG element", () => {
- render();
- const icon = screen.getByTestId("icon");
- expect(icon).toBeInTheDocument();
- expect(icon.tagName).toBe("svg");
- });
+describe('Icon', () => {
+ describe('Rendering', () => {
+ it('renders an SVG element', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ expect(icon).toBeInTheDocument();
+ expect(icon.tagName).toBe('svg');
+ });
- it("renders with correct viewBox", () => {
- render();
- const icon = screen.getByTestId("icon");
- expect(icon).toHaveAttribute("viewBox", "0 0 24 24");
- });
+ it('renders with correct viewBox', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ expect(icon).toHaveAttribute('viewBox', '0 0 24 24');
+ });
- it("renders with currentColor fill", () => {
- render();
- const icon = screen.getByTestId("icon");
- expect(icon).toHaveAttribute("fill", "currentColor");
- });
+ it('renders with currentColor fill', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ expect(icon).toHaveAttribute('fill', 'currentColor');
+ });
- it("includes path element", () => {
- render();
- const icon = screen.getByTestId("icon");
- const path = icon.querySelector("path");
- expect(path).toBeInTheDocument();
- });
+ it('includes path element', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ const path = icon.querySelector('path');
+ expect(path).toBeInTheDocument();
+ });
- it("is aria-hidden by default", () => {
- render();
- const icon = screen.getByTestId("icon");
- expect(icon).toHaveAttribute("aria-hidden", "true");
- });
- });
+ it('is aria-hidden by default', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ expect(icon).toHaveAttribute('aria-hidden', 'true');
+ });
+ });
- describe("Size Variants", () => {
- it("applies small size", () => {
- render();
- const icon = screen.getByTestId("icon");
- expect(icon.className).toContain("w-4");
- expect(icon.className).toContain("h-4");
- });
+ describe('Size Variants', () => {
+ it('applies small size', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ expect(icon.className).toContain('w-4');
+ expect(icon.className).toContain('h-4');
+ });
- it("applies medium size", () => {
- render();
- const icon = screen.getByTestId("icon");
- expect(icon.className).toContain("w-5");
- expect(icon.className).toContain("h-5");
- });
+ it('applies medium size', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ expect(icon.className).toContain('w-5');
+ expect(icon.className).toContain('h-5');
+ });
- it("applies large size", () => {
- render();
- const icon = screen.getByTestId("icon");
- expect(icon.className).toContain("w-6");
- expect(icon.className).toContain("h-6");
- });
+ it('applies large size', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ expect(icon.className).toContain('w-6');
+ expect(icon.className).toContain('h-6');
+ });
- it("uses md size by default", () => {
- render();
- const icon = screen.getByTestId("icon");
- expect(icon.className).toContain("w-5");
- expect(icon.className).toContain("h-5");
- });
- });
+ it('uses md size by default', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ expect(icon.className).toContain('w-5');
+ expect(icon.className).toContain('h-5');
+ });
+ });
- describe("Icon Variants", () => {
- it("renders copy icon", () => {
- render();
- const icon = screen.getByTestId("icon");
- const path = icon.querySelector("path");
- expect(path).toHaveAttribute(
- "d",
- "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z",
- );
- });
+ describe('Icon Variants', () => {
+ it('renders copy icon', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ const path = icon.querySelector('path');
+ expect(path).toHaveAttribute(
+ 'd',
+ 'M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z'
+ );
+ });
- it("renders externalLink icon", () => {
- render();
- const icon = screen.getByTestId("icon");
- const path = icon.querySelector("path");
- expect(path).toHaveAttribute(
- "d",
- "M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z",
- );
- });
+ it('renders externalLink icon', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ const path = icon.querySelector('path');
+ expect(path).toHaveAttribute(
+ 'd',
+ 'M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z'
+ );
+ });
- it("renders github icon", () => {
- render();
- const icon = screen.getByTestId("icon");
- const path = icon.querySelector("path");
- expect(path?.getAttribute("d")).toContain("M12 2C6.477 2 2 6.477 2 12");
- });
+ it('renders github icon', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ const path = icon.querySelector('path');
+ expect(path?.getAttribute('d')).toContain('M12 2C6.477 2 2 6.477 2 12');
+ });
- it("renders check icon", () => {
- render();
- const icon = screen.getByTestId("icon");
- const path = icon.querySelector("path");
- expect(path).toHaveAttribute(
- "d",
- "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
- );
- });
+ it('renders check icon', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ const path = icon.querySelector('path');
+ expect(path).toHaveAttribute(
+ 'd',
+ 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z'
+ );
+ });
- it("renders x icon", () => {
- render();
- const icon = screen.getByTestId("icon");
- const path = icon.querySelector("path");
- expect(path).toHaveAttribute(
- "d",
- "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z",
- );
- });
+ it('renders x icon', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ const path = icon.querySelector('path');
+ expect(path).toHaveAttribute(
+ 'd',
+ 'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z'
+ );
+ });
- it("renders chevronDown icon", () => {
- render();
- const icon = screen.getByTestId("icon");
- const path = icon.querySelector("path");
- expect(path).toHaveAttribute(
- "d",
- "M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z",
- );
- });
- });
+ it('renders chevronDown icon', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ const path = icon.querySelector('path');
+ expect(path).toHaveAttribute('d', 'M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z');
+ });
+ });
- describe("SVG Attributes", () => {
- it("passes through className", () => {
- render();
- const icon = screen.getByTestId("icon");
- expect(icon.className).toContain("custom-class");
- });
+ describe('SVG Attributes', () => {
+ it('passes through className', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ expect(icon.className).toContain('custom-class');
+ });
- it("merges custom className with variant classes", () => {
- render();
- const icon = screen.getByTestId("icon");
- expect(icon.className).toContain("text-red-500");
- expect(icon.className).toContain("inline-block");
- expect(icon.className).toContain("fill-current");
- });
+ it('merges custom className with variant classes', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ expect(icon.className).toContain('text-red-500');
+ expect(icon.className).toContain('inline-block');
+ expect(icon.className).toContain('fill-current');
+ });
- it("allows custom aria-hidden", () => {
- render();
- const icon = screen.getByTestId("icon");
- expect(icon).toHaveAttribute("aria-hidden", "false");
- });
+ it('allows custom aria-hidden', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ expect(icon).toHaveAttribute('aria-hidden', 'false');
+ });
- it("passes through aria-label", () => {
- render(
- ,
- );
- const icon = screen.getByTestId("icon");
- expect(icon).toHaveAttribute("aria-label", "Success");
- });
+ it('passes through aria-label', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ expect(icon).toHaveAttribute('aria-label', 'Success');
+ });
- it("passes through id attribute", () => {
- render();
- const icon = screen.getByTestId("icon");
- expect(icon).toHaveAttribute("id", "icon-id");
- });
- });
+ it('passes through id attribute', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ expect(icon).toHaveAttribute('id', 'icon-id');
+ });
+ });
- describe("Ref Forwarding", () => {
- it("forwards ref to SVG element", () => {
- let ref: SVGSVGElement | null = null;
- render(
- {
- ref = el;
- }}
- />,
- );
- expect(ref).toBeInstanceOf(SVGSVGElement);
- expect(ref!.tagName).toBe("svg");
- });
- });
+ describe('Ref Forwarding', () => {
+ it('forwards ref to SVG element', () => {
+ let ref: SVGSVGElement | null = null;
+ render(
+ {
+ ref = el;
+ }}
+ />
+ );
+ expect(ref).toBeInstanceOf(SVGSVGElement);
+ expect(ref!.tagName).toBe('svg');
+ });
+ });
- describe("Base Classes", () => {
- it("always includes base classes", () => {
- render();
- const icon = screen.getByTestId("icon");
- expect(icon.className).toContain("inline-block");
- expect(icon.className).toContain("fill-current");
- });
- });
+ describe('Base Classes', () => {
+ it('always includes base classes', () => {
+ render();
+ const icon = screen.getByTestId('icon');
+ expect(icon.className).toContain('inline-block');
+ expect(icon.className).toContain('fill-current');
+ });
+ });
- describe("Compound Scenarios", () => {
- it("works correctly with custom size and className", () => {
- render(
- ,
- );
- const icon = screen.getByTestId("icon");
- expect(icon.className).toContain("w-6");
- expect(icon.className).toContain("h-6");
- expect(icon.className).toContain("text-zinc-400");
- expect(icon.className).toContain("hover:text-zinc-100");
- });
+ describe('Compound Scenarios', () => {
+ it('works correctly with custom size and className', () => {
+ render(
+
+ );
+ const icon = screen.getByTestId('icon');
+ expect(icon.className).toContain('w-6');
+ expect(icon.className).toContain('h-6');
+ expect(icon.className).toContain('text-zinc-400');
+ expect(icon.className).toContain('hover:text-zinc-100');
+ });
- it("works correctly with aria attributes and custom size", () => {
- render(
- ,
- );
- const icon = screen.getByTestId("icon");
- expect(icon.className).toContain("w-4");
- expect(icon.className).toContain("h-4");
- expect(icon).toHaveAttribute("aria-label", "Opens in new tab");
- expect(icon).toHaveAttribute("aria-hidden", "false");
- });
- });
+ it('works correctly with aria attributes and custom size', () => {
+ render(
+
+ );
+ const icon = screen.getByTestId('icon');
+ expect(icon.className).toContain('w-4');
+ expect(icon.className).toContain('h-4');
+ expect(icon).toHaveAttribute('aria-label', 'Opens in new tab');
+ expect(icon).toHaveAttribute('aria-hidden', 'false');
+ });
+ });
});
diff --git a/packages/ui/src/Icon/Icon.tsx b/packages/ui/src/Icon/Icon.tsx
index 15585b3..3f6b2ed 100644
--- a/packages/ui/src/Icon/Icon.tsx
+++ b/packages/ui/src/Icon/Icon.tsx
@@ -1,8 +1,8 @@
-import { cn } from "@tpmjs/utils/cn";
-import { forwardRef } from "react";
-import { type IconName, icons } from "./icons";
-import type { IconProps } from "./types";
-import { iconVariants } from "./variants";
+import { cn } from '@tpmjs/utils/cn';
+import { forwardRef } from 'react';
+import { type IconName, icons } from './icons';
+import type { IconProps } from './types';
+import { iconVariants } from './variants';
export type { IconName };
@@ -22,27 +22,27 @@ export type { IconName };
* ```
*/
export const Icon = forwardRef(
- ({ className, icon, size = "md", ...props }, ref) => {
- const iconData = icons[icon];
+ ({ className, icon, size = 'md', ...props }, ref) => {
+ const iconData = icons[icon];
- return (
-
- );
- },
+ return (
+
+ );
+ }
);
-Icon.displayName = "Icon";
+Icon.displayName = 'Icon';
diff --git a/packages/ui/src/Icon/icons.ts b/packages/ui/src/Icon/icons.ts
index f4a9c25..cd57c82 100644
--- a/packages/ui/src/Icon/icons.ts
+++ b/packages/ui/src/Icon/icons.ts
@@ -4,38 +4,38 @@
*/
export const icons = {
- copy: {
- viewBox: "0 0 24 24",
- path: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z",
- },
- externalLink: {
- viewBox: "0 0 24 24",
- path: "M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z",
- },
- github: {
- viewBox: "0 0 24 24",
- path: "M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.17 6.839 9.49.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.603-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.831.092-.646.35-1.086.636-1.336-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.167 22 16.418 22 12c0-5.523-4.477-10-10-10z",
- },
- check: {
- viewBox: "0 0 24 24",
- path: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
- },
- x: {
- viewBox: "0 0 24 24",
- path: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z",
- },
- chevronDown: {
- viewBox: "0 0 24 24",
- path: "M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z",
- },
- sun: {
- viewBox: "0 0 24 24",
- path: "M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z",
- },
- moon: {
- viewBox: "0 0 24 24",
- path: "M9.37 5.51c-.18.64-.27 1.31-.27 1.99 0 4.08 3.32 7.4 7.4 7.4.68 0 1.35-.09 1.99-.27C17.45 17.19 14.93 19 12 19c-3.86 0-7-3.14-7-7 0-2.93 1.81-5.45 4.37-6.49zM12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z",
- },
+ copy: {
+ viewBox: '0 0 24 24',
+ path: 'M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z',
+ },
+ externalLink: {
+ viewBox: '0 0 24 24',
+ path: 'M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z',
+ },
+ github: {
+ viewBox: '0 0 24 24',
+ path: 'M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.17 6.839 9.49.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.603-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.831.092-.646.35-1.086.636-1.336-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.167 22 16.418 22 12c0-5.523-4.477-10-10-10z',
+ },
+ check: {
+ viewBox: '0 0 24 24',
+ path: 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z',
+ },
+ x: {
+ viewBox: '0 0 24 24',
+ path: 'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z',
+ },
+ chevronDown: {
+ viewBox: '0 0 24 24',
+ path: 'M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z',
+ },
+ sun: {
+ viewBox: '0 0 24 24',
+ path: 'M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z',
+ },
+ moon: {
+ viewBox: '0 0 24 24',
+ path: 'M9.37 5.51c-.18.64-.27 1.31-.27 1.99 0 4.08 3.32 7.4 7.4 7.4.68 0 1.35-.09 1.99-.27C17.45 17.19 14.93 19 12 19c-3.86 0-7-3.14-7-7 0-2.93 1.81-5.45 4.37-6.49zM12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z',
+ },
} as const;
export type IconName = keyof typeof icons;
diff --git a/packages/ui/src/Icon/tokens.ts b/packages/ui/src/Icon/tokens.ts
index 90c56b6..e1fd90c 100644
--- a/packages/ui/src/Icon/tokens.ts
+++ b/packages/ui/src/Icon/tokens.ts
@@ -3,10 +3,10 @@
* Maps size variants to pixel dimensions
*/
export const iconTokens = {
- /** Icon sizes in pixels */
- size: {
- sm: "16px", // 1rem
- md: "20px", // 1.25rem
- lg: "24px", // 1.5rem
- },
+ /** Icon sizes in pixels */
+ size: {
+ sm: '16px', // 1rem
+ md: '20px', // 1.25rem
+ lg: '24px', // 1.5rem
+ },
} as const;
diff --git a/packages/ui/src/Icon/types.ts b/packages/ui/src/Icon/types.ts
index f8007f3..281aa4c 100644
--- a/packages/ui/src/Icon/types.ts
+++ b/packages/ui/src/Icon/types.ts
@@ -1,21 +1,20 @@
-import type { SVGAttributes } from "react";
-import type { IconName } from "./icons";
+import type { SVGAttributes } from 'react';
+import type { IconName } from './icons';
/**
* Icon component props
*/
-export interface IconProps
- extends Omit, "children"> {
- /**
- * Icon to display
- */
- icon: IconName;
+export interface IconProps extends Omit, 'children'> {
+ /**
+ * Icon to display
+ */
+ icon: IconName;
- /**
- * Size of the icon
- * @default 'md'
- */
- size?: "sm" | "md" | "lg";
+ /**
+ * Size of the icon
+ * @default 'md'
+ */
+ size?: 'sm' | 'md' | 'lg';
}
/**
diff --git a/packages/ui/src/Icon/variants.ts b/packages/ui/src/Icon/variants.ts
index 7599853..1db6927 100644
--- a/packages/ui/src/Icon/variants.ts
+++ b/packages/ui/src/Icon/variants.ts
@@ -1,28 +1,28 @@
-import { createVariants } from "../system/variants";
+import { createVariants } from '../system/variants';
/**
* Icon variant definitions
* Uses custom variant system for type-safe class composition
*/
export const iconVariants = createVariants({
- base: [
- // Display
- "inline-block",
- // Fill
- "fill-current",
- ].join(" "),
+ base: [
+ // Display
+ 'inline-block',
+ // Fill
+ 'fill-current',
+ ].join(' '),
- variants: {
- size: {
- sm: "w-4 h-4", // 16px
- md: "w-5 h-5", // 20px
- lg: "w-6 h-6", // 24px
- },
- },
+ variants: {
+ size: {
+ sm: 'w-4 h-4', // 16px
+ md: 'w-5 h-5', // 20px
+ lg: 'w-6 h-6', // 24px
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- size: "md",
- },
+ defaultVariants: {
+ size: 'md',
+ },
});
diff --git a/packages/ui/src/Input/Input.test.tsx b/packages/ui/src/Input/Input.test.tsx
index 9d5ea4d..f9d18a1 100644
--- a/packages/ui/src/Input/Input.test.tsx
+++ b/packages/ui/src/Input/Input.test.tsx
@@ -1,414 +1,412 @@
-import { fireEvent, render, screen } from "@testing-library/react";
-import { describe, expect, it, vi } from "vitest";
-import { Input } from "./Input";
+import { fireEvent, render, screen } from '@testing-library/react';
+import { describe, expect, it, vi } from 'vitest';
+import { Input } from './Input';
-describe("Input", () => {
- describe("Rendering", () => {
- it("renders an input element", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toBeInTheDocument();
- expect(input.tagName).toBe("INPUT");
- });
+describe('Input', () => {
+ describe('Rendering', () => {
+ it('renders an input element', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toBeInTheDocument();
+ expect(input.tagName).toBe('INPUT');
+ });
- it('renders with default type="text"', () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("type", "text");
- });
- });
+ it('renders with default type="text"', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('type', 'text');
+ });
+ });
- describe("States", () => {
- it("applies default state classes", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("border-border");
- expect(input.className).toContain("text-foreground");
- });
+ describe('States', () => {
+ it('applies default state classes', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('border-border');
+ expect(input.className).toContain('text-foreground');
+ });
- it("applies error state classes", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("border-error");
- });
+ it('applies error state classes', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('border-error');
+ });
- it("applies success state classes", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("border-success");
- });
+ it('applies success state classes', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('border-success');
+ });
- it("sets aria-invalid on error state", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("aria-invalid", "true");
- });
+ it('sets aria-invalid on error state', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('aria-invalid', 'true');
+ });
- it("does not set aria-invalid on default state", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).not.toHaveAttribute("aria-invalid");
- });
+ it('does not set aria-invalid on default state', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).not.toHaveAttribute('aria-invalid');
+ });
- it("does not set aria-invalid on success state", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).not.toHaveAttribute("aria-invalid");
- });
- });
+ it('does not set aria-invalid on success state', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).not.toHaveAttribute('aria-invalid');
+ });
+ });
- describe("Sizes", () => {
- it("applies small size classes", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("h-9");
- expect(input.className).toContain("px-3");
- expect(input.className).toContain("text-sm");
- });
+ describe('Sizes', () => {
+ it('applies small size classes', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('h-9');
+ expect(input.className).toContain('px-3');
+ expect(input.className).toContain('text-sm');
+ });
- it("applies medium size classes (default)", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("h-10");
- expect(input.className).toContain("px-3");
- expect(input.className).toContain("text-base");
- });
+ it('applies medium size classes (default)', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('h-10');
+ expect(input.className).toContain('px-3');
+ expect(input.className).toContain('text-base');
+ });
- it("applies large size classes", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("h-11");
- expect(input.className).toContain("px-4");
- expect(input.className).toContain("text-lg");
- });
- });
+ it('applies large size classes', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('h-11');
+ expect(input.className).toContain('px-4');
+ expect(input.className).toContain('text-lg');
+ });
+ });
- describe("Full Width", () => {
- it("is full width by default", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("w-full");
- });
+ describe('Full Width', () => {
+ it('is full width by default', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('w-full');
+ });
- it("applies full width when explicitly true", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("w-full");
- });
+ it('applies full width when explicitly true', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('w-full');
+ });
- it("does not apply full width when false", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("w-auto");
- });
- });
+ it('does not apply full width when false', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('w-auto');
+ });
+ });
- describe("Input Types", () => {
- it('renders with type="email"', () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("type", "email");
- });
+ describe('Input Types', () => {
+ it('renders with type="email"', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('type', 'email');
+ });
- it('renders with type="password"', () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("type", "password");
- });
+ it('renders with type="password"', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('type', 'password');
+ });
- it('renders with type="number"', () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("type", "number");
- });
+ it('renders with type="number"', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('type', 'number');
+ });
- it('renders with type="tel"', () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("type", "tel");
- });
+ it('renders with type="tel"', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('type', 'tel');
+ });
- it('renders with type="url"', () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("type", "url");
- });
+ it('renders with type="url"', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('type', 'url');
+ });
- it('renders with type="search"', () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("type", "search");
- });
+ it('renders with type="search"', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('type', 'search');
+ });
- it('renders with type="date"', () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("type", "date");
- });
+ it('renders with type="date"', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('type', 'date');
+ });
- it('renders with type="time"', () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("type", "time");
- });
+ it('renders with type="time"', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('type', 'time');
+ });
- it('renders with type="file"', () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("type", "file");
- });
- });
+ it('renders with type="file"', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('type', 'file');
+ });
+ });
- describe("Disabled State", () => {
- it("is not disabled by default", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).not.toBeDisabled();
- });
+ describe('Disabled State', () => {
+ it('is not disabled by default', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).not.toBeDisabled();
+ });
- it("disables input when disabled prop is true", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toBeDisabled();
- });
+ it('disables input when disabled prop is true', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toBeDisabled();
+ });
- it("applies disabled opacity class", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("disabled:opacity-50");
- expect(input.className).toContain("disabled:cursor-not-allowed");
- });
- });
+ it('applies disabled opacity class', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('disabled:opacity-50');
+ expect(input.className).toContain('disabled:cursor-not-allowed');
+ });
+ });
- describe("Placeholder", () => {
- it("renders with placeholder text", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("placeholder", "Enter your email");
- });
+ describe('Placeholder', () => {
+ it('renders with placeholder text', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('placeholder', 'Enter your email');
+ });
- it("applies placeholder styling classes", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("placeholder:text-foreground-tertiary");
- });
- });
+ it('applies placeholder styling classes', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('placeholder:text-foreground-tertiary');
+ });
+ });
- describe("Value and onChange", () => {
- it("renders with initial value", () => {
- render(
- {}} data-testid="input" />,
- );
- const input = screen.getByTestId("input") as HTMLInputElement;
- expect(input.value).toBe("test value");
- });
+ describe('Value and onChange', () => {
+ it('renders with initial value', () => {
+ render( {}} data-testid="input" />);
+ const input = screen.getByTestId('input') as HTMLInputElement;
+ expect(input.value).toBe('test value');
+ });
- it("calls onChange when input value changes", () => {
- const handleChange = vi.fn();
- render();
- const input = screen.getByTestId("input");
- fireEvent.change(input, { target: { value: "new value" } });
- expect(handleChange).toHaveBeenCalledTimes(1);
- });
+ it('calls onChange when input value changes', () => {
+ const handleChange = vi.fn();
+ render();
+ const input = screen.getByTestId('input');
+ fireEvent.change(input, { target: { value: 'new value' } });
+ expect(handleChange).toHaveBeenCalledTimes(1);
+ });
- it("updates value correctly when typing", () => {
- render();
- const input = screen.getByTestId("input") as HTMLInputElement;
- fireEvent.change(input, { target: { value: "typed text" } });
- expect(input.value).toBe("typed text");
- });
- });
+ it('updates value correctly when typing', () => {
+ render();
+ const input = screen.getByTestId('input') as HTMLInputElement;
+ fireEvent.change(input, { target: { value: 'typed text' } });
+ expect(input.value).toBe('typed text');
+ });
+ });
- describe("HTML Attributes", () => {
- it("passes through id attribute", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("id", "test-input");
- });
+ describe('HTML Attributes', () => {
+ it('passes through id attribute', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('id', 'test-input');
+ });
- it("passes through name attribute", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("name", "email");
- });
+ it('passes through name attribute', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('name', 'email');
+ });
- it("passes through required attribute", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("required");
- });
+ it('passes through required attribute', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('required');
+ });
- it("passes through maxLength attribute", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("maxLength", "100");
- });
+ it('passes through maxLength attribute', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('maxLength', '100');
+ });
- it("passes through pattern attribute", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("pattern", "[0-9]*");
- });
+ it('passes through pattern attribute', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('pattern', '[0-9]*');
+ });
- it("passes through min and max for number input", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("min", "0");
- expect(input).toHaveAttribute("max", "100");
- });
+ it('passes through min and max for number input', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('min', '0');
+ expect(input).toHaveAttribute('max', '100');
+ });
- it("passes through step for number input", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("step", "0.01");
- });
+ it('passes through step for number input', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('step', '0.01');
+ });
- it("passes through autoComplete attribute", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("autoComplete", "email");
- });
+ it('passes through autoComplete attribute', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('autoComplete', 'email');
+ });
- it("passes through autoFocus attribute", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveFocus();
- });
- });
+ it('passes through autoFocus attribute', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveFocus();
+ });
+ });
- describe("ARIA Attributes", () => {
- it("passes through aria-label", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("aria-label", "Email address");
- });
+ describe('ARIA Attributes', () => {
+ it('passes through aria-label', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('aria-label', 'Email address');
+ });
- it("passes through aria-describedby", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("aria-describedby", "helper-text");
- });
+ it('passes through aria-describedby', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('aria-describedby', 'helper-text');
+ });
- it("passes through aria-labelledby", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toHaveAttribute("aria-labelledby", "label-id");
- });
- });
+ it('passes through aria-labelledby', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toHaveAttribute('aria-labelledby', 'label-id');
+ });
+ });
- describe("Custom className", () => {
- it("merges custom className with variant classes", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("custom-input");
- expect(input.className).toContain("rounded-md");
- expect(input.className).toContain("border");
- });
- });
+ describe('Custom className', () => {
+ it('merges custom className with variant classes', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('custom-input');
+ expect(input.className).toContain('rounded-md');
+ expect(input.className).toContain('border');
+ });
+ });
- describe("Ref Forwarding", () => {
- it("forwards ref to input element", () => {
- let ref: HTMLInputElement | null = null;
- render(
- {
- ref = el;
- }}
- />,
- );
- expect(ref).toBeInstanceOf(HTMLInputElement);
- expect(ref!.tagName).toBe("INPUT");
- });
+ describe('Ref Forwarding', () => {
+ it('forwards ref to input element', () => {
+ let ref: HTMLInputElement | null = null;
+ render(
+ {
+ ref = el;
+ }}
+ />
+ );
+ expect(ref).toBeInstanceOf(HTMLInputElement);
+ expect(ref!.tagName).toBe('INPUT');
+ });
- it("can focus input through ref", () => {
- let ref: HTMLInputElement | null = null;
- render(
- {
- ref = el;
- }}
- data-testid="input"
- />,
- );
- ref!.focus();
- expect(screen.getByTestId("input")).toHaveFocus();
- });
- });
+ it('can focus input through ref', () => {
+ let ref: HTMLInputElement | null = null;
+ render(
+ {
+ ref = el;
+ }}
+ data-testid="input"
+ />
+ );
+ ref!.focus();
+ expect(screen.getByTestId('input')).toHaveFocus();
+ });
+ });
- describe("Base Classes", () => {
- it("always includes base classes", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("flex");
- expect(input.className).toContain("w-full");
- expect(input.className).toContain("font-sans");
- expect(input.className).toContain("rounded-md");
- expect(input.className).toContain("border");
- expect(input.className).toContain("bg-background");
- });
+ describe('Base Classes', () => {
+ it('always includes base classes', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('flex');
+ expect(input.className).toContain('w-full');
+ expect(input.className).toContain('font-sans');
+ expect(input.className).toContain('rounded-md');
+ expect(input.className).toContain('border');
+ expect(input.className).toContain('bg-background');
+ });
- it("includes transition classes", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("transition-base");
- });
+ it('includes transition classes', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('transition-base');
+ });
- it("includes focus ring classes", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("focus-ring");
- });
- });
+ it('includes focus ring classes', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('focus-ring');
+ });
+ });
- describe("Event Handlers", () => {
- it("calls onFocus when input is focused", () => {
- const handleFocus = vi.fn();
- render();
- const input = screen.getByTestId("input");
- fireEvent.focus(input);
- expect(handleFocus).toHaveBeenCalledTimes(1);
- });
+ describe('Event Handlers', () => {
+ it('calls onFocus when input is focused', () => {
+ const handleFocus = vi.fn();
+ render();
+ const input = screen.getByTestId('input');
+ fireEvent.focus(input);
+ expect(handleFocus).toHaveBeenCalledTimes(1);
+ });
- it("calls onBlur when input loses focus", () => {
- const handleBlur = vi.fn();
- render();
- const input = screen.getByTestId("input");
- fireEvent.focus(input);
- fireEvent.blur(input);
- expect(handleBlur).toHaveBeenCalledTimes(1);
- });
+ it('calls onBlur when input loses focus', () => {
+ const handleBlur = vi.fn();
+ render();
+ const input = screen.getByTestId('input');
+ fireEvent.focus(input);
+ fireEvent.blur(input);
+ expect(handleBlur).toHaveBeenCalledTimes(1);
+ });
- it("calls onKeyDown when key is pressed", () => {
- const handleKeyDown = vi.fn();
- render();
- const input = screen.getByTestId("input");
- fireEvent.keyDown(input, { key: "Enter" });
- expect(handleKeyDown).toHaveBeenCalledTimes(1);
- });
- });
+ it('calls onKeyDown when key is pressed', () => {
+ const handleKeyDown = vi.fn();
+ render();
+ const input = screen.getByTestId('input');
+ fireEvent.keyDown(input, { key: 'Enter' });
+ expect(handleKeyDown).toHaveBeenCalledTimes(1);
+ });
+ });
- describe("Compound Scenarios", () => {
- it("works correctly with error state and small size", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("border-error");
- expect(input.className).toContain("h-9");
- expect(input).toHaveAttribute("aria-invalid", "true");
- });
+ describe('Compound Scenarios', () => {
+ it('works correctly with error state and small size', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('border-error');
+ expect(input.className).toContain('h-9');
+ expect(input).toHaveAttribute('aria-invalid', 'true');
+ });
- it("works correctly with success state and large size", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input.className).toContain("border-success");
- expect(input.className).toContain("h-11");
- });
+ it('works correctly with success state and large size', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input.className).toContain('border-success');
+ expect(input.className).toContain('h-11');
+ });
- it("works correctly with disabled state and error state", () => {
- render();
- const input = screen.getByTestId("input");
- expect(input).toBeDisabled();
- expect(input.className).toContain("border-error");
- expect(input).toHaveAttribute("aria-invalid", "true");
- });
- });
+ it('works correctly with disabled state and error state', () => {
+ render();
+ const input = screen.getByTestId('input');
+ expect(input).toBeDisabled();
+ expect(input.className).toContain('border-error');
+ expect(input).toHaveAttribute('aria-invalid', 'true');
+ });
+ });
});
diff --git a/packages/ui/src/Input/Input.tsx b/packages/ui/src/Input/Input.tsx
index f136599..7c5db69 100644
--- a/packages/ui/src/Input/Input.tsx
+++ b/packages/ui/src/Input/Input.tsx
@@ -1,7 +1,7 @@
-import { cn } from "@tpmjs/utils/cn";
-import { forwardRef } from "react";
-import type { InputProps } from "./types";
-import { inputVariants } from "./variants";
+import { cn } from '@tpmjs/utils/cn';
+import { forwardRef } from 'react';
+import type { InputProps } from './types';
+import { inputVariants } from './variants';
/**
* Input component
@@ -25,36 +25,36 @@ import { inputVariants } from "./variants";
* ```
*/
export const Input = forwardRef(
- (
- {
- className,
- type = "text",
- state = "default",
- size = "md",
- fullWidth = true,
- disabled = false,
- ...props
- },
- ref,
- ) => {
- return (
-
- );
- },
+ (
+ {
+ className,
+ type = 'text',
+ state = 'default',
+ size = 'md',
+ fullWidth = true,
+ disabled = false,
+ ...props
+ },
+ ref
+ ) => {
+ return (
+
+ );
+ }
);
-Input.displayName = "Input";
+Input.displayName = 'Input';
diff --git a/packages/ui/src/Input/tokens.ts b/packages/ui/src/Input/tokens.ts
index 2b35e75..264956f 100644
--- a/packages/ui/src/Input/tokens.ts
+++ b/packages/ui/src/Input/tokens.ts
@@ -1,44 +1,44 @@
-import { borderRadius, spacing } from "../tokens";
+import { borderRadius, spacing } from '../tokens';
/**
* Input component design tokens
* Maps global tokens to input-specific semantics
*/
export const inputTokens = {
- /** Input heights for each size */
- height: {
- sm: spacing[9], // 2.25rem (36px)
- md: spacing[10], // 2.5rem (40px)
- lg: spacing[11], // 2.75rem (44px)
- },
+ /** Input heights for each size */
+ height: {
+ sm: spacing[9], // 2.25rem (36px)
+ md: spacing[10], // 2.5rem (40px)
+ lg: spacing[11], // 2.75rem (44px)
+ },
- /** Horizontal padding for each size */
- padding: {
- x: {
- sm: spacing[3], // 0.75rem (12px)
- md: spacing[3], // 0.75rem (12px)
- lg: spacing[4], // 1rem (16px)
- },
- y: {
- sm: spacing[2], // 0.5rem (8px)
- md: spacing[2.5], // 0.625rem (10px)
- lg: spacing[3], // 0.75rem (12px)
- },
- },
+ /** Horizontal padding for each size */
+ padding: {
+ x: {
+ sm: spacing[3], // 0.75rem (12px)
+ md: spacing[3], // 0.75rem (12px)
+ lg: spacing[4], // 1rem (16px)
+ },
+ y: {
+ sm: spacing[2], // 0.5rem (8px)
+ md: spacing[2.5], // 0.625rem (10px)
+ lg: spacing[3], // 0.75rem (12px)
+ },
+ },
- /** Font sizes for each size */
- fontSize: {
- sm: "0.875rem", // 14px
- md: "1rem", // 16px
- lg: "1.125rem", // 18px
- },
+ /** Font sizes for each size */
+ fontSize: {
+ sm: '0.875rem', // 14px
+ md: '1rem', // 16px
+ lg: '1.125rem', // 18px
+ },
- /** Border radius */
- borderRadius: borderRadius.md,
+ /** Border radius */
+ borderRadius: borderRadius.md,
- /** Border width */
- borderWidth: "1px",
+ /** Border width */
+ borderWidth: '1px',
- /** Transition duration */
- transitionDuration: "200ms",
+ /** Transition duration */
+ transitionDuration: '200ms',
} as const;
diff --git a/packages/ui/src/Input/types.ts b/packages/ui/src/Input/types.ts
index 94b6ac1..e5b2538 100644
--- a/packages/ui/src/Input/types.ts
+++ b/packages/ui/src/Input/types.ts
@@ -1,27 +1,26 @@
-import type { InputHTMLAttributes } from "react";
+import type { InputHTMLAttributes } from 'react';
/**
* Input component props
*/
-export interface InputProps
- extends Omit, "size"> {
- /**
- * Visual state of the input
- * @default 'default'
- */
- state?: "default" | "error" | "success";
+export interface InputProps extends Omit, 'size'> {
+ /**
+ * Visual state of the input
+ * @default 'default'
+ */
+ state?: 'default' | 'error' | 'success';
- /**
- * Size of the input
- * @default 'md'
- */
- size?: "sm" | "md" | "lg";
+ /**
+ * Size of the input
+ * @default 'md'
+ */
+ size?: 'sm' | 'md' | 'lg';
- /**
- * Full width input
- * @default false
- */
- fullWidth?: boolean;
+ /**
+ * Full width input
+ * @default false
+ */
+ fullWidth?: boolean;
}
/**
diff --git a/packages/ui/src/Input/variants.ts b/packages/ui/src/Input/variants.ts
index 7120473..61d78aa 100644
--- a/packages/ui/src/Input/variants.ts
+++ b/packages/ui/src/Input/variants.ts
@@ -1,71 +1,71 @@
-import { createVariants } from "../system/variants";
+import { createVariants } from '../system/variants';
/**
* Input variant definitions
* Uses custom variant system for type-safe class composition
*/
export const inputVariants = createVariants({
- base: [
- // Layout
- "flex w-full",
- // Typography
- "font-sans",
- // Borders & Radius
- "rounded-md border",
- // Background
- "bg-background",
- // Transitions
- "transition-base",
- // Focus
- "focus-ring",
- // Placeholder
- "placeholder:text-foreground-tertiary",
- // File input
- "file:border-0 file:bg-transparent file:text-sm file:font-medium",
- // Disabled state
- "disabled:cursor-not-allowed disabled:opacity-50",
- ].join(" "),
+ base: [
+ // Layout
+ 'flex w-full',
+ // Typography
+ 'font-sans',
+ // Borders & Radius
+ 'rounded-md border',
+ // Background
+ 'bg-background',
+ // Transitions
+ 'transition-base',
+ // Focus
+ 'focus-ring',
+ // Placeholder
+ 'placeholder:text-foreground-tertiary',
+ // File input
+ 'file:border-0 file:bg-transparent file:text-sm file:font-medium',
+ // Disabled state
+ 'disabled:cursor-not-allowed disabled:opacity-50',
+ ].join(' '),
- variants: {
- state: {
- default: [
- "border-border text-foreground",
- "hover:border-border-strong",
- "focus:border-primary",
- ].join(" "),
+ variants: {
+ state: {
+ default: [
+ 'border-border text-foreground',
+ 'hover:border-border-strong',
+ 'focus:border-primary',
+ ].join(' '),
- error: [
- "border-error text-foreground",
- "hover:border-error",
- "focus:border-error",
- "focus:ring-error/20",
- ].join(" "),
+ error: [
+ 'border-error text-foreground',
+ 'hover:border-error',
+ 'focus:border-error',
+ 'focus:ring-error/20',
+ ].join(' '),
- success: [
- "border-success text-foreground",
- "hover:border-success",
- "focus:border-success",
- "focus:ring-success/20",
- ].join(" "),
- },
+ success: [
+ 'border-success text-foreground',
+ 'hover:border-success',
+ 'focus:border-success',
+ 'focus:ring-success/20',
+ ].join(' '),
+ },
- size: {
- sm: "h-9 px-3 py-2 text-sm",
- md: "h-10 px-3 py-2.5 text-base",
- lg: "h-11 px-4 py-3 text-lg",
- },
+ size: {
+ sm: 'h-9 px-3 py-2 text-sm',
+ md: 'h-10 px-3 py-2.5 text-base',
+ lg: 'h-11 px-4 py-3 text-lg',
+ },
- fullWidth: {
- true: "w-full",
- false: "w-auto",
- },
- },
+ fullWidth: {
+ true: 'w-full',
+ false: 'w-auto',
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- state: "default",
- size: "md",
- fullWidth: "true",
- },
+ defaultVariants: {
+ state: 'default',
+ size: 'md',
+ fullWidth: 'true',
+ },
});
diff --git a/packages/ui/src/Label/Label.test.tsx b/packages/ui/src/Label/Label.test.tsx
index 8afeefd..b683d25 100644
--- a/packages/ui/src/Label/Label.test.tsx
+++ b/packages/ui/src/Label/Label.test.tsx
@@ -1,317 +1,317 @@
-import { render, screen } from "@testing-library/react";
-import { describe, expect, it } from "vitest";
-import { Label } from "./Label";
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { Label } from './Label';
-describe("Label", () => {
- describe("Rendering", () => {
- it("renders a label element", () => {
- render();
- const label = screen.getByTestId("label");
- expect(label).toBeInTheDocument();
- expect(label.tagName).toBe("LABEL");
- });
+describe('Label', () => {
+ describe('Rendering', () => {
+ it('renders a label element', () => {
+ render();
+ const label = screen.getByTestId('label');
+ expect(label).toBeInTheDocument();
+ expect(label.tagName).toBe('LABEL');
+ });
- it("renders children text", () => {
- render();
- expect(screen.getByText("Email Address")).toBeInTheDocument();
- });
- });
+ it('renders children text', () => {
+ render();
+ expect(screen.getByText('Email Address')).toBeInTheDocument();
+ });
+ });
- describe("Sizes", () => {
- it("applies small size classes", () => {
- render(
- ,
- );
- const label = screen.getByTestId("label");
- expect(label.className).toContain("text-sm");
- });
+ describe('Sizes', () => {
+ it('applies small size classes', () => {
+ render(
+
+ );
+ const label = screen.getByTestId('label');
+ expect(label.className).toContain('text-sm');
+ });
- it("applies medium size classes (default)", () => {
- render(
- ,
- );
- const label = screen.getByTestId("label");
- expect(label.className).toContain("text-base");
- });
+ it('applies medium size classes (default)', () => {
+ render(
+
+ );
+ const label = screen.getByTestId('label');
+ expect(label.className).toContain('text-base');
+ });
- it("applies large size classes", () => {
- render(
- ,
- );
- const label = screen.getByTestId("label");
- expect(label.className).toContain("text-lg");
- });
+ it('applies large size classes', () => {
+ render(
+
+ );
+ const label = screen.getByTestId('label');
+ expect(label.className).toContain('text-lg');
+ });
- it("uses medium size by default", () => {
- render();
- const label = screen.getByTestId("label");
- expect(label.className).toContain("text-base");
- });
- });
+ it('uses medium size by default', () => {
+ render();
+ const label = screen.getByTestId('label');
+ expect(label.className).toContain('text-base');
+ });
+ });
- describe("Required Indicator", () => {
- it("does not show required indicator by default", () => {
- render();
- expect(screen.queryByText("*")).not.toBeInTheDocument();
- });
+ describe('Required Indicator', () => {
+ it('does not show required indicator by default', () => {
+ render();
+ expect(screen.queryByText('*')).not.toBeInTheDocument();
+ });
- it("shows required indicator when required is true", () => {
- render();
- expect(screen.getByText("*")).toBeInTheDocument();
- });
+ it('shows required indicator when required is true', () => {
+ render();
+ expect(screen.getByText('*')).toBeInTheDocument();
+ });
- it("hides required indicator from screen readers", () => {
- render(
- ,
- );
- const asterisk = screen.getByText("*");
- expect(asterisk).toHaveAttribute("aria-hidden", "true");
- });
+ it('hides required indicator from screen readers', () => {
+ render(
+
+ );
+ const asterisk = screen.getByText('*');
+ expect(asterisk).toHaveAttribute('aria-hidden', 'true');
+ });
- it("applies error color to required indicator", () => {
- render();
- const asterisk = screen.getByText("*");
- expect(asterisk.className).toContain("text-error");
- });
+ it('applies error color to required indicator', () => {
+ render();
+ const asterisk = screen.getByText('*');
+ expect(asterisk.className).toContain('text-error');
+ });
- it("applies proper spacing to required indicator", () => {
- render();
- const asterisk = screen.getByText("*");
- expect(asterisk.className).toContain("ml-1");
- });
- });
+ it('applies proper spacing to required indicator', () => {
+ render();
+ const asterisk = screen.getByText('*');
+ expect(asterisk.className).toContain('ml-1');
+ });
+ });
- describe("Disabled State", () => {
- it("is not disabled by default", () => {
- render();
- const label = screen.getByTestId("label");
- // Should not have the direct opacity-50 class (peer-disabled:opacity-50 is okay)
- expect(label.className).toContain("peer-disabled:opacity-50");
- expect(label.className.split(" ")).not.toContain("opacity-50");
- });
+ describe('Disabled State', () => {
+ it('is not disabled by default', () => {
+ render();
+ const label = screen.getByTestId('label');
+ // Should not have the direct opacity-50 class (peer-disabled:opacity-50 is okay)
+ expect(label.className).toContain('peer-disabled:opacity-50');
+ expect(label.className.split(' ')).not.toContain('opacity-50');
+ });
- it("applies disabled classes when disabled is true", () => {
- render(
- ,
- );
- const label = screen.getByTestId("label");
- expect(label.className).toContain("opacity-50");
- expect(label.className).toContain("cursor-not-allowed");
- });
- });
+ it('applies disabled classes when disabled is true', () => {
+ render(
+
+ );
+ const label = screen.getByTestId('label');
+ expect(label.className).toContain('opacity-50');
+ expect(label.className).toContain('cursor-not-allowed');
+ });
+ });
- describe("HTML Attributes", () => {
- it("associates with input using htmlFor", () => {
- render(
- ,
- );
- const label = screen.getByTestId("label");
- expect(label).toHaveAttribute("for", "email-input");
- });
+ describe('HTML Attributes', () => {
+ it('associates with input using htmlFor', () => {
+ render(
+
+ );
+ const label = screen.getByTestId('label');
+ expect(label).toHaveAttribute('for', 'email-input');
+ });
- it("passes through id attribute", () => {
- render(
- ,
- );
- const label = screen.getByTestId("label");
- expect(label).toHaveAttribute("id", "label-id");
- });
+ it('passes through id attribute', () => {
+ render(
+
+ );
+ const label = screen.getByTestId('label');
+ expect(label).toHaveAttribute('id', 'label-id');
+ });
- it("passes through className", () => {
- render(
- ,
- );
- const label = screen.getByTestId("label");
- expect(label.className).toContain("custom-label");
- });
+ it('passes through className', () => {
+ render(
+
+ );
+ const label = screen.getByTestId('label');
+ expect(label.className).toContain('custom-label');
+ });
- it("passes through onClick handler", () => {
- let clicked = false;
- render(
- ,
- );
- const label = screen.getByTestId("label");
- label.click();
- expect(clicked).toBe(true);
- });
- });
+ it('passes through onClick handler', () => {
+ let clicked = false;
+ render(
+
+ );
+ const label = screen.getByTestId('label');
+ label.click();
+ expect(clicked).toBe(true);
+ });
+ });
- describe("ARIA Attributes", () => {
- it("passes through aria-label", () => {
- render(
- ,
- );
- const label = screen.getByTestId("label");
- expect(label).toHaveAttribute("aria-label", "Field label");
- });
+ describe('ARIA Attributes', () => {
+ it('passes through aria-label', () => {
+ render(
+
+ );
+ const label = screen.getByTestId('label');
+ expect(label).toHaveAttribute('aria-label', 'Field label');
+ });
- it("passes through aria-describedby", () => {
- render(
- ,
- );
- const label = screen.getByTestId("label");
- expect(label).toHaveAttribute("aria-describedby", "description-id");
- });
- });
+ it('passes through aria-describedby', () => {
+ render(
+
+ );
+ const label = screen.getByTestId('label');
+ expect(label).toHaveAttribute('aria-describedby', 'description-id');
+ });
+ });
- describe("Custom className", () => {
- it("merges custom className with base classes", () => {
- render(
- ,
- );
- const label = screen.getByTestId("label");
- expect(label.className).toContain("custom-class");
- expect(label.className).toContain("font-medium");
- expect(label.className).toContain("text-foreground");
- });
- });
+ describe('Custom className', () => {
+ it('merges custom className with base classes', () => {
+ render(
+
+ );
+ const label = screen.getByTestId('label');
+ expect(label.className).toContain('custom-class');
+ expect(label.className).toContain('font-medium');
+ expect(label.className).toContain('text-foreground');
+ });
+ });
- describe("Ref Forwarding", () => {
- it("forwards ref to label element", () => {
- let ref: HTMLLabelElement | null = null;
- render(
- ,
- );
- expect(ref).toBeInstanceOf(HTMLLabelElement);
- expect(ref!.tagName).toBe("LABEL");
- });
- });
+ describe('Ref Forwarding', () => {
+ it('forwards ref to label element', () => {
+ let ref: HTMLLabelElement | null = null;
+ render(
+
+ );
+ expect(ref).toBeInstanceOf(HTMLLabelElement);
+ expect(ref!.tagName).toBe('LABEL');
+ });
+ });
- describe("Base Classes", () => {
- it("always includes base classes", () => {
- render();
- const label = screen.getByTestId("label");
- expect(label.className).toContain("font-medium");
- expect(label.className).toContain("text-foreground");
- expect(label.className).toContain("cursor-pointer");
- });
+ describe('Base Classes', () => {
+ it('always includes base classes', () => {
+ render();
+ const label = screen.getByTestId('label');
+ expect(label.className).toContain('font-medium');
+ expect(label.className).toContain('text-foreground');
+ expect(label.className).toContain('cursor-pointer');
+ });
- it("includes peer-disabled classes", () => {
- render();
- const label = screen.getByTestId("label");
- expect(label.className).toContain("peer-disabled:cursor-not-allowed");
- expect(label.className).toContain("peer-disabled:opacity-50");
- });
- });
+ it('includes peer-disabled classes', () => {
+ render();
+ const label = screen.getByTestId('label');
+ expect(label.className).toContain('peer-disabled:cursor-not-allowed');
+ expect(label.className).toContain('peer-disabled:opacity-50');
+ });
+ });
- describe("Compound Scenarios", () => {
- it("works correctly with required and small size", () => {
- render(
- ,
- );
- const label = screen.getByTestId("label");
- expect(label.className).toContain("text-sm");
- expect(screen.getByText("*")).toBeInTheDocument();
- });
+ describe('Compound Scenarios', () => {
+ it('works correctly with required and small size', () => {
+ render(
+
+ );
+ const label = screen.getByTestId('label');
+ expect(label.className).toContain('text-sm');
+ expect(screen.getByText('*')).toBeInTheDocument();
+ });
- it("works correctly with disabled and required", () => {
- render(
- ,
- );
- const label = screen.getByTestId("label");
- expect(label.className).toContain("opacity-50");
- expect(label.className).toContain("cursor-not-allowed");
- expect(screen.getByText("*")).toBeInTheDocument();
- });
+ it('works correctly with disabled and required', () => {
+ render(
+
+ );
+ const label = screen.getByTestId('label');
+ expect(label.className).toContain('opacity-50');
+ expect(label.className).toContain('cursor-not-allowed');
+ expect(screen.getByText('*')).toBeInTheDocument();
+ });
- it("works correctly with htmlFor and required", () => {
- render(
- ,
- );
- const label = screen.getByTestId("label");
- expect(label).toHaveAttribute("for", "field-id");
- expect(screen.getByText("*")).toBeInTheDocument();
- });
+ it('works correctly with htmlFor and required', () => {
+ render(
+
+ );
+ const label = screen.getByTestId('label');
+ expect(label).toHaveAttribute('for', 'field-id');
+ expect(screen.getByText('*')).toBeInTheDocument();
+ });
- it("works correctly with large size and custom className", () => {
- render(
- ,
- );
- const label = screen.getByTestId("label");
- expect(label.className).toContain("text-lg");
- expect(label.className).toContain("font-bold");
- });
- });
+ it('works correctly with large size and custom className', () => {
+ render(
+
+ );
+ const label = screen.getByTestId('label');
+ expect(label.className).toContain('text-lg');
+ expect(label.className).toContain('font-bold');
+ });
+ });
- describe("Integration with Form Controls", () => {
- it("associates correctly with input element", () => {
- const { container } = render(
-
-
-
-
,
- );
+ describe('Integration with Form Controls', () => {
+ it('associates correctly with input element', () => {
+ const { container } = render(
+
+
+
+
+ );
- const label = container.querySelector("label");
- const input = container.querySelector("input");
+ const label = container.querySelector('label');
+ const input = container.querySelector('input');
- expect(label).toHaveAttribute("for", "test-input");
- expect(input).toHaveAttribute("id", "test-input");
- });
+ expect(label).toHaveAttribute('for', 'test-input');
+ expect(input).toHaveAttribute('id', 'test-input');
+ });
- it("clicking label focuses associated input", () => {
- render(
-
-
-
-
,
- );
+ it('clicking label focuses associated input', () => {
+ render(
+
+
+
+
+ );
- const label = screen.getByTestId("label");
- const input = screen.getByTestId("input") as HTMLInputElement;
+ const label = screen.getByTestId('label');
+ const input = screen.getByTestId('input') as HTMLInputElement;
- // Verify the association exists
- expect(label).toHaveAttribute("for", "focus-input");
- expect(input).toHaveAttribute("id", "focus-input");
+ // Verify the association exists
+ expect(label).toHaveAttribute('for', 'focus-input');
+ expect(input).toHaveAttribute('id', 'focus-input');
- // Focus the input directly to verify it can receive focus
- input.focus();
- expect(input).toHaveFocus();
- });
- });
+ // Focus the input directly to verify it can receive focus
+ input.focus();
+ expect(input).toHaveFocus();
+ });
+ });
});
diff --git a/packages/ui/src/Label/Label.tsx b/packages/ui/src/Label/Label.tsx
index b8e9420..5e5aac6 100644
--- a/packages/ui/src/Label/Label.tsx
+++ b/packages/ui/src/Label/Label.tsx
@@ -1,7 +1,7 @@
-import { cn } from "@tpmjs/utils/cn";
-import { forwardRef } from "react";
-import type { LabelProps } from "./types";
-import { labelVariants } from "./variants";
+import { cn } from '@tpmjs/utils/cn';
+import { forwardRef } from 'react';
+import type { LabelProps } from './types';
+import { labelVariants } from './variants';
/**
* Label component
@@ -18,39 +18,29 @@ import { labelVariants } from "./variants";
* ```
*/
export const Label = forwardRef(
- (
- {
- className,
- size = "md",
- required = false,
- disabled = false,
- children,
- ...props
- },
- ref,
- ) => {
- return (
- // biome-ignore lint/a11y/noLabelWithoutControl: This is a generic label component that can be used with htmlFor or wrap inputs
-
- );
- },
+ ({ className, size = 'md', required = false, disabled = false, children, ...props }, ref) => {
+ return (
+ // biome-ignore lint/a11y/noLabelWithoutControl: This is a generic label component that can be used with htmlFor or wrap inputs
+
+ );
+ }
);
-Label.displayName = "Label";
+Label.displayName = 'Label';
diff --git a/packages/ui/src/Label/tokens.ts b/packages/ui/src/Label/tokens.ts
index d941e15..cf1c93d 100644
--- a/packages/ui/src/Label/tokens.ts
+++ b/packages/ui/src/Label/tokens.ts
@@ -1,27 +1,27 @@
-import { spacing } from "../tokens";
+import { spacing } from '../tokens';
/**
* Label component design tokens
* Maps global tokens to label-specific semantics
*/
export const labelTokens = {
- /** Font sizes for each size */
- fontSize: {
- sm: "0.875rem", // 14px
- md: "1rem", // 16px
- lg: "1.125rem", // 18px
- },
+ /** Font sizes for each size */
+ fontSize: {
+ sm: '0.875rem', // 14px
+ md: '1rem', // 16px
+ lg: '1.125rem', // 18px
+ },
- /** Line heights for each size */
- lineHeight: {
- sm: "1.25rem", // 20px
- md: "1.5rem", // 24px
- lg: "1.75rem", // 28px
- },
+ /** Line heights for each size */
+ lineHeight: {
+ sm: '1.25rem', // 20px
+ md: '1.5rem', // 24px
+ lg: '1.75rem', // 28px
+ },
- /** Margin bottom (spacing from label to input) */
- marginBottom: spacing[2], // 0.5rem (8px)
+ /** Margin bottom (spacing from label to input) */
+ marginBottom: spacing[2], // 0.5rem (8px)
- /** Required indicator spacing */
- requiredSpacing: spacing[1], // 0.25rem (4px)
+ /** Required indicator spacing */
+ requiredSpacing: spacing[1], // 0.25rem (4px)
} as const;
diff --git a/packages/ui/src/Label/types.ts b/packages/ui/src/Label/types.ts
index b121283..677c847 100644
--- a/packages/ui/src/Label/types.ts
+++ b/packages/ui/src/Label/types.ts
@@ -1,26 +1,26 @@
-import type { LabelHTMLAttributes } from "react";
+import type { LabelHTMLAttributes } from 'react';
/**
* Label component props
*/
export interface LabelProps extends LabelHTMLAttributes {
- /**
- * Size of the label
- * @default 'md'
- */
- size?: "sm" | "md" | "lg";
+ /**
+ * Size of the label
+ * @default 'md'
+ */
+ size?: 'sm' | 'md' | 'lg';
- /**
- * Whether the associated field is required
- * @default false
- */
- required?: boolean;
+ /**
+ * Whether the associated field is required
+ * @default false
+ */
+ required?: boolean;
- /**
- * Whether the label should be visually disabled
- * @default false
- */
- disabled?: boolean;
+ /**
+ * Whether the label should be visually disabled
+ * @default false
+ */
+ disabled?: boolean;
}
/**
diff --git a/packages/ui/src/Label/variants.ts b/packages/ui/src/Label/variants.ts
index 6264d69..0cbfbda 100644
--- a/packages/ui/src/Label/variants.ts
+++ b/packages/ui/src/Label/variants.ts
@@ -1,37 +1,37 @@
-import { createVariants } from "../system/variants";
+import { createVariants } from '../system/variants';
/**
* Label variant definitions
* Uses custom variant system for type-safe class composition
*/
export const labelVariants = createVariants({
- base: [
- // Typography
- "font-medium text-foreground",
- "leading-none",
- // Cursor
- "cursor-pointer",
- // Peer styling support
- "peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
- ].join(" "),
+ base: [
+ // Typography
+ 'font-medium text-foreground',
+ 'leading-none',
+ // Cursor
+ 'cursor-pointer',
+ // Peer styling support
+ 'peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
+ ].join(' '),
- variants: {
- size: {
- sm: "text-sm",
- md: "text-base",
- lg: "text-lg",
- },
+ variants: {
+ size: {
+ sm: 'text-sm',
+ md: 'text-base',
+ lg: 'text-lg',
+ },
- disabled: {
- true: "cursor-not-allowed opacity-50",
- false: "",
- },
- },
+ disabled: {
+ true: 'cursor-not-allowed opacity-50',
+ false: '',
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- size: "md",
- disabled: "false",
- },
+ defaultVariants: {
+ size: 'md',
+ disabled: 'false',
+ },
});
diff --git a/packages/ui/src/ProgressBar/ProgressBar.test.tsx b/packages/ui/src/ProgressBar/ProgressBar.test.tsx
index 7d13405..fe49d98 100644
--- a/packages/ui/src/ProgressBar/ProgressBar.test.tsx
+++ b/packages/ui/src/ProgressBar/ProgressBar.test.tsx
@@ -1,290 +1,265 @@
-import { render, screen } from "@testing-library/react";
-import { describe, expect, it } from "vitest";
-import { ProgressBar } from "./ProgressBar";
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { ProgressBar } from './ProgressBar';
-describe("ProgressBar", () => {
- describe("Rendering", () => {
- it("renders a progress bar element", () => {
- render();
- const progress = screen.getByTestId("progress");
- expect(progress).toBeInTheDocument();
- expect(progress.tagName).toBe("DIV");
- });
+describe('ProgressBar', () => {
+ describe('Rendering', () => {
+ it('renders a progress bar element', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress).toBeInTheDocument();
+ expect(progress.tagName).toBe('DIV');
+ });
- it("has progressbar role", () => {
- render();
- const progress = screen.getByTestId("progress");
- expect(progress).toHaveAttribute("role", "progressbar");
- });
+ it('has progressbar role', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress).toHaveAttribute('role', 'progressbar');
+ });
- it("sets correct aria attributes", () => {
- render();
- const progress = screen.getByTestId("progress");
- expect(progress).toHaveAttribute("aria-valuenow", "75");
- expect(progress).toHaveAttribute("aria-valuemin", "0");
- expect(progress).toHaveAttribute("aria-valuemax", "100");
- });
+ it('sets correct aria attributes', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress).toHaveAttribute('aria-valuenow', '75');
+ expect(progress).toHaveAttribute('aria-valuemin', '0');
+ expect(progress).toHaveAttribute('aria-valuemax', '100');
+ });
- it("renders fill element with correct width", () => {
- render();
- const progress = screen.getByTestId("progress");
- const fill = progress.querySelector("div");
- expect(fill).toHaveStyle({ width: "60%" });
- });
- });
+ it('renders fill element with correct width', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ const fill = progress.querySelector('div');
+ expect(fill).toHaveStyle({ width: '60%' });
+ });
+ });
- describe("Value Handling", () => {
- it("clamps value above 100 to 100", () => {
- render();
- const progress = screen.getByTestId("progress");
- expect(progress).toHaveAttribute("aria-valuenow", "100");
- const fill = progress.querySelector("div");
- expect(fill).toHaveStyle({ width: "100%" });
- });
+ describe('Value Handling', () => {
+ it('clamps value above 100 to 100', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress).toHaveAttribute('aria-valuenow', '100');
+ const fill = progress.querySelector('div');
+ expect(fill).toHaveStyle({ width: '100%' });
+ });
- it("clamps value below 0 to 0", () => {
- render();
- const progress = screen.getByTestId("progress");
- expect(progress).toHaveAttribute("aria-valuenow", "0");
- const fill = progress.querySelector("div");
- expect(fill).toHaveStyle({ width: "0%" });
- });
+ it('clamps value below 0 to 0', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress).toHaveAttribute('aria-valuenow', '0');
+ const fill = progress.querySelector('div');
+ expect(fill).toHaveStyle({ width: '0%' });
+ });
- it("handles 0 value", () => {
- render();
- const progress = screen.getByTestId("progress");
- expect(progress).toHaveAttribute("aria-valuenow", "0");
- const fill = progress.querySelector("div");
- expect(fill).toHaveStyle({ width: "0%" });
- });
+ it('handles 0 value', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress).toHaveAttribute('aria-valuenow', '0');
+ const fill = progress.querySelector('div');
+ expect(fill).toHaveStyle({ width: '0%' });
+ });
- it("handles 100 value", () => {
- render();
- const progress = screen.getByTestId("progress");
- expect(progress).toHaveAttribute("aria-valuenow", "100");
- const fill = progress.querySelector("div");
- expect(fill).toHaveStyle({ width: "100%" });
- });
+ it('handles 100 value', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress).toHaveAttribute('aria-valuenow', '100');
+ const fill = progress.querySelector('div');
+ expect(fill).toHaveStyle({ width: '100%' });
+ });
- it("handles decimal values", () => {
- render();
- const progress = screen.getByTestId("progress");
- expect(progress).toHaveAttribute("aria-valuenow", "33.7");
- const fill = progress.querySelector("div");
- expect(fill).toHaveStyle({ width: "33.7%" });
- });
- });
+ it('handles decimal values', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress).toHaveAttribute('aria-valuenow', '33.7');
+ const fill = progress.querySelector('div');
+ expect(fill).toHaveStyle({ width: '33.7%' });
+ });
+ });
- describe("Size Variants", () => {
- it("applies small size", () => {
- render();
- const progress = screen.getByTestId("progress");
- expect(progress.className).toContain("h-1");
- });
+ describe('Size Variants', () => {
+ it('applies small size', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress.className).toContain('h-1');
+ });
- it("applies medium size", () => {
- render();
- const progress = screen.getByTestId("progress");
- expect(progress.className).toContain("h-2");
- });
+ it('applies medium size', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress.className).toContain('h-2');
+ });
- it("applies large size", () => {
- render();
- const progress = screen.getByTestId("progress");
- expect(progress.className).toContain("h-3");
- });
+ it('applies large size', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress.className).toContain('h-3');
+ });
- it("uses md size by default", () => {
- render();
- const progress = screen.getByTestId("progress");
- expect(progress.className).toContain("h-2");
- });
- });
+ it('uses md size by default', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress.className).toContain('h-2');
+ });
+ });
- describe("Color Variants", () => {
- it("applies primary variant", () => {
- render(
- ,
- );
- const progress = screen.getByTestId("progress");
- const fill = progress.querySelector("div");
- expect(fill?.className).toContain("bg-primary");
- });
+ describe('Color Variants', () => {
+ it('applies primary variant', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ const fill = progress.querySelector('div');
+ expect(fill?.className).toContain('bg-primary');
+ });
- it("applies success variant", () => {
- render(
- ,
- );
- const progress = screen.getByTestId("progress");
- const fill = progress.querySelector("div");
- expect(fill?.className).toContain("bg-success");
- });
+ it('applies success variant', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ const fill = progress.querySelector('div');
+ expect(fill?.className).toContain('bg-success');
+ });
- it("applies warning variant", () => {
- render(
- ,
- );
- const progress = screen.getByTestId("progress");
- const fill = progress.querySelector("div");
- expect(fill?.className).toContain("bg-warning");
- });
+ it('applies warning variant', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ const fill = progress.querySelector('div');
+ expect(fill?.className).toContain('bg-warning');
+ });
- it("applies danger variant", () => {
- render(
- ,
- );
- const progress = screen.getByTestId("progress");
- const fill = progress.querySelector("div");
- expect(fill?.className).toContain("bg-error");
- });
+ it('applies danger variant', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ const fill = progress.querySelector('div');
+ expect(fill?.className).toContain('bg-error');
+ });
- it("uses primary variant by default", () => {
- render();
- const progress = screen.getByTestId("progress");
- const fill = progress.querySelector("div");
- expect(fill?.className).toContain("bg-primary");
- });
- });
+ it('uses primary variant by default', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ const fill = progress.querySelector('div');
+ expect(fill?.className).toContain('bg-primary');
+ });
+ });
- describe("Label Display", () => {
- it("does not show label by default", () => {
- const { container } = render();
- expect(container.textContent).toBe("");
- });
+ describe('Label Display', () => {
+ it('does not show label by default', () => {
+ const { container } = render();
+ expect(container.textContent).toBe('');
+ });
- it("shows label when showLabel is true", () => {
- render();
- expect(screen.getByText("50%")).toBeInTheDocument();
- });
+ it('shows label when showLabel is true', () => {
+ render();
+ expect(screen.getByText('50%')).toBeInTheDocument();
+ });
- it("rounds label to nearest integer", () => {
- render();
- expect(screen.getByText("34%")).toBeInTheDocument();
- });
+ it('rounds label to nearest integer', () => {
+ render();
+ expect(screen.getByText('34%')).toBeInTheDocument();
+ });
- it("shows 0% label for 0 value", () => {
- render();
- expect(screen.getByText("0%")).toBeInTheDocument();
- });
+ it('shows 0% label for 0 value', () => {
+ render();
+ expect(screen.getByText('0%')).toBeInTheDocument();
+ });
- it("shows 100% label for 100 value", () => {
- render();
- expect(screen.getByText("100%")).toBeInTheDocument();
- });
+ it('shows 100% label for 100 value', () => {
+ render();
+ expect(screen.getByText('100%')).toBeInTheDocument();
+ });
- it("label has correct styling classes", () => {
- render();
- const label = screen.getByText("50%");
- expect(label.className).toContain("text-sm");
- expect(label.className).toContain("text-foreground-secondary");
- expect(label.className).toContain("tabular-nums");
- });
- });
+ it('label has correct styling classes', () => {
+ render();
+ const label = screen.getByText('50%');
+ expect(label.className).toContain('text-sm');
+ expect(label.className).toContain('text-foreground-secondary');
+ expect(label.className).toContain('tabular-nums');
+ });
+ });
- describe("HTML Attributes", () => {
- it("passes through id attribute", () => {
- render(
- ,
- );
- const progress = screen.getByTestId("progress");
- expect(progress).toHaveAttribute("id", "progress-id");
- });
+ describe('HTML Attributes', () => {
+ it('passes through id attribute', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress).toHaveAttribute('id', 'progress-id');
+ });
- it("passes through data attributes", () => {
- render(
- ,
- );
- const progress = screen.getByTestId("progress");
- expect(progress).toHaveAttribute("data-custom", "test");
- });
- });
+ it('passes through data attributes', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress).toHaveAttribute('data-custom', 'test');
+ });
+ });
- describe("Custom className", () => {
- it("merges custom className with variant classes", () => {
- render(
- ,
- );
- const progress = screen.getByTestId("progress");
- expect(progress.className).toContain("custom-class");
- expect(progress.className).toContain("bg-surface");
- expect(progress.className).toContain("rounded");
- });
- });
+ describe('Custom className', () => {
+ it('merges custom className with variant classes', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress.className).toContain('custom-class');
+ expect(progress.className).toContain('bg-surface');
+ expect(progress.className).toContain('rounded');
+ });
+ });
- describe("Ref Forwarding", () => {
- it("forwards ref to progress bar element", () => {
- let ref: HTMLDivElement | null = null;
- render(
- {
- ref = el;
- }}
- />,
- );
- expect(ref).toBeInstanceOf(HTMLDivElement);
- expect(ref).toHaveAttribute("role", "progressbar");
- });
- });
+ describe('Ref Forwarding', () => {
+ it('forwards ref to progress bar element', () => {
+ let ref: HTMLDivElement | null = null;
+ render(
+ {
+ ref = el;
+ }}
+ />
+ );
+ expect(ref).toBeInstanceOf(HTMLDivElement);
+ expect(ref).toHaveAttribute('role', 'progressbar');
+ });
+ });
- describe("Base Classes", () => {
- it("always includes track base classes", () => {
- render();
- const progress = screen.getByTestId("progress");
- expect(progress.className).toContain("relative");
- expect(progress.className).toContain("overflow-hidden");
- expect(progress.className).toContain("bg-surface");
- expect(progress.className).toContain("rounded");
- });
+ describe('Base Classes', () => {
+ it('always includes track base classes', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress.className).toContain('relative');
+ expect(progress.className).toContain('overflow-hidden');
+ expect(progress.className).toContain('bg-surface');
+ expect(progress.className).toContain('rounded');
+ });
- it("fill includes base classes", () => {
- render();
- const progress = screen.getByTestId("progress");
- const fill = progress.querySelector("div");
- expect(fill?.className).toContain("h-full");
- expect(fill?.className).toContain("transition-all");
- expect(fill?.className).toContain("rounded");
- });
- });
+ it('fill includes base classes', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ const fill = progress.querySelector('div');
+ expect(fill?.className).toContain('h-full');
+ expect(fill?.className).toContain('transition-all');
+ expect(fill?.className).toContain('rounded');
+ });
+ });
- describe("Compound Scenarios", () => {
- it("works correctly with large size and success variant", () => {
- render(
- ,
- );
- const progress = screen.getByTestId("progress");
- expect(progress.className).toContain("h-3");
- const fill = progress.querySelector("div");
- expect(fill?.className).toContain("bg-success");
- expect(fill).toHaveStyle({ width: "80%" });
- });
+ describe('Compound Scenarios', () => {
+ it('works correctly with large size and success variant', () => {
+ render();
+ const progress = screen.getByTestId('progress');
+ expect(progress.className).toContain('h-3');
+ const fill = progress.querySelector('div');
+ expect(fill?.className).toContain('bg-success');
+ expect(fill).toHaveStyle({ width: '80%' });
+ });
- it("works correctly with label, custom className, and warning variant", () => {
- render(
- ,
- );
- const wrapper = screen.getByTestId("progress");
- // Custom className is applied to the track, which is the first child
- const track = wrapper.querySelector('[role="progressbar"]');
- expect(track?.className).toContain("my-custom-class");
- const fill = track?.querySelector("div");
- expect(fill?.className).toContain("bg-warning");
- expect(screen.getByText("45%")).toBeInTheDocument();
- });
- });
+ it('works correctly with label, custom className, and warning variant', () => {
+ render(
+
+ );
+ const wrapper = screen.getByTestId('progress');
+ // Custom className is applied to the track, which is the first child
+ const track = wrapper.querySelector('[role="progressbar"]');
+ expect(track?.className).toContain('my-custom-class');
+ const fill = track?.querySelector('div');
+ expect(fill?.className).toContain('bg-warning');
+ expect(screen.getByText('45%')).toBeInTheDocument();
+ });
+ });
});
diff --git a/packages/ui/src/ProgressBar/ProgressBar.tsx b/packages/ui/src/ProgressBar/ProgressBar.tsx
index 2cb77bc..9817d7f 100644
--- a/packages/ui/src/ProgressBar/ProgressBar.tsx
+++ b/packages/ui/src/ProgressBar/ProgressBar.tsx
@@ -1,7 +1,7 @@
-import { cn } from "@tpmjs/utils/cn";
-import { forwardRef } from "react";
-import type { ProgressBarProps } from "./types";
-import { progressBarFillVariants, progressBarTrackVariants } from "./variants";
+import { cn } from '@tpmjs/utils/cn';
+import { forwardRef } from 'react';
+import type { ProgressBarProps } from './types';
+import { progressBarFillVariants, progressBarTrackVariants } from './variants';
/**
* ProgressBar component
@@ -26,79 +26,69 @@ import { progressBarFillVariants, progressBarTrackVariants } from "./variants";
* ```
*/
export const ProgressBar = forwardRef(
- (
- {
- className,
- value,
- size = "md",
- variant = "primary",
- showLabel = false,
- ...props
- },
- ref,
- ) => {
- // Clamp value between 0 and 100
- const clampedValue = Math.min(Math.max(value, 0), 100);
+ ({ className, value, size = 'md', variant = 'primary', showLabel = false, ...props }, ref) => {
+ // Clamp value between 0 and 100
+ const clampedValue = Math.min(Math.max(value, 0), 100);
- if (!showLabel) {
- return (
-
- );
- }
+ if (!showLabel) {
+ return (
+
+ );
+ }
- return (
-
-
-
- {Math.round(clampedValue)}%
-
-
- );
- },
+ return (
+
+
+
+ {Math.round(clampedValue)}%
+
+
+ );
+ }
);
-ProgressBar.displayName = "ProgressBar";
+ProgressBar.displayName = 'ProgressBar';
diff --git a/packages/ui/src/ProgressBar/tokens.ts b/packages/ui/src/ProgressBar/tokens.ts
index f14fa48..6ac7f9a 100644
--- a/packages/ui/src/ProgressBar/tokens.ts
+++ b/packages/ui/src/ProgressBar/tokens.ts
@@ -1,17 +1,17 @@
-import { spacing } from "../tokens";
+import { spacing } from '../tokens';
/**
* ProgressBar component design tokens
* Maps global tokens to progress bar-specific semantics
*/
export const progressBarTokens = {
- /** Height for each size */
- height: {
- sm: spacing[1], // 0.25rem (4px)
- md: spacing[2], // 0.5rem (8px)
- lg: spacing[3], // 0.75rem (12px)
- },
+ /** Height for each size */
+ height: {
+ sm: spacing[1], // 0.25rem (4px)
+ md: spacing[2], // 0.5rem (8px)
+ lg: spacing[3], // 0.75rem (12px)
+ },
- /** Border radius */
- radius: spacing[1], // 0.25rem (4px)
+ /** Border radius */
+ radius: spacing[1], // 0.25rem (4px)
} as const;
diff --git a/packages/ui/src/ProgressBar/types.ts b/packages/ui/src/ProgressBar/types.ts
index 70873ec..58f5b6c 100644
--- a/packages/ui/src/ProgressBar/types.ts
+++ b/packages/ui/src/ProgressBar/types.ts
@@ -1,32 +1,31 @@
-import type { HTMLAttributes } from "react";
+import type { HTMLAttributes } from 'react';
/**
* ProgressBar component props
*/
-export interface ProgressBarProps
- extends Omit, "children"> {
- /**
- * Progress value (0-100)
- */
- value: number;
+export interface ProgressBarProps extends Omit, 'children'> {
+ /**
+ * Progress value (0-100)
+ */
+ value: number;
- /**
- * Size variant
- * @default 'md'
- */
- size?: "sm" | "md" | "lg";
+ /**
+ * Size variant
+ * @default 'md'
+ */
+ size?: 'sm' | 'md' | 'lg';
- /**
- * Color variant
- * @default 'primary'
- */
- variant?: "primary" | "success" | "warning" | "danger";
+ /**
+ * Color variant
+ * @default 'primary'
+ */
+ variant?: 'primary' | 'success' | 'warning' | 'danger';
- /**
- * Whether to show percentage label
- * @default false
- */
- showLabel?: boolean;
+ /**
+ * Whether to show percentage label
+ * @default false
+ */
+ showLabel?: boolean;
}
/**
diff --git a/packages/ui/src/ProgressBar/variants.ts b/packages/ui/src/ProgressBar/variants.ts
index e2a5f16..68b957a 100644
--- a/packages/ui/src/ProgressBar/variants.ts
+++ b/packages/ui/src/ProgressBar/variants.ts
@@ -1,58 +1,58 @@
-import { createVariants } from "../system/variants";
+import { createVariants } from '../system/variants';
/**
* ProgressBar track variant definitions
*/
export const progressBarTrackVariants = createVariants({
- base: [
- // Layout
- "relative overflow-hidden",
- // Background
- "bg-surface",
- // Border
- "rounded",
- ].join(" "),
+ base: [
+ // Layout
+ 'relative overflow-hidden',
+ // Background
+ 'bg-surface',
+ // Border
+ 'rounded',
+ ].join(' '),
- variants: {
- size: {
- sm: "h-1", // 4px
- md: "h-2", // 8px
- lg: "h-3", // 12px
- },
- },
+ variants: {
+ size: {
+ sm: 'h-1', // 4px
+ md: 'h-2', // 8px
+ lg: 'h-3', // 12px
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- size: "md",
- },
+ defaultVariants: {
+ size: 'md',
+ },
});
/**
* ProgressBar fill variant definitions
*/
export const progressBarFillVariants = createVariants({
- base: [
- // Layout
- "h-full",
- // Transition
- "transition-all duration-300 ease-out",
- // Border
- "rounded",
- ].join(" "),
+ base: [
+ // Layout
+ 'h-full',
+ // Transition
+ 'transition-all duration-300 ease-out',
+ // Border
+ 'rounded',
+ ].join(' '),
- variants: {
- variant: {
- primary: "bg-primary",
- success: "bg-success",
- warning: "bg-warning",
- danger: "bg-error",
- },
- },
+ variants: {
+ variant: {
+ primary: 'bg-primary',
+ success: 'bg-success',
+ warning: 'bg-warning',
+ danger: 'bg-error',
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- variant: "primary",
- },
+ defaultVariants: {
+ variant: 'primary',
+ },
});
diff --git a/packages/ui/src/Section/Section.test.tsx b/packages/ui/src/Section/Section.test.tsx
index 3990e4e..1e661d7 100644
--- a/packages/ui/src/Section/Section.test.tsx
+++ b/packages/ui/src/Section/Section.test.tsx
@@ -1,345 +1,339 @@
-import { render, screen } from "@testing-library/react";
-import { describe, expect, it } from "vitest";
-import { Section } from "./Section";
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { Section } from './Section';
-describe("Section", () => {
- describe("Rendering", () => {
- it("renders a section element by default", () => {
- render();
- const section = screen.getByTestId("section");
- expect(section).toBeInTheDocument();
- expect(section.tagName).toBe("SECTION");
- });
+describe('Section', () => {
+ describe('Rendering', () => {
+ it('renders a section element by default', () => {
+ render();
+ const section = screen.getByTestId('section');
+ expect(section).toBeInTheDocument();
+ expect(section.tagName).toBe('SECTION');
+ });
- it("renders content", () => {
- render();
- expect(screen.getByText("Test content")).toBeInTheDocument();
- });
+ it('renders content', () => {
+ render();
+ expect(screen.getByText('Test content')).toBeInTheDocument();
+ });
- it("renders as different semantic elements", () => {
- const { rerender } = render(
- ,
- );
- expect(screen.getByTestId("element").tagName).toBe("ARTICLE");
+ it('renders as different semantic elements', () => {
+ const { rerender } = render(
+
+ );
+ expect(screen.getByTestId('element').tagName).toBe('ARTICLE');
- rerender(
- ,
- );
- expect(screen.getByTestId("element").tagName).toBe("ASIDE");
+ rerender(
+
+ );
+ expect(screen.getByTestId('element').tagName).toBe('ASIDE');
- rerender(
- ,
- );
- expect(screen.getByTestId("element").tagName).toBe("NAV");
+ rerender(
+
+ );
+ expect(screen.getByTestId('element').tagName).toBe('NAV');
- rerender(
- ,
- );
- expect(screen.getByTestId("element").tagName).toBe("DIV");
- });
- });
+ rerender(
+
+ );
+ expect(screen.getByTestId('element').tagName).toBe('DIV');
+ });
+ });
- describe("Spacing Variants", () => {
- it("applies no spacing", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("py-0");
- });
+ describe('Spacing Variants', () => {
+ it('applies no spacing', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('py-0');
+ });
- it("applies small spacing", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("py-8");
- });
+ it('applies small spacing', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('py-8');
+ });
- it("applies medium spacing", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("py-16");
- });
+ it('applies medium spacing', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('py-16');
+ });
- it("applies large spacing", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("py-24");
- });
+ it('applies large spacing', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('py-24');
+ });
- it("applies extra large spacing", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("py-32");
- });
+ it('applies extra large spacing', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('py-32');
+ });
- it("uses md spacing by default", () => {
- render();
- const section = screen.getByTestId("section");
- expect(section.className).toContain("py-16");
- });
- });
+ it('uses md spacing by default', () => {
+ render();
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('py-16');
+ });
+ });
- describe("Background Variants", () => {
- it("applies default background", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("bg-background");
- });
+ describe('Background Variants', () => {
+ it('applies default background', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('bg-background');
+ });
- it("applies surface background", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("bg-surface");
- });
+ it('applies surface background', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('bg-surface');
+ });
- it("applies dotted-grid background", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("dotted-grid-background");
- });
+ it('applies dotted-grid background', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('dotted-grid-background');
+ });
- it("applies blueprint background", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("blueprint-background");
- });
+ it('applies blueprint background', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('blueprint-background');
+ });
- it("applies grid background", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("grid-background");
- });
- });
+ it('applies grid background', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('grid-background');
+ });
+ });
- describe("Container Variants", () => {
- it("applies no container by default", () => {
- render();
- const section = screen.getByTestId("section");
- expect(section.className).not.toContain("max-w");
- });
+ describe('Container Variants', () => {
+ it('applies no container by default', () => {
+ render();
+ const section = screen.getByTestId('section');
+ expect(section.className).not.toContain('max-w');
+ });
- it("applies small container", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("max-w-3xl");
- });
+ it('applies small container', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('max-w-3xl');
+ });
- it("applies medium container", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("max-w-5xl");
- });
+ it('applies medium container', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('max-w-5xl');
+ });
- it("applies large container", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("max-w-7xl");
- });
+ it('applies large container', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('max-w-7xl');
+ });
- it("applies extra large container", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("max-w-[90rem]");
- });
+ it('applies extra large container', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('max-w-[90rem]');
+ });
- it("applies full width container", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("max-w-full");
- });
- });
+ it('applies full width container', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('max-w-full');
+ });
+ });
- describe("Centered Option", () => {
- it("does not center by default", () => {
- render();
- const section = screen.getByTestId("section");
- expect(section.className).not.toContain("mx-auto");
- });
+ describe('Centered Option', () => {
+ it('does not center by default', () => {
+ render();
+ const section = screen.getByTestId('section');
+ expect(section.className).not.toContain('mx-auto');
+ });
- it("centers content when centered is true", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("mx-auto");
- });
- });
+ it('centers content when centered is true', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('mx-auto');
+ });
+ });
- describe("HTML Attributes", () => {
- it("passes through id attribute", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section).toHaveAttribute("id", "section-id");
- });
+ describe('HTML Attributes', () => {
+ it('passes through id attribute', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section).toHaveAttribute('id', 'section-id');
+ });
- it("passes through data attributes", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section).toHaveAttribute("data-custom", "test");
- });
+ it('passes through data attributes', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section).toHaveAttribute('data-custom', 'test');
+ });
- it("passes through aria attributes", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section).toHaveAttribute("aria-label", "Main section");
- });
- });
+ it('passes through aria attributes', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section).toHaveAttribute('aria-label', 'Main section');
+ });
+ });
- describe("Custom className", () => {
- it("merges custom className with variant classes", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("custom-class");
- expect(section.className).toContain("relative");
- expect(section.className).toContain("w-full");
- });
- });
+ describe('Custom className', () => {
+ it('merges custom className with variant classes', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('custom-class');
+ expect(section.className).toContain('relative');
+ expect(section.className).toContain('w-full');
+ });
+ });
- describe("Ref Forwarding", () => {
- it("forwards ref to section element", () => {
- let ref: HTMLElement | null = null;
- render(
- {
- ref = el;
- }}
- >
- Content
- ,
- );
- expect(ref).toBeInstanceOf(HTMLElement);
- expect(ref!.tagName).toBe("SECTION");
- });
- });
+ describe('Ref Forwarding', () => {
+ it('forwards ref to section element', () => {
+ let ref: HTMLElement | null = null;
+ render(
+ {
+ ref = el;
+ }}
+ >
+ Content
+
+ );
+ expect(ref).toBeInstanceOf(HTMLElement);
+ expect(ref!.tagName).toBe('SECTION');
+ });
+ });
- describe("Base Classes", () => {
- it("always includes base classes", () => {
- render();
- const section = screen.getByTestId("section");
- expect(section.className).toContain("relative");
- expect(section.className).toContain("w-full");
- });
- });
+ describe('Base Classes', () => {
+ it('always includes base classes', () => {
+ render();
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('relative');
+ expect(section.className).toContain('w-full');
+ });
+ });
- describe("Compound Scenarios", () => {
- it("works with multiple variants combined", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("py-24");
- expect(section.className).toContain("blueprint-background");
- expect(section.className).toContain("max-w-[90rem]");
- expect(section.className).toContain("mx-auto");
- });
+ describe('Compound Scenarios', () => {
+ it('works with multiple variants combined', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('py-24');
+ expect(section.className).toContain('blueprint-background');
+ expect(section.className).toContain('max-w-[90rem]');
+ expect(section.className).toContain('mx-auto');
+ });
- it("works with custom className and variants", () => {
- render(
- ,
- );
- const section = screen.getByTestId("section");
- expect(section.className).toContain("py-16");
- expect(section.className).toContain("bg-surface");
- expect(section.className).toContain("border-t");
- });
- });
+ it('works with custom className and variants', () => {
+ render(
+
+ );
+ const section = screen.getByTestId('section');
+ expect(section.className).toContain('py-16');
+ expect(section.className).toContain('bg-surface');
+ expect(section.className).toContain('border-t');
+ });
+ });
});
diff --git a/packages/ui/src/Section/Section.tsx b/packages/ui/src/Section/Section.tsx
index 0df743d..cdb1a48 100644
--- a/packages/ui/src/Section/Section.tsx
+++ b/packages/ui/src/Section/Section.tsx
@@ -1,7 +1,7 @@
-import { cn } from "@tpmjs/utils/cn";
-import { forwardRef } from "react";
-import type { SectionProps } from "./types";
-import { sectionVariants } from "./variants";
+import { cn } from '@tpmjs/utils/cn';
+import { forwardRef } from 'react';
+import type { SectionProps } from './types';
+import { sectionVariants } from './variants';
/**
* Section component
@@ -28,41 +28,40 @@ import { sectionVariants } from "./variants";
* ```
*/
export const Section = forwardRef(
- (
- {
- className,
- as = "section",
- spacing = "md",
- background = "default",
- container = "none",
- centered = false,
- children,
- ...props
- },
- ref,
- ) => {
- const Component = as;
+ (
+ {
+ className,
+ as = 'section',
+ spacing = 'md',
+ background = 'default',
+ container = 'none',
+ centered = false,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ const Component = as;
- return (
-
- {children}
-
- );
- },
+ return (
+
+ {children}
+
+ );
+ }
);
-Section.displayName = "Section";
+Section.displayName = 'Section';
diff --git a/packages/ui/src/Section/types.ts b/packages/ui/src/Section/types.ts
index 954c8ff..a36f7e8 100644
--- a/packages/ui/src/Section/types.ts
+++ b/packages/ui/src/Section/types.ts
@@ -1,38 +1,38 @@
-import type { HTMLAttributes } from "react";
+import type { HTMLAttributes } from 'react';
/**
* Section component props
*/
export interface SectionProps extends HTMLAttributes {
- /**
- * Semantic section element to render
- * @default 'section'
- */
- as?: "section" | "article" | "aside" | "nav" | "div";
+ /**
+ * Semantic section element to render
+ * @default 'section'
+ */
+ as?: 'section' | 'article' | 'aside' | 'nav' | 'div';
- /**
- * Vertical spacing size
- * @default 'md'
- */
- spacing?: "none" | "sm" | "md" | "lg" | "xl";
+ /**
+ * Vertical spacing size
+ * @default 'md'
+ */
+ spacing?: 'none' | 'sm' | 'md' | 'lg' | 'xl';
- /**
- * Background variant
- * @default 'default'
- */
- background?: "default" | "surface" | "dotted-grid" | "blueprint" | "grid";
+ /**
+ * Background variant
+ * @default 'default'
+ */
+ background?: 'default' | 'surface' | 'dotted-grid' | 'blueprint' | 'grid';
- /**
- * Maximum width container
- * @default 'none'
- */
- container?: "none" | "sm" | "md" | "lg" | "xl" | "full";
+ /**
+ * Maximum width container
+ * @default 'none'
+ */
+ container?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
- /**
- * Center content horizontally
- * @default false
- */
- centered?: boolean;
+ /**
+ * Center content horizontally
+ * @default false
+ */
+ centered?: boolean;
}
/**
diff --git a/packages/ui/src/Section/variants.ts b/packages/ui/src/Section/variants.ts
index f7a99e1..44db7d7 100644
--- a/packages/ui/src/Section/variants.ts
+++ b/packages/ui/src/Section/variants.ts
@@ -1,49 +1,49 @@
-import { createVariants } from "../system/variants";
+import { createVariants } from '../system/variants';
/**
* Section variant definitions
*/
export const sectionVariants = createVariants({
- base: ["relative w-full"].join(" "),
+ base: ['relative w-full'].join(' '),
- variants: {
- spacing: {
- none: "py-0",
- sm: "py-8",
- md: "py-16",
- lg: "py-24",
- xl: "py-32",
- },
+ variants: {
+ spacing: {
+ none: 'py-0',
+ sm: 'py-8',
+ md: 'py-16',
+ lg: 'py-24',
+ xl: 'py-32',
+ },
- background: {
- default: "bg-background",
- surface: "bg-surface",
- "dotted-grid": "dotted-grid-background",
- blueprint: "blueprint-background",
- grid: "grid-background",
- },
+ background: {
+ default: 'bg-background',
+ surface: 'bg-surface',
+ 'dotted-grid': 'dotted-grid-background',
+ blueprint: 'blueprint-background',
+ grid: 'grid-background',
+ },
- container: {
- none: "",
- sm: "max-w-3xl",
- md: "max-w-5xl",
- lg: "max-w-7xl",
- xl: "max-w-[90rem]",
- full: "max-w-full",
- },
+ container: {
+ none: '',
+ sm: 'max-w-3xl',
+ md: 'max-w-5xl',
+ lg: 'max-w-7xl',
+ xl: 'max-w-[90rem]',
+ full: 'max-w-full',
+ },
- centered: {
- true: "mx-auto",
- false: "",
- },
- },
+ centered: {
+ true: 'mx-auto',
+ false: '',
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- spacing: "md",
- background: "default",
- container: "none",
- centered: "false",
- },
+ defaultVariants: {
+ spacing: 'md',
+ background: 'default',
+ container: 'none',
+ centered: 'false',
+ },
});
diff --git a/packages/ui/src/Tabs/Tabs.test.tsx b/packages/ui/src/Tabs/Tabs.test.tsx
index 49cb311..02790ac 100644
--- a/packages/ui/src/Tabs/Tabs.test.tsx
+++ b/packages/ui/src/Tabs/Tabs.test.tsx
@@ -1,456 +1,343 @@
-import { render, screen } from "@testing-library/react";
-import { describe, expect, it, vi } from "vitest";
-import { Tabs } from "./Tabs";
-import type { Tab } from "./types";
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it, vi } from 'vitest';
+import { Tabs } from './Tabs';
+import type { Tab } from './types';
const mockTabs: Tab[] = [
- { id: "all", label: "All Tools", count: 1234 },
- { id: "featured", label: "Featured", count: 42 },
- { id: "recent", label: "Recent" },
+ { id: 'all', label: 'All Tools', count: 1234 },
+ { id: 'featured', label: 'Featured', count: 42 },
+ { id: 'recent', label: 'Recent' },
];
-describe("Tabs", () => {
- describe("Rendering", () => {
- it("renders tabs container", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const tabs = screen.getByTestId("tabs");
- expect(tabs).toBeInTheDocument();
- expect(tabs.tagName).toBe("DIV");
- });
+describe('Tabs', () => {
+ describe('Rendering', () => {
+ it('renders tabs container', () => {
+ const handleChange = vi.fn();
+ render(
+
+ );
+ const tabs = screen.getByTestId('tabs');
+ expect(tabs).toBeInTheDocument();
+ expect(tabs.tagName).toBe('DIV');
+ });
- it("has tablist role", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const tabs = screen.getByTestId("tabs");
- expect(tabs).toHaveAttribute("role", "tablist");
- });
+ it('has tablist role', () => {
+ const handleChange = vi.fn();
+ render(
+
+ );
+ const tabs = screen.getByTestId('tabs');
+ expect(tabs).toHaveAttribute('role', 'tablist');
+ });
- it("renders all tabs", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- expect(screen.getByTestId("tab-all")).toBeInTheDocument();
- expect(screen.getByTestId("tab-featured")).toBeInTheDocument();
- expect(screen.getByTestId("tab-recent")).toBeInTheDocument();
- });
+ it('renders all tabs', () => {
+ const handleChange = vi.fn();
+ render();
+ expect(screen.getByTestId('tab-all')).toBeInTheDocument();
+ expect(screen.getByTestId('tab-featured')).toBeInTheDocument();
+ expect(screen.getByTestId('tab-recent')).toBeInTheDocument();
+ });
- it("renders tab labels", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- expect(screen.getByText("All Tools")).toBeInTheDocument();
- expect(screen.getByText("Featured")).toBeInTheDocument();
- expect(screen.getByText("Recent")).toBeInTheDocument();
- });
+ it('renders tab labels', () => {
+ const handleChange = vi.fn();
+ render();
+ expect(screen.getByText('All Tools')).toBeInTheDocument();
+ expect(screen.getByText('Featured')).toBeInTheDocument();
+ expect(screen.getByText('Recent')).toBeInTheDocument();
+ });
- it("renders tabs as buttons", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const tab = screen.getByTestId("tab-all");
- expect(tab.tagName).toBe("BUTTON");
- expect(tab).toHaveAttribute("type", "button");
- });
- });
+ it('renders tabs as buttons', () => {
+ const handleChange = vi.fn();
+ render();
+ const tab = screen.getByTestId('tab-all');
+ expect(tab.tagName).toBe('BUTTON');
+ expect(tab).toHaveAttribute('type', 'button');
+ });
+ });
- describe("Active State", () => {
- it("marks active tab with aria-selected", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- expect(screen.getByTestId("tab-featured")).toHaveAttribute(
- "aria-selected",
- "true",
- );
- expect(screen.getByTestId("tab-all")).toHaveAttribute(
- "aria-selected",
- "false",
- );
- expect(screen.getByTestId("tab-recent")).toHaveAttribute(
- "aria-selected",
- "false",
- );
- });
+ describe('Active State', () => {
+ it('marks active tab with aria-selected', () => {
+ const handleChange = vi.fn();
+ render();
+ expect(screen.getByTestId('tab-featured')).toHaveAttribute('aria-selected', 'true');
+ expect(screen.getByTestId('tab-all')).toHaveAttribute('aria-selected', 'false');
+ expect(screen.getByTestId('tab-recent')).toHaveAttribute('aria-selected', 'false');
+ });
- it("applies active styling to active tab", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const activeTab = screen.getByTestId("tab-all");
- expect(activeTab.className).toContain("text-foreground");
- expect(activeTab.className).toContain("border-primary");
- });
+ it('applies active styling to active tab', () => {
+ const handleChange = vi.fn();
+ render();
+ const activeTab = screen.getByTestId('tab-all');
+ expect(activeTab.className).toContain('text-foreground');
+ expect(activeTab.className).toContain('border-primary');
+ });
- it("applies inactive styling to inactive tabs", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const inactiveTab = screen.getByTestId("tab-featured");
- expect(inactiveTab.className).toContain("text-foreground-secondary");
- expect(inactiveTab.className).toContain("border-transparent");
- });
- });
+ it('applies inactive styling to inactive tabs', () => {
+ const handleChange = vi.fn();
+ render();
+ const inactiveTab = screen.getByTestId('tab-featured');
+ expect(inactiveTab.className).toContain('text-foreground-secondary');
+ expect(inactiveTab.className).toContain('border-transparent');
+ });
+ });
- describe("Click Handling", () => {
- it("calls onTabChange when tab is clicked", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- screen.getByTestId("tab-featured").click();
- expect(handleChange).toHaveBeenCalledWith("featured");
- });
+ describe('Click Handling', () => {
+ it('calls onTabChange when tab is clicked', () => {
+ const handleChange = vi.fn();
+ render();
+ screen.getByTestId('tab-featured').click();
+ expect(handleChange).toHaveBeenCalledWith('featured');
+ });
- it("calls onTabChange with correct tab ID", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- screen.getByTestId("tab-recent").click();
- expect(handleChange).toHaveBeenCalledWith("recent");
- expect(handleChange).toHaveBeenCalledTimes(1);
- });
+ it('calls onTabChange with correct tab ID', () => {
+ const handleChange = vi.fn();
+ render();
+ screen.getByTestId('tab-recent').click();
+ expect(handleChange).toHaveBeenCalledWith('recent');
+ expect(handleChange).toHaveBeenCalledTimes(1);
+ });
- it("allows clicking already active tab", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- screen.getByTestId("tab-all").click();
- expect(handleChange).toHaveBeenCalledWith("all");
- });
- });
+ it('allows clicking already active tab', () => {
+ const handleChange = vi.fn();
+ render();
+ screen.getByTestId('tab-all').click();
+ expect(handleChange).toHaveBeenCalledWith('all');
+ });
+ });
- describe("Count Badges", () => {
- it("renders count badge when count is provided", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- expect(screen.getByText("1234")).toBeInTheDocument();
- expect(screen.getByText("42")).toBeInTheDocument();
- });
+ describe('Count Badges', () => {
+ it('renders count badge when count is provided', () => {
+ const handleChange = vi.fn();
+ render();
+ expect(screen.getByText('1234')).toBeInTheDocument();
+ expect(screen.getByText('42')).toBeInTheDocument();
+ });
- it("does not render count badge when count is undefined", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const recentTab = screen.getByTestId("tab-recent");
- const badge = recentTab.querySelector("span[aria-label]");
- expect(badge).not.toBeInTheDocument();
- });
+ it('does not render count badge when count is undefined', () => {
+ const handleChange = vi.fn();
+ render();
+ const recentTab = screen.getByTestId('tab-recent');
+ const badge = recentTab.querySelector('span[aria-label]');
+ expect(badge).not.toBeInTheDocument();
+ });
- it("count badge has aria-label", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const badge = screen.getByText("1234");
- expect(badge).toHaveAttribute("aria-label", "1234 items");
- });
+ it('count badge has aria-label', () => {
+ const handleChange = vi.fn();
+ render();
+ const badge = screen.getByText('1234');
+ expect(badge).toHaveAttribute('aria-label', '1234 items');
+ });
- it("applies active styling to count badge on active tab", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const activeBadge = screen.getByText("1234");
- expect(activeBadge.className).toContain("text-foreground");
- });
+ it('applies active styling to count badge on active tab', () => {
+ const handleChange = vi.fn();
+ render();
+ const activeBadge = screen.getByText('1234');
+ expect(activeBadge.className).toContain('text-foreground');
+ });
- it("applies inactive styling to count badge on inactive tab", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const inactiveBadge = screen.getByText("42");
- expect(inactiveBadge.className).toContain("text-foreground-tertiary");
- });
+ it('applies inactive styling to count badge on inactive tab', () => {
+ const handleChange = vi.fn();
+ render();
+ const inactiveBadge = screen.getByText('42');
+ expect(inactiveBadge.className).toContain('text-foreground-tertiary');
+ });
- it("handles zero count", () => {
- const handleChange = vi.fn();
- const tabsWithZero: Tab[] = [{ id: "empty", label: "Empty", count: 0 }];
- render(
- ,
- );
- expect(screen.getByText("0")).toBeInTheDocument();
- });
- });
+ it('handles zero count', () => {
+ const handleChange = vi.fn();
+ const tabsWithZero: Tab[] = [{ id: 'empty', label: 'Empty', count: 0 }];
+ render();
+ expect(screen.getByText('0')).toBeInTheDocument();
+ });
+ });
- describe("Size Variants", () => {
- it("applies small size", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const tab = screen.getByTestId("tab-all");
- expect(tab.className).toContain("text-sm");
- expect(tab.className).toContain("px-3");
- expect(tab.className).toContain("py-2");
- });
+ describe('Size Variants', () => {
+ it('applies small size', () => {
+ const handleChange = vi.fn();
+ render();
+ const tab = screen.getByTestId('tab-all');
+ expect(tab.className).toContain('text-sm');
+ expect(tab.className).toContain('px-3');
+ expect(tab.className).toContain('py-2');
+ });
- it("applies medium size", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const tab = screen.getByTestId("tab-all");
- expect(tab.className).toContain("text-base");
- expect(tab.className).toContain("px-4");
- expect(tab.className).toContain("py-3");
- });
+ it('applies medium size', () => {
+ const handleChange = vi.fn();
+ render();
+ const tab = screen.getByTestId('tab-all');
+ expect(tab.className).toContain('text-base');
+ expect(tab.className).toContain('px-4');
+ expect(tab.className).toContain('py-3');
+ });
- it("applies large size", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const tab = screen.getByTestId("tab-all");
- expect(tab.className).toContain("text-lg");
- expect(tab.className).toContain("px-6");
- expect(tab.className).toContain("py-4");
- });
+ it('applies large size', () => {
+ const handleChange = vi.fn();
+ render();
+ const tab = screen.getByTestId('tab-all');
+ expect(tab.className).toContain('text-lg');
+ expect(tab.className).toContain('px-6');
+ expect(tab.className).toContain('py-4');
+ });
- it("uses md size by default", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const tab = screen.getByTestId("tab-all");
- expect(tab.className).toContain("text-base");
- expect(tab.className).toContain("px-4");
- expect(tab.className).toContain("py-3");
- });
- });
+ it('uses md size by default', () => {
+ const handleChange = vi.fn();
+ render();
+ const tab = screen.getByTestId('tab-all');
+ expect(tab.className).toContain('text-base');
+ expect(tab.className).toContain('px-4');
+ expect(tab.className).toContain('py-3');
+ });
+ });
- describe("Accessibility", () => {
- it('tabs have role="tab"', () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const tab = screen.getByTestId("tab-all");
- expect(tab).toHaveAttribute("role", "tab");
- });
+ describe('Accessibility', () => {
+ it('tabs have role="tab"', () => {
+ const handleChange = vi.fn();
+ render();
+ const tab = screen.getByTestId('tab-all');
+ expect(tab).toHaveAttribute('role', 'tab');
+ });
- it("tabs have unique IDs", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- expect(screen.getByTestId("tab-all")).toHaveAttribute("id", "tab-all");
- expect(screen.getByTestId("tab-featured")).toHaveAttribute(
- "id",
- "tab-featured",
- );
- expect(screen.getByTestId("tab-recent")).toHaveAttribute(
- "id",
- "tab-recent",
- );
- });
+ it('tabs have unique IDs', () => {
+ const handleChange = vi.fn();
+ render();
+ expect(screen.getByTestId('tab-all')).toHaveAttribute('id', 'tab-all');
+ expect(screen.getByTestId('tab-featured')).toHaveAttribute('id', 'tab-featured');
+ expect(screen.getByTestId('tab-recent')).toHaveAttribute('id', 'tab-recent');
+ });
- it("tabs have aria-controls attribute", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- expect(screen.getByTestId("tab-all")).toHaveAttribute(
- "aria-controls",
- "tabpanel-all",
- );
- expect(screen.getByTestId("tab-featured")).toHaveAttribute(
- "aria-controls",
- "tabpanel-featured",
- );
- });
- });
+ it('tabs have aria-controls attribute', () => {
+ const handleChange = vi.fn();
+ render();
+ expect(screen.getByTestId('tab-all')).toHaveAttribute('aria-controls', 'tabpanel-all');
+ expect(screen.getByTestId('tab-featured')).toHaveAttribute(
+ 'aria-controls',
+ 'tabpanel-featured'
+ );
+ });
+ });
- describe("HTML Attributes", () => {
- it("passes through id attribute", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const tabs = screen.getByTestId("tabs");
- expect(tabs).toHaveAttribute("id", "tabs-id");
- });
+ describe('HTML Attributes', () => {
+ it('passes through id attribute', () => {
+ const handleChange = vi.fn();
+ render(
+
+ );
+ const tabs = screen.getByTestId('tabs');
+ expect(tabs).toHaveAttribute('id', 'tabs-id');
+ });
- it("passes through data attributes", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const tabs = screen.getByTestId("tabs");
- expect(tabs).toHaveAttribute("data-custom", "test");
- });
- });
+ it('passes through data attributes', () => {
+ const handleChange = vi.fn();
+ render(
+
+ );
+ const tabs = screen.getByTestId('tabs');
+ expect(tabs).toHaveAttribute('data-custom', 'test');
+ });
+ });
- describe("Custom className", () => {
- it("merges custom className with variant classes", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const tabs = screen.getByTestId("tabs");
- expect(tabs.className).toContain("custom-class");
- expect(tabs.className).toContain("flex");
- expect(tabs.className).toContain("border-b");
- });
- });
+ describe('Custom className', () => {
+ it('merges custom className with variant classes', () => {
+ const handleChange = vi.fn();
+ render(
+
+ );
+ const tabs = screen.getByTestId('tabs');
+ expect(tabs.className).toContain('custom-class');
+ expect(tabs.className).toContain('flex');
+ expect(tabs.className).toContain('border-b');
+ });
+ });
- describe("Ref Forwarding", () => {
- it("forwards ref to container element", () => {
- const handleChange = vi.fn();
- let ref: HTMLDivElement | null = null;
- render(
- {
- ref = el;
- }}
- />,
- );
- expect(ref).toBeInstanceOf(HTMLDivElement);
- expect(ref).toHaveAttribute("role", "tablist");
- });
- });
+ describe('Ref Forwarding', () => {
+ it('forwards ref to container element', () => {
+ const handleChange = vi.fn();
+ let ref: HTMLDivElement | null = null;
+ render(
+ {
+ ref = el;
+ }}
+ />
+ );
+ expect(ref).toBeInstanceOf(HTMLDivElement);
+ expect(ref).toHaveAttribute('role', 'tablist');
+ });
+ });
- describe("Base Classes", () => {
- it("container includes base classes", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const tabs = screen.getByTestId("tabs");
- expect(tabs.className).toContain("flex");
- expect(tabs.className).toContain("items-center");
- expect(tabs.className).toContain("overflow-x-auto");
- expect(tabs.className).toContain("border-b");
- });
+ describe('Base Classes', () => {
+ it('container includes base classes', () => {
+ const handleChange = vi.fn();
+ render(
+
+ );
+ const tabs = screen.getByTestId('tabs');
+ expect(tabs.className).toContain('flex');
+ expect(tabs.className).toContain('items-center');
+ expect(tabs.className).toContain('overflow-x-auto');
+ expect(tabs.className).toContain('border-b');
+ });
- it("tab buttons include base classes", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const tab = screen.getByTestId("tab-all");
- expect(tab.className).toContain("inline-flex");
- expect(tab.className).toContain("items-center");
- expect(tab.className).toContain("font-medium");
- expect(tab.className).toContain("whitespace-nowrap");
- expect(tab.className).toContain("border-b-2");
- expect(tab.className).toContain("transition-colors");
- });
- });
+ it('tab buttons include base classes', () => {
+ const handleChange = vi.fn();
+ render();
+ const tab = screen.getByTestId('tab-all');
+ expect(tab.className).toContain('inline-flex');
+ expect(tab.className).toContain('items-center');
+ expect(tab.className).toContain('font-medium');
+ expect(tab.className).toContain('whitespace-nowrap');
+ expect(tab.className).toContain('border-b-2');
+ expect(tab.className).toContain('transition-colors');
+ });
+ });
- describe("Compound Scenarios", () => {
- it("works correctly with large size and active tab with count", () => {
- const handleChange = vi.fn();
- render(
- ,
- );
- const tab = screen.getByTestId("tab-featured");
- expect(tab.className).toContain("text-lg");
- expect(tab.className).toContain("px-6");
- expect(tab.className).toContain("py-4");
- expect(tab.className).toContain("text-foreground");
- expect(tab.className).toContain("border-primary");
- const badge = screen.getByText("42");
- expect(badge.className).toContain("text-foreground");
- });
+ describe('Compound Scenarios', () => {
+ it('works correctly with large size and active tab with count', () => {
+ const handleChange = vi.fn();
+ render();
+ const tab = screen.getByTestId('tab-featured');
+ expect(tab.className).toContain('text-lg');
+ expect(tab.className).toContain('px-6');
+ expect(tab.className).toContain('py-4');
+ expect(tab.className).toContain('text-foreground');
+ expect(tab.className).toContain('border-primary');
+ const badge = screen.getByText('42');
+ expect(badge.className).toContain('text-foreground');
+ });
- it("handles single tab", () => {
- const handleChange = vi.fn();
- const singleTab: Tab[] = [{ id: "only", label: "Only Tab" }];
- render(
- ,
- );
- expect(screen.getByTestId("tab-only")).toBeInTheDocument();
- expect(screen.getByText("Only Tab")).toBeInTheDocument();
- });
+ it('handles single tab', () => {
+ const handleChange = vi.fn();
+ const singleTab: Tab[] = [{ id: 'only', label: 'Only Tab' }];
+ render();
+ expect(screen.getByTestId('tab-only')).toBeInTheDocument();
+ expect(screen.getByText('Only Tab')).toBeInTheDocument();
+ });
- it("handles empty tabs array", () => {
- const handleChange = vi.fn();
- const { container } = render(
- ,
- );
- const buttons = container.querySelectorAll("button");
- expect(buttons.length).toBe(0);
- });
- });
+ it('handles empty tabs array', () => {
+ const handleChange = vi.fn();
+ const { container } = render();
+ const buttons = container.querySelectorAll('button');
+ expect(buttons.length).toBe(0);
+ });
+ });
});
diff --git a/packages/ui/src/Tabs/Tabs.tsx b/packages/ui/src/Tabs/Tabs.tsx
index 0512c31..8d668a3 100644
--- a/packages/ui/src/Tabs/Tabs.tsx
+++ b/packages/ui/src/Tabs/Tabs.tsx
@@ -1,11 +1,7 @@
-import { cn } from "@tpmjs/utils/cn";
-import { forwardRef } from "react";
-import type { Tab, TabsProps } from "./types";
-import {
- tabButtonVariants,
- tabCountVariants,
- tabsContainerVariants,
-} from "./variants";
+import { cn } from '@tpmjs/utils/cn';
+import { forwardRef } from 'react';
+import type { Tab, TabsProps } from './types';
+import { tabButtonVariants, tabCountVariants, tabsContainerVariants } from './variants';
// Re-export types for consumers
export type { Tab, TabsProps };
@@ -39,67 +35,59 @@ export type { Tab, TabsProps };
* ```
*/
export const Tabs = forwardRef(
- (
- {
- className,
- tabs,
- activeTab,
- onTabChange,
- size = "md",
- variant = "default",
- ...props
- },
- ref,
- ) => {
- return (
-
- {tabs.map((tab) => {
- const isActive = tab.id === activeTab;
+ (
+ { className, tabs, activeTab, onTabChange, size = 'md', variant = 'default', ...props },
+ ref
+ ) => {
+ return (
+
+ {tabs.map((tab) => {
+ const isActive = tab.id === activeTab;
- return (
-
- );
- })}
-
- );
- },
+ return (
+
+ );
+ })}
+
+ );
+ }
);
-Tabs.displayName = "Tabs";
+Tabs.displayName = 'Tabs';
diff --git a/packages/ui/src/Tabs/tokens.ts b/packages/ui/src/Tabs/tokens.ts
index 6403ff1..08f4810 100644
--- a/packages/ui/src/Tabs/tokens.ts
+++ b/packages/ui/src/Tabs/tokens.ts
@@ -1,33 +1,33 @@
-import { fontSize, spacing } from "../tokens";
+import { fontSize, spacing } from '../tokens';
/**
* Tabs component design tokens
* Maps global tokens to tabs-specific semantics
*/
export const tabsTokens = {
- /** Padding for each size */
- padding: {
- sm: {
- x: spacing[3], // 0.75rem (12px)
- y: spacing[2], // 0.5rem (8px)
- },
- md: {
- x: spacing[4], // 1rem (16px)
- y: spacing[3], // 0.75rem (12px)
- },
- lg: {
- x: spacing[6], // 1.5rem (24px)
- y: spacing[4], // 1rem (16px)
- },
- },
+ /** Padding for each size */
+ padding: {
+ sm: {
+ x: spacing[3], // 0.75rem (12px)
+ y: spacing[2], // 0.5rem (8px)
+ },
+ md: {
+ x: spacing[4], // 1rem (16px)
+ y: spacing[3], // 0.75rem (12px)
+ },
+ lg: {
+ x: spacing[6], // 1.5rem (24px)
+ y: spacing[4], // 1rem (16px)
+ },
+ },
- /** Font size for each size */
- fontSize: {
- sm: fontSize.sm, // 0.875rem (14px)
- md: fontSize.base, // 1rem (16px)
- lg: fontSize.lg, // 1.125rem (18px)
- },
+ /** Font size for each size */
+ fontSize: {
+ sm: fontSize.sm, // 0.875rem (14px)
+ md: fontSize.base, // 1rem (16px)
+ lg: fontSize.lg, // 1.125rem (18px)
+ },
- /** Gap between tabs */
- gap: spacing[1], // 0.25rem (4px)
+ /** Gap between tabs */
+ gap: spacing[1], // 0.25rem (4px)
} as const;
diff --git a/packages/ui/src/Tabs/types.ts b/packages/ui/src/Tabs/types.ts
index ed224ba..94c6cdd 100644
--- a/packages/ui/src/Tabs/types.ts
+++ b/packages/ui/src/Tabs/types.ts
@@ -1,56 +1,55 @@
-import type { HTMLAttributes } from "react";
+import type { HTMLAttributes } from 'react';
/**
* Tab definition
*/
export interface Tab {
- /**
- * Unique identifier for the tab
- */
- id: string;
+ /**
+ * Unique identifier for the tab
+ */
+ id: string;
- /**
- * Display label for the tab
- */
- label: string;
+ /**
+ * Display label for the tab
+ */
+ label: string;
- /**
- * Optional badge count to display
- */
- count?: number;
+ /**
+ * Optional badge count to display
+ */
+ count?: number;
}
/**
* Tabs component props
*/
-export interface TabsProps
- extends Omit, "onChange"> {
- /**
- * Array of tabs to display
- */
- tabs: Tab[];
+export interface TabsProps extends Omit, 'onChange'> {
+ /**
+ * Array of tabs to display
+ */
+ tabs: Tab[];
- /**
- * Currently active tab ID
- */
- activeTab: string;
+ /**
+ * Currently active tab ID
+ */
+ activeTab: string;
- /**
- * Callback when tab is changed
- */
- onTabChange: (tabId: string) => void;
+ /**
+ * Callback when tab is changed
+ */
+ onTabChange: (tabId: string) => void;
- /**
- * Size variant
- * @default 'md'
- */
- size?: "sm" | "md" | "lg";
+ /**
+ * Size variant
+ * @default 'md'
+ */
+ size?: 'sm' | 'md' | 'lg';
- /**
- * Visual variant
- * @default 'default'
- */
- variant?: "default" | "blueprint";
+ /**
+ * Visual variant
+ * @default 'default'
+ */
+ variant?: 'default' | 'blueprint';
}
/**
diff --git a/packages/ui/src/Tabs/variants.ts b/packages/ui/src/Tabs/variants.ts
index 8b41085..a3cd41e 100644
--- a/packages/ui/src/Tabs/variants.ts
+++ b/packages/ui/src/Tabs/variants.ts
@@ -1,106 +1,106 @@
-import { createVariants } from "../system/variants";
+import { createVariants } from '../system/variants';
/**
* Tabs container variant definitions
*/
export const tabsContainerVariants = createVariants({
- base: [
- // Layout
- "flex items-center",
- // Overflow
- "overflow-x-auto",
- ].join(" "),
+ base: [
+ // Layout
+ 'flex items-center',
+ // Overflow
+ 'overflow-x-auto',
+ ].join(' '),
- variants: {
- size: {
- sm: "gap-1",
- md: "gap-1",
- lg: "gap-1",
- },
- variant: {
- default: "border-b border-border",
- blueprint: "border-b border-dotted border-border",
- },
- },
+ variants: {
+ size: {
+ sm: 'gap-1',
+ md: 'gap-1',
+ lg: 'gap-1',
+ },
+ variant: {
+ default: 'border-b border-border',
+ blueprint: 'border-b border-dotted border-border',
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- size: "md",
- variant: "default",
- },
+ defaultVariants: {
+ size: 'md',
+ variant: 'default',
+ },
});
/**
* Tab button variant definitions
*/
export const tabButtonVariants = createVariants({
- base: [
- // Display
- "inline-flex items-center gap-2",
- // Font
- "font-medium whitespace-nowrap",
- // Transition
- "transition-colors duration-200",
- // Cursor
- "cursor-pointer",
- ].join(" "),
+ base: [
+ // Display
+ 'inline-flex items-center gap-2',
+ // Font
+ 'font-medium whitespace-nowrap',
+ // Transition
+ 'transition-colors duration-200',
+ // Cursor
+ 'cursor-pointer',
+ ].join(' '),
- variants: {
- size: {
- sm: "text-sm px-3 py-2",
- md: "text-base px-4 py-3",
- lg: "text-lg px-6 py-4",
- },
- active: {
- true: "text-foreground border-primary",
- false:
- "text-foreground-secondary border-transparent hover:text-foreground hover:border-border-strong",
- },
- variant: {
- default: "border-b-2",
- blueprint: "border-b-2 border-dotted",
- },
- },
+ variants: {
+ size: {
+ sm: 'text-sm px-3 py-2',
+ md: 'text-base px-4 py-3',
+ lg: 'text-lg px-6 py-4',
+ },
+ active: {
+ true: 'text-foreground border-primary',
+ false:
+ 'text-foreground-secondary border-transparent hover:text-foreground hover:border-border-strong',
+ },
+ variant: {
+ default: 'border-b-2',
+ blueprint: 'border-b-2 border-dotted',
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- size: "md",
- active: "false",
- variant: "default",
- },
+ defaultVariants: {
+ size: 'md',
+ active: 'false',
+ variant: 'default',
+ },
});
/**
* Tab count badge variant definitions
*/
export const tabCountVariants = createVariants({
- base: [
- // Display
- "inline-flex items-center justify-center",
- // Size
- "min-w-[1.25rem] h-5",
- // Padding
- "px-1.5",
- // Font
- "text-xs font-medium tabular-nums",
- // Background
- "bg-surface-elevated",
- // Border
- "rounded-full",
- ].join(" "),
+ base: [
+ // Display
+ 'inline-flex items-center justify-center',
+ // Size
+ 'min-w-[1.25rem] h-5',
+ // Padding
+ 'px-1.5',
+ // Font
+ 'text-xs font-medium tabular-nums',
+ // Background
+ 'bg-surface-elevated',
+ // Border
+ 'rounded-full',
+ ].join(' '),
- variants: {
- active: {
- true: "text-foreground",
- false: "text-foreground-tertiary",
- },
- },
+ variants: {
+ active: {
+ true: 'text-foreground',
+ false: 'text-foreground-tertiary',
+ },
+ },
- compoundVariants: [],
+ compoundVariants: [],
- defaultVariants: {
- active: "false",
- },
+ defaultVariants: {
+ active: 'false',
+ },
});
diff --git a/packages/ui/src/system/variants.ts b/packages/ui/src/system/variants.ts
index 9d95d6b..050822e 100644
--- a/packages/ui/src/system/variants.ts
+++ b/packages/ui/src/system/variants.ts
@@ -38,29 +38,28 @@
* Configuration for a variant system
*/
export type VariantConfig>> = {
- /** Base classes applied to all variants */
- base: string;
+ /** Base classes applied to all variants */
+ base: string;
- /** Variant definitions - each key is a variant axis with possible values */
- variants: T;
+ /** Variant definitions - each key is a variant axis with possible values */
+ variants: T;
- /** Compound variants - apply classes when multiple conditions are met */
- compoundVariants?: Array<{
- conditions: Partial<{ [K in keyof T]: keyof T[K] }>;
- className: string;
- }>;
+ /** Compound variants - apply classes when multiple conditions are met */
+ compoundVariants?: Array<{
+ conditions: Partial<{ [K in keyof T]: keyof T[K] }>;
+ className: string;
+ }>;
- /** Default variant values */
- defaultVariants?: Partial<{ [K in keyof T]: keyof T[K] }>;
+ /** Default variant values */
+ defaultVariants?: Partial<{ [K in keyof T]: keyof T[K] }>;
};
/**
* Props type for consuming variant functions
*/
-export type VariantProps>> =
- Partial<{
- [K in keyof T]: keyof T[K];
- }>;
+export type VariantProps>> = Partial<{
+ [K in keyof T]: keyof T[K];
+}>;
/**
* Creates a type-safe variant composition function
@@ -68,44 +67,39 @@ export type VariantProps>> =
* @param config - Variant configuration with base, variants, compound variants, and defaults
* @returns Function that accepts variant props and returns composed className string
*/
-export function createVariants<
- T extends Record>,
->(config: VariantConfig) {
- return function getVariantClasses(props?: VariantProps): string {
- const classes: string[] = [config.base];
+export function createVariants>>(
+ config: VariantConfig
+) {
+ return function getVariantClasses(props?: VariantProps): string {
+ const classes: string[] = [config.base];
- // Merge props with defaults
- const mergedProps = {
- ...config.defaultVariants,
- ...props,
- } as VariantProps;
+ // Merge props with defaults
+ const mergedProps = {
+ ...config.defaultVariants,
+ ...props,
+ } as VariantProps;
- // Apply variant classes
- for (const [key, value] of Object.entries(mergedProps)) {
- const valueStr = value as string;
- if (
- value !== undefined &&
- value !== null &&
- config.variants[key]?.[valueStr]
- ) {
- classes.push(config.variants[key][valueStr]);
- }
- }
+ // Apply variant classes
+ for (const [key, value] of Object.entries(mergedProps)) {
+ const valueStr = value as string;
+ if (value !== undefined && value !== null && config.variants[key]?.[valueStr]) {
+ classes.push(config.variants[key][valueStr]);
+ }
+ }
- // Apply compound variants
- if (config.compoundVariants) {
- for (const { conditions, className } of config.compoundVariants) {
- const matches = Object.entries(conditions).every(
- ([key, value]) =>
- mergedProps[key as keyof typeof mergedProps] === value,
- );
- if (matches) {
- classes.push(className);
- }
- }
- }
+ // Apply compound variants
+ if (config.compoundVariants) {
+ for (const { conditions, className } of config.compoundVariants) {
+ const matches = Object.entries(conditions).every(
+ ([key, value]) => mergedProps[key as keyof typeof mergedProps] === value
+ );
+ if (matches) {
+ classes.push(className);
+ }
+ }
+ }
- // Filter out any empty strings and join
- return classes.filter(Boolean).join(" ");
- };
+ // Filter out any empty strings and join
+ return classes.filter(Boolean).join(' ');
+ };
}
diff --git a/packages/ui/src/test-setup.ts b/packages/ui/src/test-setup.ts
index f149f27..bb02c60 100644
--- a/packages/ui/src/test-setup.ts
+++ b/packages/ui/src/test-setup.ts
@@ -1 +1 @@
-import "@testing-library/jest-dom/vitest";
+import '@testing-library/jest-dom/vitest';
diff --git a/packages/ui/src/tokens/animations.ts b/packages/ui/src/tokens/animations.ts
index 0444958..ea39fdc 100644
--- a/packages/ui/src/tokens/animations.ts
+++ b/packages/ui/src/tokens/animations.ts
@@ -7,11 +7,11 @@
* Animation durations
*/
export const duration = {
- fast: "150ms",
- base: "200ms",
- slow: "300ms",
- slower: "500ms",
- slowest: "700ms",
+ fast: '150ms',
+ base: '200ms',
+ slow: '300ms',
+ slower: '500ms',
+ slowest: '700ms',
} as const;
/**
@@ -19,15 +19,15 @@ export const duration = {
* Cubic bezier curves for natural motion
*/
export const easing = {
- linear: "linear",
- ease: "ease",
- easeIn: "cubic-bezier(0.4, 0, 1, 1)",
- easeOut: "cubic-bezier(0, 0, 0.2, 1)",
- easeInOut: "cubic-bezier(0.4, 0, 0.2, 1)",
- // Custom easings for specific interactions
- smooth: "cubic-bezier(0.25, 0.1, 0.25, 1)",
- bounce: "cubic-bezier(0.68, -0.55, 0.265, 1.55)",
- spring: "cubic-bezier(0.175, 0.885, 0.32, 1.275)",
+ linear: 'linear',
+ ease: 'ease',
+ easeIn: 'cubic-bezier(0.4, 0, 1, 1)',
+ easeOut: 'cubic-bezier(0, 0, 0.2, 1)',
+ easeInOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
+ // Custom easings for specific interactions
+ smooth: 'cubic-bezier(0.25, 0.1, 0.25, 1)',
+ bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
+ spring: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
} as const;
/**
@@ -35,63 +35,63 @@ export const easing = {
* Predefined animation sequences
*/
export const keyframes = {
- fadeIn: {
- from: { opacity: "0" },
- to: { opacity: "1" },
- },
- fadeOut: {
- from: { opacity: "1" },
- to: { opacity: "0" },
- },
- slideUp: {
- from: { transform: "translateY(10px)", opacity: "0" },
- to: { transform: "translateY(0)", opacity: "1" },
- },
- slideDown: {
- from: { transform: "translateY(-10px)", opacity: "0" },
- to: { transform: "translateY(0)", opacity: "1" },
- },
- slideLeft: {
- from: { transform: "translateX(10px)", opacity: "0" },
- to: { transform: "translateX(0)", opacity: "1" },
- },
- slideRight: {
- from: { transform: "translateX(-10px)", opacity: "0" },
- to: { transform: "translateX(0)", opacity: "1" },
- },
- scaleIn: {
- from: { transform: "scale(0.95)", opacity: "0" },
- to: { transform: "scale(1)", opacity: "1" },
- },
- scaleOut: {
- from: { transform: "scale(1)", opacity: "1" },
- to: { transform: "scale(0.95)", opacity: "0" },
- },
- shimmer: {
- "0%": { backgroundPosition: "-1000px 0" },
- "100%": { backgroundPosition: "1000px 0" },
- },
- pulse: {
- "0%, 100%": { opacity: "1" },
- "50%": { opacity: "0.5" },
- },
- spin: {
- from: { transform: "rotate(0deg)" },
- to: { transform: "rotate(360deg)" },
- },
- ping: {
- "75%, 100%": { transform: "scale(2)", opacity: "0" },
- },
- bounce: {
- "0%, 100%": {
- transform: "translateY(-25%)",
- animationTimingFunction: "cubic-bezier(0.8, 0, 1, 1)",
- },
- "50%": {
- transform: "translateY(0)",
- animationTimingFunction: "cubic-bezier(0, 0, 0.2, 1)",
- },
- },
+ fadeIn: {
+ from: { opacity: '0' },
+ to: { opacity: '1' },
+ },
+ fadeOut: {
+ from: { opacity: '1' },
+ to: { opacity: '0' },
+ },
+ slideUp: {
+ from: { transform: 'translateY(10px)', opacity: '0' },
+ to: { transform: 'translateY(0)', opacity: '1' },
+ },
+ slideDown: {
+ from: { transform: 'translateY(-10px)', opacity: '0' },
+ to: { transform: 'translateY(0)', opacity: '1' },
+ },
+ slideLeft: {
+ from: { transform: 'translateX(10px)', opacity: '0' },
+ to: { transform: 'translateX(0)', opacity: '1' },
+ },
+ slideRight: {
+ from: { transform: 'translateX(-10px)', opacity: '0' },
+ to: { transform: 'translateX(0)', opacity: '1' },
+ },
+ scaleIn: {
+ from: { transform: 'scale(0.95)', opacity: '0' },
+ to: { transform: 'scale(1)', opacity: '1' },
+ },
+ scaleOut: {
+ from: { transform: 'scale(1)', opacity: '1' },
+ to: { transform: 'scale(0.95)', opacity: '0' },
+ },
+ shimmer: {
+ '0%': { backgroundPosition: '-1000px 0' },
+ '100%': { backgroundPosition: '1000px 0' },
+ },
+ pulse: {
+ '0%, 100%': { opacity: '1' },
+ '50%': { opacity: '0.5' },
+ },
+ spin: {
+ from: { transform: 'rotate(0deg)' },
+ to: { transform: 'rotate(360deg)' },
+ },
+ ping: {
+ '75%, 100%': { transform: 'scale(2)', opacity: '0' },
+ },
+ bounce: {
+ '0%, 100%': {
+ transform: 'translateY(-25%)',
+ animationTimingFunction: 'cubic-bezier(0.8, 0, 1, 1)',
+ },
+ '50%': {
+ transform: 'translateY(0)',
+ animationTimingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
+ },
+ },
} as const;
/**
@@ -99,31 +99,31 @@ export const keyframes = {
* Commonly used transition combinations
*/
export const transitions = {
- /** Base transition for colors and backgrounds */
- base: `background-color ${duration.base} ${easing.easeInOut}, border-color ${duration.base} ${easing.easeInOut}, color ${duration.base} ${easing.easeInOut}`,
+ /** Base transition for colors and backgrounds */
+ base: `background-color ${duration.base} ${easing.easeInOut}, border-color ${duration.base} ${easing.easeInOut}, color ${duration.base} ${easing.easeInOut}`,
- /** Transform transition */
- transform: `transform ${duration.base} ${easing.easeInOut}`,
+ /** Transform transition */
+ transform: `transform ${duration.base} ${easing.easeInOut}`,
- /** Opacity transition */
- opacity: `opacity ${duration.base} ${easing.easeInOut}`,
+ /** Opacity transition */
+ opacity: `opacity ${duration.base} ${easing.easeInOut}`,
- /** All properties transition */
- all: `all ${duration.slow} ${easing.easeInOut}`,
+ /** All properties transition */
+ all: `all ${duration.slow} ${easing.easeInOut}`,
- /** Fast transition for immediate feedback */
- fast: `all ${duration.fast} ${easing.easeOut}`,
+ /** Fast transition for immediate feedback */
+ fast: `all ${duration.fast} ${easing.easeOut}`,
- /** Smooth transition for hover effects */
- smooth: `all ${duration.base} ${easing.smooth}`,
+ /** Smooth transition for hover effects */
+ smooth: `all ${duration.base} ${easing.smooth}`,
} as const;
/**
* Complete animations export
*/
export const animations = {
- duration,
- easing,
- keyframes,
- transitions,
+ duration,
+ easing,
+ keyframes,
+ transitions,
} as const;
diff --git a/packages/ui/src/tokens/borders.ts b/packages/ui/src/tokens/borders.ts
index 7b3c87a..5b728fe 100644
--- a/packages/ui/src/tokens/borders.ts
+++ b/packages/ui/src/tokens/borders.ts
@@ -7,21 +7,21 @@
* Border widths
*/
export const borderWidth = {
- DEFAULT: "1px",
- 0: "0",
- 2: "2px",
- 4: "4px",
- 8: "8px",
+ DEFAULT: '1px',
+ 0: '0',
+ 2: '2px',
+ 4: '4px',
+ 8: '8px',
} as const;
/**
* Border styles
*/
export const borderStyle = {
- solid: "solid",
- dashed: "dashed",
- dotted: "dotted",
- none: "none",
+ solid: 'solid',
+ dashed: 'dashed',
+ dotted: 'dotted',
+ none: 'none',
} as const;
/**
@@ -29,14 +29,14 @@ export const borderStyle = {
* References CSS variables for theme consistency
*/
export const borderRadius = {
- none: "0",
- sm: "var(--radius-sm)",
- md: "var(--radius-md)",
- lg: "var(--radius-lg)",
- xl: "var(--radius-xl)",
- "2xl": "var(--radius-2xl, 1rem)",
- "3xl": "var(--radius-3xl, 1.5rem)",
- full: "9999px",
+ none: '0',
+ sm: 'var(--radius-sm)',
+ md: 'var(--radius-md)',
+ lg: 'var(--radius-lg)',
+ xl: 'var(--radius-xl)',
+ '2xl': 'var(--radius-2xl, 1rem)',
+ '3xl': 'var(--radius-3xl, 1.5rem)',
+ full: '9999px',
} as const;
/**
@@ -44,29 +44,29 @@ export const borderRadius = {
* Used in globals.css
*/
export const borderRadiusVars = {
- sm: "4px",
- md: "6px",
- lg: "8px",
- xl: "12px",
+ sm: '4px',
+ md: '6px',
+ lg: '8px',
+ xl: '12px',
} as const;
/**
* Border color references (use CSS variables)
*/
export const borderColor = {
- DEFAULT: "hsl(var(--border))",
- strong: "hsl(var(--border-strong))",
- subtle: "hsl(var(--border-subtle))",
- input: "hsl(var(--input))",
- ring: "hsl(var(--ring))",
+ DEFAULT: 'hsl(var(--border))',
+ strong: 'hsl(var(--border-strong))',
+ subtle: 'hsl(var(--border-subtle))',
+ input: 'hsl(var(--input))',
+ ring: 'hsl(var(--ring))',
} as const;
/**
* Complete border export
*/
export const borders = {
- width: borderWidth,
- style: borderStyle,
- radius: borderRadius,
- color: borderColor,
+ width: borderWidth,
+ style: borderStyle,
+ radius: borderRadius,
+ color: borderColor,
} as const;
diff --git a/packages/ui/src/tokens/colors.ts b/packages/ui/src/tokens/colors.ts
index d0d5745..a19e4bc 100644
--- a/packages/ui/src/tokens/colors.ts
+++ b/packages/ui/src/tokens/colors.ts
@@ -10,19 +10,19 @@
* Optimized for dark mode with subtle, desaturated tones
*/
export const neutral = {
- 0: "#000000",
- 50: "#0a0a0a",
- 100: "#171717",
- 200: "#262626",
- 300: "#404040",
- 400: "#525252",
- 500: "#737373",
- 600: "#a3a3a3",
- 700: "#d4d4d4",
- 800: "#e5e5e5",
- 900: "#f5f5f5",
- 950: "#fafafa",
- 1000: "#ffffff",
+ 0: '#000000',
+ 50: '#0a0a0a',
+ 100: '#171717',
+ 200: '#262626',
+ 300: '#404040',
+ 400: '#525252',
+ 500: '#737373',
+ 600: '#a3a3a3',
+ 700: '#d4d4d4',
+ 800: '#e5e5e5',
+ 900: '#f5f5f5',
+ 950: '#fafafa',
+ 1000: '#ffffff',
} as const;
/**
@@ -30,61 +30,61 @@ export const neutral = {
* Reference CSS variables for theme-aware colors
*/
export const semantic = {
- background: {
- DEFAULT: "hsl(var(--background))",
- surface: "hsl(var(--surface))",
- elevated: "hsl(var(--surface-elevated))",
- overlay: "hsl(var(--surface-overlay))",
- },
- foreground: {
- DEFAULT: "hsl(var(--foreground))",
- secondary: "hsl(var(--foreground-secondary))",
- tertiary: "hsl(var(--foreground-tertiary))",
- muted: "hsl(var(--foreground-muted))",
- },
- border: {
- DEFAULT: "hsl(var(--border))",
- strong: "hsl(var(--border-strong))",
- subtle: "hsl(var(--border-subtle))",
- },
- primary: {
- DEFAULT: "hsl(var(--primary))",
- foreground: "hsl(var(--primary-foreground))",
- },
- secondary: {
- DEFAULT: "hsl(var(--secondary))",
- foreground: "hsl(var(--secondary-foreground))",
- },
- accent: {
- DEFAULT: "hsl(var(--accent))",
- foreground: "hsl(var(--accent-foreground))",
- },
- muted: {
- DEFAULT: "hsl(var(--muted))",
- foreground: "hsl(var(--muted-foreground))",
- },
+ background: {
+ DEFAULT: 'hsl(var(--background))',
+ surface: 'hsl(var(--surface))',
+ elevated: 'hsl(var(--surface-elevated))',
+ overlay: 'hsl(var(--surface-overlay))',
+ },
+ foreground: {
+ DEFAULT: 'hsl(var(--foreground))',
+ secondary: 'hsl(var(--foreground-secondary))',
+ tertiary: 'hsl(var(--foreground-tertiary))',
+ muted: 'hsl(var(--foreground-muted))',
+ },
+ border: {
+ DEFAULT: 'hsl(var(--border))',
+ strong: 'hsl(var(--border-strong))',
+ subtle: 'hsl(var(--border-subtle))',
+ },
+ primary: {
+ DEFAULT: 'hsl(var(--primary))',
+ foreground: 'hsl(var(--primary-foreground))',
+ },
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary))',
+ foreground: 'hsl(var(--secondary-foreground))',
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent))',
+ foreground: 'hsl(var(--accent-foreground))',
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted))',
+ foreground: 'hsl(var(--muted-foreground))',
+ },
} as const;
/**
* Status colors (subtle, desaturated for dark mode)
*/
export const status = {
- success: {
- DEFAULT: "hsl(var(--success))",
- foreground: "hsl(var(--success-foreground))",
- },
- error: {
- DEFAULT: "hsl(var(--error))",
- foreground: "hsl(var(--error-foreground))",
- },
- warning: {
- DEFAULT: "hsl(var(--warning))",
- foreground: "hsl(var(--warning-foreground))",
- },
- info: {
- DEFAULT: "hsl(var(--info))",
- foreground: "hsl(var(--info-foreground))",
- },
+ success: {
+ DEFAULT: 'hsl(var(--success))',
+ foreground: 'hsl(var(--success-foreground))',
+ },
+ error: {
+ DEFAULT: 'hsl(var(--error))',
+ foreground: 'hsl(var(--error-foreground))',
+ },
+ warning: {
+ DEFAULT: 'hsl(var(--warning))',
+ foreground: 'hsl(var(--warning-foreground))',
+ },
+ info: {
+ DEFAULT: 'hsl(var(--info))',
+ foreground: 'hsl(var(--info-foreground))',
+ },
} as const;
/**
@@ -92,69 +92,69 @@ export const status = {
* Used sparingly for visual interest
*/
export const accents = {
- blue: "#60a5fa",
- purple: "#a78bfa",
- green: "#34d399",
- pink: "#f472b6",
- orange: "#fb923c",
- yellow: "#fbbf24",
+ blue: '#60a5fa',
+ purple: '#a78bfa',
+ green: '#34d399',
+ pink: '#f472b6',
+ orange: '#fb923c',
+ yellow: '#fbbf24',
} as const;
/**
* Form element colors
*/
export const form = {
- input: "hsl(var(--input))",
- ring: "hsl(var(--ring))",
- ringOffset: "hsl(var(--ring-offset))",
+ input: 'hsl(var(--input))',
+ ring: 'hsl(var(--ring))',
+ ringOffset: 'hsl(var(--ring-offset))',
} as const;
/**
* Grid/blueprint pattern colors
*/
export const grid = {
- color: "hsl(var(--grid-color))",
- size: "var(--grid-size)",
+ color: 'hsl(var(--grid-color))',
+ size: 'var(--grid-size)',
} as const;
/**
* Card colors
*/
export const card = {
- DEFAULT: "hsl(var(--card))",
- foreground: "hsl(var(--card-foreground))",
+ DEFAULT: 'hsl(var(--card))',
+ foreground: 'hsl(var(--card-foreground))',
} as const;
/**
* Legacy destructive color (for backward compatibility)
*/
export const destructive = {
- DEFAULT: "hsl(var(--destructive))",
- foreground: "hsl(var(--destructive-foreground))",
+ DEFAULT: 'hsl(var(--destructive))',
+ foreground: 'hsl(var(--destructive-foreground))',
} as const;
/**
* Complete color export
*/
export const colors = {
- neutral,
- semantic,
- status,
- accents,
- form,
- grid,
- card,
- destructive,
- // Flat exports for easy access
- background: semantic.background.DEFAULT,
- foreground: semantic.foreground.DEFAULT,
- border: semantic.border.DEFAULT,
- primary: semantic.primary.DEFAULT,
- secondary: semantic.secondary.DEFAULT,
- accent: semantic.accent.DEFAULT,
- muted: semantic.muted.DEFAULT,
- success: status.success.DEFAULT,
- error: status.error.DEFAULT,
- warning: status.warning.DEFAULT,
- info: status.info.DEFAULT,
+ neutral,
+ semantic,
+ status,
+ accents,
+ form,
+ grid,
+ card,
+ destructive,
+ // Flat exports for easy access
+ background: semantic.background.DEFAULT,
+ foreground: semantic.foreground.DEFAULT,
+ border: semantic.border.DEFAULT,
+ primary: semantic.primary.DEFAULT,
+ secondary: semantic.secondary.DEFAULT,
+ accent: semantic.accent.DEFAULT,
+ muted: semantic.muted.DEFAULT,
+ success: status.success.DEFAULT,
+ error: status.error.DEFAULT,
+ warning: status.warning.DEFAULT,
+ info: status.info.DEFAULT,
} as const;
diff --git a/packages/ui/src/tokens/index.ts b/packages/ui/src/tokens/index.ts
index a6558c1..df07940 100644
--- a/packages/ui/src/tokens/index.ts
+++ b/packages/ui/src/tokens/index.ts
@@ -3,9 +3,9 @@
* Export all token categories
*/
-export * from "./colors";
-export * from "./typography";
-export * from "./spacing";
-export * from "./borders";
-export * from "./shadows";
-export * from "./animations";
+export * from './colors';
+export * from './typography';
+export * from './spacing';
+export * from './borders';
+export * from './shadows';
+export * from './animations';
diff --git a/packages/ui/src/tokens/shadows.ts b/packages/ui/src/tokens/shadows.ts
index dbef7d5..9f7628a 100644
--- a/packages/ui/src/tokens/shadows.ts
+++ b/packages/ui/src/tokens/shadows.ts
@@ -8,27 +8,27 @@
* Subtle and understated for technical aesthetic
*/
export const boxShadow = {
- none: "none",
- sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
- base: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
- md: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
- lg: "0 10px 15px -3px rgb(0 0 0 / 0.2), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
- xl: "0 20px 25px -5px rgb(0 0 0 / 0.2), 0 8px 10px -6px rgb(0 0 0 / 0.1)",
- "2xl": "0 25px 50px -12px rgb(0 0 0 / 0.3)",
- inner: "inset 0 2px 4px 0 rgb(0 0 0 / 0.05)",
+ none: 'none',
+ sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
+ base: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
+ md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
+ lg: '0 10px 15px -3px rgb(0 0 0 / 0.2), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
+ xl: '0 20px 25px -5px rgb(0 0 0 / 0.2), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
+ '2xl': '0 25px 50px -12px rgb(0 0 0 / 0.3)',
+ inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)',
} as const;
/**
* Drop shadow definitions (for SVG and images)
*/
export const dropShadow = {
- none: "none",
- sm: "drop-shadow(0 1px 1px rgb(0 0 0 / 0.05))",
- base: "drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) drop-shadow(0 1px 1px rgb(0 0 0 / 0.06))",
- md: "drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06))",
- lg: "drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1))",
- xl: "drop-shadow(0 20px 13px rgb(0 0 0 / 0.03)) drop-shadow(0 8px 5px rgb(0 0 0 / 0.08))",
- "2xl": "drop-shadow(0 25px 25px rgb(0 0 0 / 0.15))",
+ none: 'none',
+ sm: 'drop-shadow(0 1px 1px rgb(0 0 0 / 0.05))',
+ base: 'drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) drop-shadow(0 1px 1px rgb(0 0 0 / 0.06))',
+ md: 'drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06))',
+ lg: 'drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1))',
+ xl: 'drop-shadow(0 20px 13px rgb(0 0 0 / 0.03)) drop-shadow(0 8px 5px rgb(0 0 0 / 0.08))',
+ '2xl': 'drop-shadow(0 25px 25px rgb(0 0 0 / 0.15))',
} as const;
/**
@@ -36,20 +36,20 @@ export const dropShadow = {
* Semantic elevation levels for layered interfaces
*/
export const elevation = {
- 0: boxShadow.none,
- 1: boxShadow.sm,
- 2: boxShadow.base,
- 3: boxShadow.md,
- 4: boxShadow.lg,
- 5: boxShadow.xl,
- 6: boxShadow["2xl"],
+ 0: boxShadow.none,
+ 1: boxShadow.sm,
+ 2: boxShadow.base,
+ 3: boxShadow.md,
+ 4: boxShadow.lg,
+ 5: boxShadow.xl,
+ 6: boxShadow['2xl'],
} as const;
/**
* Complete shadows export
*/
export const shadows = {
- box: boxShadow,
- drop: dropShadow,
- elevation,
+ box: boxShadow,
+ drop: dropShadow,
+ elevation,
} as const;
diff --git a/packages/ui/src/tokens/spacing.ts b/packages/ui/src/tokens/spacing.ts
index ec25df7..61b0748 100644
--- a/packages/ui/src/tokens/spacing.ts
+++ b/packages/ui/src/tokens/spacing.ts
@@ -8,85 +8,85 @@
* Uses rem units for accessibility (scales with user font size)
*/
export const spacing = {
- px: "1px",
- 0: "0",
- 0.5: "0.125rem", // 2px
- 1: "0.25rem", // 4px
- 1.5: "0.375rem", // 6px
- 2: "0.5rem", // 8px
- 2.5: "0.625rem", // 10px
- 3: "0.75rem", // 12px
- 3.5: "0.875rem", // 14px
- 4: "1rem", // 16px
- 5: "1.25rem", // 20px
- 6: "1.5rem", // 24px
- 7: "1.75rem", // 28px
- 8: "2rem", // 32px
- 9: "2.25rem", // 36px
- 10: "2.5rem", // 40px
- 11: "2.75rem", // 44px
- 12: "3rem", // 48px
- 14: "3.5rem", // 56px
- 16: "4rem", // 64px
- 20: "5rem", // 80px
- 24: "6rem", // 96px
- 28: "7rem", // 112px
- 32: "8rem", // 128px
- 36: "9rem", // 144px
- 40: "10rem", // 160px
- 44: "11rem", // 176px
- 48: "12rem", // 192px
- 52: "13rem", // 208px
- 56: "14rem", // 224px
- 60: "15rem", // 240px
- 64: "16rem", // 256px
- 72: "18rem", // 288px
- 80: "20rem", // 320px
- 96: "24rem", // 384px
+ px: '1px',
+ 0: '0',
+ 0.5: '0.125rem', // 2px
+ 1: '0.25rem', // 4px
+ 1.5: '0.375rem', // 6px
+ 2: '0.5rem', // 8px
+ 2.5: '0.625rem', // 10px
+ 3: '0.75rem', // 12px
+ 3.5: '0.875rem', // 14px
+ 4: '1rem', // 16px
+ 5: '1.25rem', // 20px
+ 6: '1.5rem', // 24px
+ 7: '1.75rem', // 28px
+ 8: '2rem', // 32px
+ 9: '2.25rem', // 36px
+ 10: '2.5rem', // 40px
+ 11: '2.75rem', // 44px
+ 12: '3rem', // 48px
+ 14: '3.5rem', // 56px
+ 16: '4rem', // 64px
+ 20: '5rem', // 80px
+ 24: '6rem', // 96px
+ 28: '7rem', // 112px
+ 32: '8rem', // 128px
+ 36: '9rem', // 144px
+ 40: '10rem', // 160px
+ 44: '11rem', // 176px
+ 48: '12rem', // 192px
+ 52: '13rem', // 208px
+ 56: '14rem', // 224px
+ 60: '15rem', // 240px
+ 64: '16rem', // 256px
+ 72: '18rem', // 288px
+ 80: '20rem', // 320px
+ 96: '24rem', // 384px
} as const;
/**
* Container widths
*/
export const containerWidth = {
- xs: "20rem", // 320px
- sm: "24rem", // 384px
- md: "28rem", // 448px
- lg: "32rem", // 512px
- xl: "36rem", // 576px
- "2xl": "42rem", // 672px
- "3xl": "48rem", // 768px
- "4xl": "56rem", // 896px
- "5xl": "64rem", // 1024px
- "6xl": "72rem", // 1152px
- "7xl": "80rem", // 1280px
- full: "100%",
- screen: "100vw",
+ xs: '20rem', // 320px
+ sm: '24rem', // 384px
+ md: '28rem', // 448px
+ lg: '32rem', // 512px
+ xl: '36rem', // 576px
+ '2xl': '42rem', // 672px
+ '3xl': '48rem', // 768px
+ '4xl': '56rem', // 896px
+ '5xl': '64rem', // 1024px
+ '6xl': '72rem', // 1152px
+ '7xl': '80rem', // 1280px
+ full: '100%',
+ screen: '100vw',
} as const;
/**
* Component-specific spacing presets
*/
export const component = {
- /** Padding for buttons, inputs, etc. */
- padding: {
- sm: spacing[3], // 12px
- md: spacing[4], // 16px
- lg: spacing[5], // 20px
- },
+ /** Padding for buttons, inputs, etc. */
+ padding: {
+ sm: spacing[3], // 12px
+ md: spacing[4], // 16px
+ lg: spacing[5], // 20px
+ },
- /** Gap between elements */
- gap: {
- sm: spacing[2], // 8px
- md: spacing[3], // 12px
- lg: spacing[4], // 16px
- },
+ /** Gap between elements */
+ gap: {
+ sm: spacing[2], // 8px
+ md: spacing[3], // 12px
+ lg: spacing[4], // 16px
+ },
- /** Margin between sections */
- margin: {
- sm: spacing[4], // 16px
- md: spacing[6], // 24px
- lg: spacing[8], // 32px
- xl: spacing[12], // 48px
- },
+ /** Margin between sections */
+ margin: {
+ sm: spacing[4], // 16px
+ md: spacing[6], // 24px
+ lg: spacing[8], // 32px
+ xl: spacing[12], // 48px
+ },
} as const;
diff --git a/packages/ui/src/tokens/typography.ts b/packages/ui/src/tokens/typography.ts
index 16e031d..7376a81 100644
--- a/packages/ui/src/tokens/typography.ts
+++ b/packages/ui/src/tokens/typography.ts
@@ -8,25 +8,25 @@
* Geist font stack with system fallbacks
*/
export const fontFamily = {
- sans: [
- "Geist",
- "-apple-system",
- "BlinkMacSystemFont",
- '"Segoe UI"',
- "Roboto",
- '"Helvetica Neue"',
- "Arial",
- "sans-serif",
- ],
- mono: [
- "Geist Mono",
- '"SF Mono"',
- "Monaco",
- "Consolas",
- '"Liberation Mono"',
- '"Courier New"',
- "monospace",
- ],
+ sans: [
+ 'Geist',
+ '-apple-system',
+ 'BlinkMacSystemFont',
+ '"Segoe UI"',
+ 'Roboto',
+ '"Helvetica Neue"',
+ 'Arial',
+ 'sans-serif',
+ ],
+ mono: [
+ 'Geist Mono',
+ '"SF Mono"',
+ 'Monaco',
+ 'Consolas',
+ '"Liberation Mono"',
+ '"Courier New"',
+ 'monospace',
+ ],
} as const;
/**
@@ -35,64 +35,64 @@ export const fontFamily = {
* Tighter letter spacing for larger sizes (optical sizing)
*/
export const fontSize = {
- xs: ["0.75rem", { lineHeight: "1rem", letterSpacing: "0" }],
- sm: ["0.875rem", { lineHeight: "1.25rem", letterSpacing: "0" }],
- base: ["1rem", { lineHeight: "1.5rem", letterSpacing: "0" }],
- lg: ["1.125rem", { lineHeight: "1.75rem", letterSpacing: "-0.01em" }],
- xl: ["1.25rem", { lineHeight: "1.75rem", letterSpacing: "-0.01em" }],
- "2xl": ["1.5rem", { lineHeight: "2rem", letterSpacing: "-0.02em" }],
- "3xl": ["1.875rem", { lineHeight: "2.25rem", letterSpacing: "-0.02em" }],
- "4xl": ["2.25rem", { lineHeight: "2.5rem", letterSpacing: "-0.03em" }],
- "5xl": ["3rem", { lineHeight: "1", letterSpacing: "-0.03em" }],
- "6xl": ["3.75rem", { lineHeight: "1", letterSpacing: "-0.04em" }],
- "7xl": ["4.5rem", { lineHeight: "1", letterSpacing: "-0.04em" }],
- "8xl": ["6rem", { lineHeight: "1", letterSpacing: "-0.05em" }],
- "9xl": ["8rem", { lineHeight: "1", letterSpacing: "-0.05em" }],
+ xs: ['0.75rem', { lineHeight: '1rem', letterSpacing: '0' }],
+ sm: ['0.875rem', { lineHeight: '1.25rem', letterSpacing: '0' }],
+ base: ['1rem', { lineHeight: '1.5rem', letterSpacing: '0' }],
+ lg: ['1.125rem', { lineHeight: '1.75rem', letterSpacing: '-0.01em' }],
+ xl: ['1.25rem', { lineHeight: '1.75rem', letterSpacing: '-0.01em' }],
+ '2xl': ['1.5rem', { lineHeight: '2rem', letterSpacing: '-0.02em' }],
+ '3xl': ['1.875rem', { lineHeight: '2.25rem', letterSpacing: '-0.02em' }],
+ '4xl': ['2.25rem', { lineHeight: '2.5rem', letterSpacing: '-0.03em' }],
+ '5xl': ['3rem', { lineHeight: '1', letterSpacing: '-0.03em' }],
+ '6xl': ['3.75rem', { lineHeight: '1', letterSpacing: '-0.04em' }],
+ '7xl': ['4.5rem', { lineHeight: '1', letterSpacing: '-0.04em' }],
+ '8xl': ['6rem', { lineHeight: '1', letterSpacing: '-0.05em' }],
+ '9xl': ['8rem', { lineHeight: '1', letterSpacing: '-0.05em' }],
} as const;
/**
* Font weights
*/
export const fontWeight = {
- light: "300",
- normal: "400",
- medium: "500",
- semibold: "600",
- bold: "700",
- extrabold: "800",
+ light: '300',
+ normal: '400',
+ medium: '500',
+ semibold: '600',
+ bold: '700',
+ extrabold: '800',
} as const;
/**
* Letter spacing values
*/
export const letterSpacing = {
- tighter: "-0.05em",
- tight: "-0.025em",
- normal: "0",
- wide: "0.025em",
- wider: "0.05em",
- widest: "0.1em",
+ tighter: '-0.05em',
+ tight: '-0.025em',
+ normal: '0',
+ wide: '0.025em',
+ wider: '0.05em',
+ widest: '0.1em',
} as const;
/**
* Line heights
*/
export const lineHeight = {
- none: "1",
- tight: "1.25",
- snug: "1.375",
- normal: "1.5",
- relaxed: "1.625",
- loose: "2",
+ none: '1',
+ tight: '1.25',
+ snug: '1.375',
+ normal: '1.5',
+ relaxed: '1.625',
+ loose: '2',
} as const;
/**
* Complete typography export
*/
export const typography = {
- fontFamily,
- fontSize,
- fontWeight,
- letterSpacing,
- lineHeight,
+ fontFamily,
+ fontSize,
+ fontWeight,
+ letterSpacing,
+ lineHeight,
} as const;
diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json
index c5eb8ea..ae193d1 100644
--- a/packages/ui/tsconfig.json
+++ b/packages/ui/tsconfig.json
@@ -1,11 +1,11 @@
{
- "extends": "@tpmjs/tsconfig/react-library.json",
- "compilerOptions": {
- "outDir": "dist",
- "rootDir": "src",
- "incremental": false,
- "composite": false
- },
- "include": ["src"],
- "exclude": ["node_modules", "dist"]
+ "extends": "@tpmjs/tsconfig/react-library.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "incremental": false,
+ "composite": false
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
}
diff --git a/packages/ui/tsup.config.ts b/packages/ui/tsup.config.ts
index 7888df4..dda9aee 100644
--- a/packages/ui/tsup.config.ts
+++ b/packages/ui/tsup.config.ts
@@ -1,44 +1,44 @@
-import path from "node:path";
-import { glob } from "glob";
-import { defineConfig } from "tsup";
+import path from 'node:path';
+import { glob } from 'glob';
+import { defineConfig } from 'tsup';
/**
* Auto-discover all component entry points
* Looks for src/ComponentName/ComponentName.tsx files where the folder name matches the file name
*/
-const allFiles = glob.sync("src/**/[A-Z]*.{ts,tsx}", {
- ignore: [
- "**/*.test.ts",
- "**/*.test.tsx",
- "**/*.stories.ts",
- "**/types.ts",
- "**/tokens.ts",
- "**/variants.ts",
- "**/index.ts",
- "src/test-setup.ts",
- "src/tokens/**",
- "src/system/**",
- ],
+const allFiles = glob.sync('src/**/[A-Z]*.{ts,tsx}', {
+ ignore: [
+ '**/*.test.ts',
+ '**/*.test.tsx',
+ '**/*.stories.ts',
+ '**/types.ts',
+ '**/tokens.ts',
+ '**/variants.ts',
+ '**/index.ts',
+ 'src/test-setup.ts',
+ 'src/tokens/**',
+ 'src/system/**',
+ ],
});
// Filter to only include files where the folder name matches the file name
// e.g., src/Button/Button.tsx is included, but src/Button/helpers.ts is not
const entries = allFiles.filter((file) => {
- const dir = path.dirname(file);
- const folderName = path.basename(dir);
- const fileName = path.basename(file).replace(/\.(ts|tsx)$/, "");
- return folderName === fileName;
+ const dir = path.dirname(file);
+ const folderName = path.basename(dir);
+ const fileName = path.basename(file).replace(/\.(ts|tsx)$/, '');
+ return folderName === fileName;
});
export default defineConfig({
- entry: entries,
- format: ["esm"],
- dts: true,
- clean: true,
- treeshake: true,
- splitting: false,
- external: ["react", "react-dom"],
- esbuildOptions(options) {
- options.jsx = "automatic";
- },
+ entry: entries,
+ format: ['esm'],
+ dts: true,
+ clean: true,
+ treeshake: true,
+ splitting: false,
+ external: ['react', 'react-dom'],
+ esbuildOptions(options) {
+ options.jsx = 'automatic';
+ },
});
diff --git a/packages/ui/vitest.config.ts b/packages/ui/vitest.config.ts
index 8273258..266ee43 100644
--- a/packages/ui/vitest.config.ts
+++ b/packages/ui/vitest.config.ts
@@ -1,9 +1,9 @@
-import { defineConfig } from "vitest/config";
+import { defineConfig } from 'vitest/config';
export default defineConfig({
- test: {
- globals: true,
- environment: "happy-dom",
- setupFiles: ["./src/test-setup.ts"],
- },
+ test: {
+ globals: true,
+ environment: 'happy-dom',
+ setupFiles: ['./src/test-setup.ts'],
+ },
});
diff --git a/packages/utils/package.json b/packages/utils/package.json
index b26da14..da0ddc2 100644
--- a/packages/utils/package.json
+++ b/packages/utils/package.json
@@ -12,9 +12,7 @@
"default": "./dist/format.js"
}
},
- "files": [
- "dist"
- ],
+ "files": ["dist"],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
diff --git a/packages/utils/src/cn.test.ts b/packages/utils/src/cn.test.ts
index c89f53d..12d11bd 100644
--- a/packages/utils/src/cn.test.ts
+++ b/packages/utils/src/cn.test.ts
@@ -1,4 +1,4 @@
-import { describe, it, expect } from 'vitest';
+import { describe, expect, it } from 'vitest';
import { cn } from './cn';
describe('cn', () => {
diff --git a/packages/utils/src/cn.ts b/packages/utils/src/cn.ts
index a7c2663..edac9f2 100644
--- a/packages/utils/src/cn.ts
+++ b/packages/utils/src/cn.ts
@@ -1,4 +1,4 @@
-import { clsx, type ClassValue } from 'clsx';
+import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]): string {
diff --git a/reset.d.ts b/reset.d.ts
index 80e93c2..7b8d5d6 100644
--- a/reset.d.ts
+++ b/reset.d.ts
@@ -1,3 +1,3 @@
// Enable @total-typescript/ts-reset for better built-in types
// This provides improved type definitions for common JavaScript operations
-import "@total-typescript/ts-reset";
+import '@total-typescript/ts-reset';
diff --git a/turbo.json b/turbo.json
index 0506f84..8f48a83 100644
--- a/turbo.json
+++ b/turbo.json
@@ -1,9 +1,6 @@
{
"$schema": "https://turbo.build/schema.json",
- "globalDependencies": [
- "**/.env.*local",
- "tsconfig.json"
- ],
+ "globalDependencies": ["**/.env.*local", "tsconfig.json"],
"tasks": {
"build": {
"dependsOn": ["^build"],
diff --git a/vercel.json b/vercel.json
index 3c6afeb..f88823c 100644
--- a/vercel.json
+++ b/vercel.json
@@ -1,12 +1,12 @@
{
- "$schema": "https://openapi.vercel.sh/vercel.json",
- "git": {
- "deploymentEnabled": {
- "main": true
- }
- },
- "github": {
- "silent": false,
- "autoJobCancelation": true
- }
+ "$schema": "https://openapi.vercel.sh/vercel.json",
+ "git": {
+ "deploymentEnabled": {
+ "main": true
+ }
+ },
+ "github": {
+ "silent": false,
+ "autoJobCancelation": true
+ }
}