モダンUIコンポーネントの実装パターン
実装ガイドReact、Vue、Angularで共通して使えるUIパターンを解説
セキュアなパスワード入力フィールド。表示/非表示切り替え、強度インジケーター付き
入力中のパスワードの強度を即座に5段階で評価。文字数、大小文字、数字、特殊文字の使用状況を総合的に判定
アイコンクリックで入力内容の表示/非表示を切り替え可能。入力ミスの確認や長いパスワードの入力時に便利
パスワードポリシーの各要件を視覚的に表示。満たした条件は緑色のチェックマークで即座にフィードバック
プロンプト例:
ユーザー新規登録フォーム用のパスワード入力を実装してください。パスワード強度表示、確認用パスワード欄、要件チェックリスト(8文字以上、大小文字混在、数字含む、特殊文字含む)を表示し、すべての要件を満たすまで登録ボタンを無効化します。また、パスワードジェネレーター機能も追加してください。
プロンプト例:
セキュアなパスワード変更フォームを作成してください。現在のパスワード入力欄、新しいパスワード入力欄(強度表示付き)、確認用入力欄を配置し、新しいパスワードが現在のパスワードと同じ場合はエラー表示、過去に使用したパスワードのチェック機能、変更成功時の通知機能を実装してください。
プロンプト例:
パスワード入力と二要素認証を統合したログインフォームを実装してください。初回はパスワードのみ、2回目以降は保存されたデバイスかチェック、新規デバイスの場合は認証コード入力欄を動的に表示、パスワード試行回数制限とアカウントロック機能、「パスワードを忘れた」リンクの実装を含めてください。
import React, { useState, useMemo } from 'react';
import { EyeIcon, EyeSlashIcon, LockClosedIcon } from '@heroicons/react/24/outline';
import { CheckIcon, XMarkIcon } from '@heroicons/react/24/solid';
interface PasswordInputProps {
value?: string;
onChange?: (value: string) => void;
placeholder?: string;
label?: string;
showStrength?: boolean;
showRequirements?: boolean;
disabled?: boolean;
error?: string;
className?: string;
}
// パスワード強度を計算
const calculatePasswordStrength = (password: string): number => {
let strength = 0;
// 長さのチェック
if (password.length >= 8) strength += 1;
if (password.length >= 12) strength += 1;
// 文字種のチェック
if (/[a-z]/.test(password)) strength += 1;
if (/[A-Z]/.test(password)) strength += 1;
if (/[0-9]/.test(password)) strength += 1;
if (/[^a-zA-Z0-9]/.test(password)) strength += 1;
return Math.min(strength, 5);
};
// パスワード要件をチェック
const checkPasswordRequirements = (password: string) => {
return {
length: password.length >= 8,
lowercase: /[a-z]/.test(password),
uppercase: /[A-Z]/.test(password),
number: /[0-9]/.test(password),
special: /[^a-zA-Z0-9]/.test(password)
};
};
export const PasswordInput: React.FC<PasswordInputProps> = ({
value = '',
onChange,
placeholder = 'パスワードを入力',
label,
showStrength = true,
showRequirements = false,
disabled = false,
error,
className = ''
}) => {
const [showPassword, setShowPassword] = useState(false);
const [isFocused, setIsFocused] = useState(false);
const strength = useMemo(() => calculatePasswordStrength(value), [value]);
const requirements = useMemo(() => checkPasswordRequirements(value), [value]);
// 強度に応じた色とラベル
const strengthConfig = {
0: { color: 'bg-gray-300', label: '非常に弱い' },
1: { color: 'bg-red-500', label: '弱い' },
2: { color: 'bg-orange-500', label: '普通' },
3: { color: 'bg-yellow-500', label: '強い' },
4: { color: 'bg-green-500', label: 'とても強い' },
5: { color: 'bg-green-600', label: '非常に強い' }
};
return (
<div className={className}>
{label && (
<label className="block text-sm font-medium text-gray-700 mb-1">
{label}
</label>
)}
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<LockClosedIcon className="h-5 w-5 text-gray-400" />
</div>
<input
type={showPassword ? 'text' : 'password'}
value={value}
onChange={(e) => onChange?.(e.target.value)}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
placeholder={placeholder}
disabled={disabled}
className={`
block w-full pl-10 pr-10 py-2 border rounded-lg
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
${disabled ? 'bg-gray-100 cursor-not-allowed' : ''}
${error ? 'border-red-300' : 'border-gray-300'}
${error && !isFocused ? 'focus:ring-red-500' : ''}
`}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute inset-y-0 right-0 pr-3 flex items-center"
tabIndex={-1}
>
{showPassword ? (
<EyeSlashIcon className="h-5 w-5 text-gray-400 hover:text-gray-600" />
) : (
<EyeIcon className="h-5 w-5 text-gray-400 hover:text-gray-600" />
)}
</button>
</div>
{error && (
<p className="mt-1 text-sm text-red-600">{error}</p>
)}
{showStrength && value && (
<div className="mt-2">
<div className="flex gap-1 mb-1">
{[...Array(5)].map((_, i) => (
<div
key={i}
className={`
h-1 flex-1 rounded-full transition-colors duration-200
${i < strength ? strengthConfig[strength].color : 'bg-gray-200'}
`}
/>
))}
</div>
<p className="text-xs text-gray-600">
強度: {strengthConfig[strength].label}
</p>
</div>
)}
{showRequirements && (isFocused || value) && (
<div className="mt-3 space-y-1">
<p className="text-xs font-medium text-gray-700 mb-1">パスワード要件:</p>
<RequirementItem met={requirements.length} text="8文字以上" />
<RequirementItem met={requirements.lowercase} text="小文字を含む" />
<RequirementItem met={requirements.uppercase} text="大文字を含む" />
<RequirementItem met={requirements.number} text="数字を含む" />
<RequirementItem met={requirements.special} text="特殊文字を含む" />
</div>
)}
</div>
);
};
// 要件項目コンポーネント
const RequirementItem: React.FC<{ met: boolean; text: string }> = ({ met, text }) => (
<div className="flex items-center space-x-2 text-xs">
{met ? (
<CheckIcon className="h-3 w-3 text-green-500" />
) : (
<XMarkIcon className="h-3 w-3 text-gray-300" />
)}
<span className={met ? 'text-green-700' : 'text-gray-500'}>{text}</span>
</div>
);
// 基本的な使用例
import { PasswordInput } from './components/password-input';
import { useState } from 'react';
function SignUpForm() {
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [errors, setErrors] = useState<{ password?: string; confirm?: string }>({});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const newErrors: typeof errors = {};
// パスワードの検証
if (password.length < 8) {
newErrors.password = 'パスワードは8文字以上で入力してください';
}
if (password !== confirmPassword) {
newErrors.confirm = 'パスワードが一致しません';
}
setErrors(newErrors);
if (Object.keys(newErrors).length === 0) {
// フォーム送信処理
console.log('パスワード設定完了');
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4 max-w-md">
<PasswordInput
label="パスワード"
value={password}
onChange={setPassword}
placeholder="8文字以上で入力してください"
showStrength={true}
showRequirements={true}
error={errors.password}
/>
<PasswordInput
label="パスワード(確認)"
value={confirmPassword}
onChange={setConfirmPassword}
placeholder="もう一度入力してください"
showStrength={false}
error={errors.confirm}
/>
<button
type="submit"
className="w-full px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
disabled={!password || !confirmPassword}
>
アカウントを作成
</button>
</form>
);
}