InputDrawer.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import React, { useState, useEffect, useRef } from 'react';
  2. import { createPortal } from 'react-dom';
  3. import { X, Check } from 'lucide-react';
  4. interface InputDrawerProps {
  5. isOpen: boolean;
  6. onClose: () => void;
  7. title: string;
  8. onSubmit: (value: string) => void;
  9. placeholder?: string;
  10. defaultValue?: string;
  11. submitLabel?: string;
  12. }
  13. export const InputDrawer: React.FC<InputDrawerProps> = ({
  14. isOpen,
  15. onClose,
  16. title,
  17. onSubmit,
  18. placeholder = '',
  19. defaultValue = '',
  20. submitLabel = 'Confirm'
  21. }) => {
  22. const [value, setValue] = useState(defaultValue);
  23. const inputRef = useRef<HTMLInputElement>(null);
  24. useEffect(() => {
  25. if (isOpen) {
  26. setValue(defaultValue);
  27. setTimeout(() => {
  28. inputRef.current?.focus();
  29. }, 50);
  30. }
  31. }, [isOpen, defaultValue]);
  32. const handleSubmit = (e?: React.FormEvent) => {
  33. e?.preventDefault();
  34. if (value.trim()) {
  35. onSubmit(value.trim());
  36. onClose();
  37. }
  38. };
  39. if (!isOpen) return null;
  40. return createPortal(
  41. <div className="fixed inset-0 z-50 overflow-hidden">
  42. <div className="absolute inset-0 bg-black/30 backdrop-blur-sm transition-opacity" onClick={onClose} />
  43. <div className="absolute inset-y-0 right-0 max-w-sm w-full flex pointer-events-none">
  44. {/* pointer-events-none on wrapper, auto on content to allow closing by clicking left side */}
  45. <div className="flex-1 flex flex-col bg-white shadow-xl animate-in slide-in-from-right duration-300 pointer-events-auto">
  46. <div className="flex items-center justify-between px-4 py-3 border-b border-gray-200">
  47. <h2 className="text-lg font-medium text-gray-900">{title}</h2>
  48. <button
  49. onClick={onClose}
  50. className="text-gray-400 hover:text-gray-500 focus:outline-none"
  51. >
  52. <X size={24} />
  53. </button>
  54. </div>
  55. <form onSubmit={handleSubmit} className="p-6 flex-1 flex flex-col">
  56. <label className="block text-sm font-medium text-gray-700 mb-2">
  57. {title}
  58. </label>
  59. <input
  60. ref={inputRef}
  61. type="text"
  62. value={value}
  63. onChange={(e) => setValue(e.target.value)}
  64. placeholder={placeholder}
  65. className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all"
  66. />
  67. <div className="mt-6 flex justify-end gap-3">
  68. <button
  69. type="button"
  70. onClick={onClose}
  71. className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50"
  72. >
  73. Cancel
  74. </button>
  75. <button
  76. type="submit"
  77. disabled={!value.trim()}
  78. className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50 flex items-center gap-2"
  79. >
  80. {submitLabel}
  81. </button>
  82. </div>
  83. </form>
  84. </div>
  85. </div>
  86. </div>,
  87. document.body
  88. );
  89. };