apiParams.jsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import { Select } from '@headlessui/react'
  2. import { ChevronDownIcon } from '@heroicons/react/20/solid'
  3. import { TrashIcon } from '@heroicons/react/24/outline'
  4. import { PlusIcon } from '@heroicons/react/24/solid'
  5. import clsx from 'clsx'
  6. import { PluginsIcon } from '../icons/dual-tone/PluginsIcon'
  7. // Recursive component for rendering nested parameters
  8. const ParamRenderer = ({ param, subValue, setParam, path = [], value }) => {
  9. let content
  10. switch (param.type) {
  11. case 'string':
  12. content = (
  13. <input
  14. name={param.name}
  15. type="text"
  16. className="block w-full rounded-md border border-gray-200 px-2 py-1 text-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 disabled:pointer-events-none disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-900/50 dark:text-neutral-300 dark:placeholder-neutral-500"
  17. placeholder={param.placeholder}
  18. onChange={(e) => setParam(path, e.target.value)}
  19. value={value || ''}
  20. />
  21. )
  22. break
  23. case 'number':
  24. content = (
  25. <input
  26. name={param.name}
  27. type="number"
  28. className="block w-full rounded-md border border-gray-200 px-2 py-1 text-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 disabled:pointer-events-none disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-900/50 dark:text-neutral-300 dark:placeholder-neutral-500"
  29. placeholder={param.value}
  30. onChange={(e) => setParam(path, Number.parseInt(e.target.value))}
  31. value={value || ''}
  32. />
  33. )
  34. break
  35. case 'object':
  36. content = (
  37. <div className="-mx-3 ml-2 mt-1 flex flex-col">
  38. {Object.entries(param.value).map(([key, value]) => (
  39. <ParamRenderer
  40. path={[...path, key]}
  41. key={key}
  42. param={{ name: key, ...value, subValue: true }}
  43. subValue={true}
  44. setParam={(path, value) => setParam(path, value)}
  45. />
  46. ))}
  47. </div>
  48. )
  49. break
  50. case 'array':
  51. content = (
  52. <div className="m-0 flex flex-col gap-2">
  53. {param.value.map((item, index) => (
  54. <div key={index} className="flex gap-2">
  55. <input
  56. className="block w-full rounded-md border border-gray-200 px-2 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 disabled:pointer-events-none disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-900/50 dark:text-neutral-300 dark:placeholder-neutral-500"
  57. placeholder={param.placeHolder}
  58. onChange={(e) => {
  59. const newValue = param.value
  60. newValue[index] = e.target.value
  61. setParam(path, newValue)
  62. }}
  63. value={item}
  64. />
  65. <TrashIcon
  66. className=" h-6 w-6 cursor-pointer self-center text-gray-500 dark:text-neutral-400"
  67. onClick={() => {
  68. const newValue = param.value
  69. newValue.splice(index, 1)
  70. setParam(path, newValue)
  71. }}
  72. />
  73. </div>
  74. ))}
  75. <PlusIcon
  76. className=" h-6 w-6 cursor-pointer self-end text-gray-500 dark:text-neutral-400"
  77. onClick={() => {
  78. // add item to array
  79. const newValue = param.value
  80. newValue.push('')
  81. setParam(path, newValue)
  82. }}
  83. />
  84. </div>
  85. )
  86. break
  87. case 'boolean':
  88. content = (
  89. <div className="relative flex h-10 w-full">
  90. <Select
  91. onChange={(e) => setParam(path, e.target.value === 'true' ? true : false)}
  92. className={clsx(
  93. 'dark:white block w-full appearance-none rounded-lg border border-black/10 bg-white/5 px-3 py-1.5 text-sm/6 text-black dark:border-white/15 dark:bg-transparent',
  94. 'focus:outline-none data-[focus]:outline-2 data-[focus]:-outline-offset-2 data-[focus]:outline-white/25',
  95. '*:text-black dark:text-white'
  96. )}
  97. >
  98. {!param.required && <option value={''} />}
  99. <option value="true">true</option>
  100. <option value="false">false</option>
  101. </Select>
  102. <ChevronDownIcon
  103. className="group pointer-events-none absolute right-2.5 top-3 my-auto size-4 fill-black/60 dark:fill-white"
  104. aria-hidden="true"
  105. />
  106. </div>
  107. )
  108. break
  109. case 'option':
  110. content = (
  111. <div className="relative flex h-10 w-full">
  112. <Select
  113. onChange={(e) => setParam(path, e.target.value)}
  114. className={clsx(
  115. 'dark:white block w-full appearance-none rounded-lg border border-black/10 bg-white/5 px-3 py-1.5 text-sm/6 text-black dark:border-white/15 dark:bg-transparent',
  116. 'focus:outline-none data-[focus]:outline-2 data-[focus]:-outline-offset-2 data-[focus]:outline-white/25',
  117. '*:text-black dark:text-white'
  118. )}
  119. >
  120. {!param.required && <option value={''} />}
  121. {param.value.map((choice, index) => {
  122. return (
  123. <option key={index} value={choice}>
  124. {choice}
  125. </option>
  126. )
  127. })}
  128. </Select>
  129. <ChevronDownIcon
  130. className="group pointer-events-none absolute right-2.5 top-3 my-auto size-4 fill-black/60 dark:fill-white"
  131. aria-hidden="true"
  132. />
  133. </div>
  134. )
  135. break
  136. case 'arrayKeyValuePair':
  137. content = (
  138. <div div className="flex flex-col gap-2">
  139. <input
  140. name={`${param.name}-key`}
  141. type="text"
  142. className="block w-full rounded-md border border-gray-200 px-2 py-1 text-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 disabled:pointer-events-none disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-900/50 dark:text-neutral-300 dark:placeholder-neutral-500"
  143. placeholder={'key'}
  144. onChange={(e) => {
  145. const newValue = [e.target.value, value ? value[1] : '']
  146. setParam(path, newValue)
  147. }}
  148. value={value ? value[0] : ''}
  149. />
  150. <input
  151. name={`${param.name}-value`}
  152. type="text"
  153. className="block w-full rounded-md border border-gray-200 px-2 py-1 text-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 disabled:pointer-events-none disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-900/50 dark:text-neutral-300 dark:placeholder-neutral-500"
  154. placeholder={'value'}
  155. onChange={(e) => {
  156. const newValue = [value ? value[0] : '', e.target.value]
  157. setParam(path, newValue)
  158. }}
  159. value={value ? value[1] : ''}
  160. />
  161. </div>
  162. )
  163. break
  164. default:
  165. content = <span className="text-sm">{String(param.value)}</span>
  166. }
  167. return (
  168. <div
  169. className={`${
  170. !subValue && 'border-t border-gray-200 py-2 dark:border-neutral-700/50'
  171. } ${param.type === 'object' ? '' : 'flex flex-col gap-2'}`}
  172. >
  173. <div className="px-3">
  174. <label className="text-sm font-medium text-black dark:text-white">
  175. {param.name}
  176. </label>
  177. <span className="ml-2 inline-block text-xs text-gray-500 dark:text-neutral-400">
  178. {param.type}
  179. </span>
  180. {param.required && (
  181. <span className="ml-2 inline-block text-xs text-red-500 dark:text-red-400">
  182. required
  183. </span>
  184. )}
  185. </div>
  186. <div className="px-3 text-xs italic">{param.description}</div>
  187. <div className="px-3 pb-2">{content}</div>
  188. </div>
  189. )
  190. }
  191. const ApiParameterDisplay = ({ params, setParam, body }) => {
  192. return (
  193. <div className="flex w-full flex-col gap-4 rounded-xl border border-gray-200 bg-white py-4 pb-0 dark:border-neutral-700/50 dark:bg-neutral-800/50">
  194. <div className="px-3 text-xs font-semibold uppercase text-gray-500 dark:text-neutral-300">
  195. Body Params
  196. </div>
  197. <div className="flex flex-col">
  198. {params.map((param) => (
  199. <ParamRenderer
  200. value={body[param.name]}
  201. path={[param.name]}
  202. key={param.name}
  203. param={param}
  204. setParam={(path, value) => setParam(path, value)}
  205. />
  206. ))}
  207. </div>
  208. </div>
  209. )
  210. }
  211. export default ApiParameterDisplay