Unified icon library: - Create src/components/ui/icon.tsx as centralized icon re-export + size constants - Migrate all 20+ files from lucide-react to @phosphor-icons/react - Remove lucide-react from package.json dependencies - Upgrade ESLint lucide-react rule from warn → error Icon mappings applied: CheckIcon → Check, ChevronDown → CaretDown, ChevronUp → CaretUp, ChevronRight → CaretRight, X → X, Search → MagnifyingGlass, Loader2 → SpinnerGap, CornerDownLeft → ArrowElbowDownLeft, Image → Image, Brain → Brain, Terminal → Terminal, Book → Book, Dot → DotOutline, Circle → Circle Files migrated: ui/ (6), ai-elements/ (11), layout/ (3) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
115 lines
3.2 KiB
JavaScript
115 lines
3.2 KiB
JavaScript
import { defineConfig, globalIgnores } from "eslint/config";
|
|
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
import nextTs from "eslint-config-next/typescript";
|
|
|
|
const eslintConfig = defineConfig([
|
|
...nextVitals,
|
|
...nextTs,
|
|
// Override default ignores of eslint-config-next.
|
|
globalIgnores([
|
|
// Default ignores of eslint-config-next:
|
|
".next/**",
|
|
"out/**",
|
|
"build/**",
|
|
"release/**",
|
|
"dist-electron/**",
|
|
"next-env.d.ts",
|
|
// apps/site generated artifacts
|
|
"apps/site/.next/**",
|
|
"apps/site/.source/**",
|
|
]),
|
|
|
|
// ── Governance rules for business components ──
|
|
{
|
|
files: [
|
|
"src/components/settings/**/*.{ts,tsx}",
|
|
"src/components/bridge/**/*.{ts,tsx}",
|
|
"src/components/chat/**/*.{ts,tsx}",
|
|
"src/components/gallery/**/*.{ts,tsx}",
|
|
"src/components/plugins/**/*.{ts,tsx}",
|
|
"src/components/skills/**/*.{ts,tsx}",
|
|
"src/components/project/**/*.{ts,tsx}",
|
|
"src/components/layout/**/*.{ts,tsx}",
|
|
"src/components/cli-tools/**/*.{ts,tsx}",
|
|
"src/app/**/*.{ts,tsx}",
|
|
],
|
|
rules: {
|
|
// Discourage native HTML controls — use ui/ components instead
|
|
"no-restricted-syntax": [
|
|
"warn",
|
|
{
|
|
selector: "JSXOpeningElement[name.name='button']",
|
|
message: "Use <Button> from @/components/ui/button instead of native <button>.",
|
|
},
|
|
{
|
|
selector: "JSXOpeningElement[name.name='input']",
|
|
message: "Use <Input> from @/components/ui/input instead of native <input>.",
|
|
},
|
|
{
|
|
selector: "JSXOpeningElement[name.name='select']",
|
|
message: "Use <Select> from @/components/ui/select instead of native <select>.",
|
|
},
|
|
{
|
|
selector: "JSXOpeningElement[name.name='textarea']",
|
|
message: "Use <Textarea> from @/components/ui/textarea instead of native <textarea>.",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
|
|
// ── Discourage Lucide imports project-wide ──
|
|
{
|
|
files: ["src/**/*.{ts,tsx}"],
|
|
rules: {
|
|
"no-restricted-imports": [
|
|
"error",
|
|
{
|
|
patterns: [
|
|
{
|
|
group: ["lucide-react"],
|
|
message: "Use @phosphor-icons/react instead. See docs/ui-governance.md for mapping.",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
|
|
// ── Component file size limit ──
|
|
{
|
|
files: ["src/components/**/*.{ts,tsx}"],
|
|
ignores: [
|
|
"src/components/ui/**",
|
|
"src/components/ai-elements/**",
|
|
],
|
|
rules: {
|
|
"max-lines": ["warn", { max: 500, skipBlankLines: true, skipComments: true }],
|
|
},
|
|
},
|
|
|
|
// ── Patterns layer: no data logic imports ──
|
|
{
|
|
files: ["src/components/patterns/**/*.{ts,tsx}"],
|
|
rules: {
|
|
"no-restricted-imports": [
|
|
"error",
|
|
{
|
|
patterns: [
|
|
{
|
|
group: ["@/hooks/*", "@/hooks"],
|
|
message: "Pattern components must be pure presentation — no hooks imports.",
|
|
},
|
|
{
|
|
group: ["@/lib/*", "!@/lib/utils"],
|
|
message: "Pattern components must be pure presentation — no lib imports. Use @/lib/utils for cn() only.",
|
|
},
|
|
],
|
|
paths: [],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
]);
|
|
|
|
export default eslintConfig;
|