# quality-ui-feedback > エディタ下部にLintエラーや警告を表示するパネルを作成する。 - Author: nekomasaru - Repository: nekomasaru/LineageDoc - Version: 20260205053519 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-06 - Source: https://github.com/nekomasaru/LineageDoc - Web: https://mule.run/skillshub/@@nekomasaru/LineageDoc~quality-ui-feedback:20260205053519 --- --- name: quality-ui-feedback description: エディタ下部にLintエラーや警告を表示するパネルを作成する。 allowed-tools: [file_edit] meta: domain: frontend role: ui-component tech_stack: react, tailwind-css phase: 2 estimated_time: 40min dependencies: [quality-logic-lint] --- # このスキルでやること Valeによる文体チェック結果(エラー・警告・提案)をエディタ下部のパネルに表示するUIコンポーネントを作成する。 # 設計思想 ## UIレイアウト ``` ┌─────────────────────────────────────────────────────┐ │ [📝 リッチ編集] [ ソースコード] │ ├─────────────────┬───────────────────────────────────┤ │ │ │ │ エディタ │ プレビュー │ │ │ │ ├─────────────────┴───────────────────────────────────┤ │ [⚠️ 3件の指摘] [▲ 折りたたみ] │ ← 問題パネル │ ───────────────────────────────────────────────── │ │ ⛔ 行5: '等々' は使用を避けてください │ │ ⚠️ 行12: '行なう' は '行う' に統一してください │ │ 💡 行18: 見出しには番号を含めることを推奨します │ └─────────────────────────────────────────────────────┘ ``` # 作成するファイル ## `src/components/_features/editor/LintPanel.tsx` ```tsx 'use client'; import { useState } from 'react'; import { AlertCircle, AlertTriangle, Lightbulb, ChevronUp, ChevronDown } from 'lucide-react'; interface LintIssue { line: number; column: number; severity: 'error' | 'warning' | 'suggestion'; message: string; rule: string; } interface LintPanelProps { issues: LintIssue[]; isLoading?: boolean; onIssueClick?: (issue: LintIssue) => void; } export function LintPanel({ issues, isLoading, onIssueClick }: LintPanelProps) { const [isCollapsed, setIsCollapsed] = useState(false); const errorCount = issues.filter(i => i.severity === 'error').length; const warningCount = issues.filter(i => i.severity === 'warning').length; const suggestionCount = issues.filter(i => i.severity === 'suggestion').length; if (issues.length === 0 && !isLoading) { return null; // 問題がなければ表示しない } return (
{/* ヘッダー */}
setIsCollapsed(!isCollapsed)} >
{isLoading ? ( チェック中... ) : ( <> {errorCount > 0 && ( {errorCount} )} {warningCount > 0 && ( {warningCount} )} {suggestionCount > 0 && ( {suggestionCount} )} {issues.length}件の指摘 )}
{/* 問題リスト */} {!isCollapsed && (
{issues.map((issue, index) => ( ))}
)}
); } function getSeverityIcon(severity: string) { switch (severity) { case 'error': return ; case 'warning': return ; default: return ; } } function getSeverityTextColor(severity: string) { switch (severity) { case 'error': return 'text-red-600'; case 'warning': return 'text-amber-600'; default: return 'text-blue-600'; } } ``` # SplitEditorLayoutへの統合 ```tsx // src/components/_features/editor/SplitEditorLayout.tsx import { LintPanel } from './LintPanel'; export function SplitEditorLayout() { const [lintIssues, setLintIssues] = useState([]); const [isLinting, setIsLinting] = useState(false); // 問題クリック時にエディタの該当行へジャンプ const handleIssueClick = (issue: LintIssue) => { // Monaco の場合 if (mode === 'code' && monacoRef.current) { monacoRef.current.revealLineInCenter(issue.line); monacoRef.current.setPosition({ lineNumber: issue.line, column: issue.column }); } }; return (
{/* モード切替タブ */} {/* エディタ + プレビュー */}
{/* 左ペイン */} {/* 右ペイン */}
{/* 問題パネル */}
); } ``` # アクセシビリティ ```tsx