# 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