パスワード入力 - Vibe Coding Showcase

パスワード入力

セキュアなパスワード入力フィールド。表示/非表示切り替え、強度インジケーター付き

デザインプレビュー

パスワード入力の特徴

リアルタイム強度判定

入力中のパスワードの強度を即座に5段階で評価。文字数、大小文字、数字、特殊文字の使用状況を総合的に判定

表示/非表示切り替え

アイコンクリックで入力内容の表示/非表示を切り替え可能。入力ミスの確認や長いパスワードの入力時に便利

要件チェックリスト

パスワードポリシーの各要件を視覚的に表示。満たした条件は緑色のチェックマークで即座にフィードバック

AI活用ガイド

新規登録フォーム

プロンプト例:

ユーザー新規登録フォーム用のパスワード入力を実装してください。パスワード強度表示、確認用パスワード欄、要件チェックリスト(8文字以上、大小文字混在、数字含む、特殊文字含む)を表示し、すべての要件を満たすまで登録ボタンを無効化します。また、パスワードジェネレーター機能も追加してください。

パスワード変更フォーム

プロンプト例:

セキュアなパスワード変更フォームを作成してください。現在のパスワード入力欄、新しいパスワード入力欄(強度表示付き)、確認用入力欄を配置し、新しいパスワードが現在のパスワードと同じ場合はエラー表示、過去に使用したパスワードのチェック機能、変更成功時の通知機能を実装してください。

二要素認証統合

プロンプト例:

パスワード入力と二要素認証を統合したログインフォームを実装してください。初回はパスワードのみ、2回目以降は保存されたデバイスかチェック、新規デバイスの場合は認証コード入力欄を動的に表示、パスワード試行回数制限とアカウントロック機能、「パスワードを忘れた」リンクの実装を含めてください。

実装コード

ファイルサイズ: 5.0KB TypeScript

コンポーネント実装

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>
  );
}