JsonInput.tsx 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. "use client";
  2. import { useEffect, useState } from "react";
  3. export default function JsonInput({
  4. value,
  5. onChange,
  6. className = "",
  7. ...props
  8. }: {
  9. value: string;
  10. onChange: (v: string) => void;
  11. className?: string;
  12. [key: string]: any;
  13. }) {
  14. const [error, setError] = useState<string | null>(null);
  15. useEffect(() => {
  16. // Auto-format valid JSON on mount or when value changes externally
  17. if (value.trim() !== "") {
  18. try {
  19. const parsed = JSON.parse(value);
  20. const formatted = JSON.stringify(parsed, null, 2);
  21. if (formatted !== value) {
  22. onChange(formatted);
  23. }
  24. setError(null);
  25. } catch (err: any) {
  26. setError(err.message);
  27. }
  28. }
  29. }, [value, onChange]);
  30. function handleInput(e: React.ChangeEvent<HTMLTextAreaElement>) {
  31. const val = e.target.value;
  32. try {
  33. if (val.trim() !== "") {
  34. JSON.parse(val);
  35. }
  36. setError(null);
  37. onChange(val);
  38. } catch (err: any) {
  39. setError(err.message);
  40. onChange(val);
  41. }
  42. }
  43. function handleFormat() {
  44. try {
  45. const formatted = JSON.stringify(JSON.parse(value), null, 2);
  46. setError(null);
  47. onChange(formatted);
  48. } catch (err: any) {
  49. setError("Invalid JSON");
  50. }
  51. }
  52. return (
  53. <div className={className}>
  54. <textarea
  55. className="block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 font-mono text-xs min-h-[200px] bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
  56. value={value}
  57. onChange={handleInput}
  58. spellCheck={false}
  59. rows={10}
  60. {...props}
  61. />
  62. <div className="flex items-center gap-2 mt-1">
  63. <button
  64. type="button"
  65. className="inline-flex items-center rounded-md bg-indigo-600 px-3 py-1 text-xs font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
  66. onClick={handleFormat}
  67. >
  68. Format JSON
  69. </button>
  70. {error && (
  71. <span className="ml-2 text-xs text-red-500 font-medium">{error}</span>
  72. )}
  73. </div>
  74. </div>
  75. );
  76. }