From 37cff0cdf99e55f93e9f9067047395e94199ade7 Mon Sep 17 00:00:00 2001 From: Mateusz Podlasin Date: Thu, 7 Jan 2021 16:22:25 +0100 Subject: [PATCH 1/9] feat: add send file button to TryIt --- .../operations/multipart-formdata-post.ts | 4 ++++ .../src/components/TryIt/ParameterEditor.tsx | 16 ++++++++++++++-- .../src/components/TryIt/parameter-utils.ts | 8 ++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/elements/src/__fixtures__/operations/multipart-formdata-post.ts b/packages/elements/src/__fixtures__/operations/multipart-formdata-post.ts index afc164ec2..8ace5b522 100644 --- a/packages/elements/src/__fixtures__/operations/multipart-formdata-post.ts +++ b/packages/elements/src/__fixtures__/operations/multipart-formdata-post.ts @@ -34,6 +34,10 @@ export const httpOperation: IHttpOperation = { type: 'string', enum: ['a', 'b', 'c'], }, + someFile: { + type: 'string', + format: 'binary' + }, }, required: ['name', 'completed'], }, diff --git a/packages/elements/src/components/TryIt/ParameterEditor.tsx b/packages/elements/src/components/TryIt/ParameterEditor.tsx index fa80e1031..81070048b 100644 --- a/packages/elements/src/components/TryIt/ParameterEditor.tsx +++ b/packages/elements/src/components/TryIt/ParameterEditor.tsx @@ -1,10 +1,11 @@ -import { Flex, Input, Select, Text } from '@stoplight/mosaic'; +import { Button, Flex, Input, Select, Text } from '@stoplight/mosaic'; import * as React from 'react'; import { exampleOptions, getPlaceholderForParameter, parameterOptions, + parameterSupportsFileUpload, ParameterSpec, selectExampleOption, } from './parameter-utils'; @@ -19,6 +20,10 @@ export const ParameterEditor: React.FC = ({ parameter, value, on const parameterValueOptions = parameterOptions(parameter); const examples = exampleOptions(parameter); const selectedExample = examples?.find(e => e.value === value) ?? selectExampleOption; + + const supportsFileUpload = parameterSupportsFileUpload(parameter); + const [files, setFiles] = React.useState(null); + return ( @@ -41,8 +46,9 @@ export const ParameterEditor: React.FC = ({ parameter, value, on placeholder={getPlaceholderForParameter(parameter)} type={parameter.schema?.type === 'number' ? 'number' : 'text'} required - value={value ?? ''} + value={files ? files.map(file => file.name).join(', ') : value ?? ''} onChange={onChange} + disabled={supportsFileUpload} /> {examples && ( setFiles(Array.from(e.currentTarget.files ?? []))} type="file" hidden id="file-upload" /> + + )} )} diff --git a/packages/elements/src/components/TryIt/parameter-utils.ts b/packages/elements/src/components/TryIt/parameter-utils.ts index 688bcca18..0c6591cce 100644 --- a/packages/elements/src/components/TryIt/parameter-utils.ts +++ b/packages/elements/src/components/TryIt/parameter-utils.ts @@ -29,11 +29,19 @@ export function exampleOptions(parameter: ParameterSpec) { : null; } +export function parameterSupportsFileUpload(parameter: ParameterSpec) { + return parameter.schema?.type === 'string' && parameter.schema.format === 'binary'; +} + export function exampleValue(example: INodeExample | INodeExternalExample) { return 'value' in example ? String(example.value) : String(example.externalValue); } export function getPlaceholderForParameter(parameter: ParameterSpec) { + if(parameterSupportsFileUpload(parameter)) { + return 'pick a file'; + } + const defaultOrType = retrieveDefaultFromSchema(parameter) ?? parameter.schema?.type; return defaultOrType !== undefined ? String(defaultOrType) : undefined; } From b03d22360995db75599becb49100288e1dd721e4 Mon Sep 17 00:00:00 2001 From: Mateusz Podlasin Date: Mon, 11 Jan 2021 10:15:03 +0100 Subject: [PATCH 2/9] feat: add file to request body types --- .../components/TryIt/OperationParameters.tsx | 7 +++-- .../src/components/TryIt/ParameterEditor.tsx | 27 ++++++++++++------- .../elements/src/components/TryIt/index.tsx | 6 ++--- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/elements/src/components/TryIt/OperationParameters.tsx b/packages/elements/src/components/TryIt/OperationParameters.tsx index 379bccc0b..a3b548f8f 100644 --- a/packages/elements/src/components/TryIt/OperationParameters.tsx +++ b/packages/elements/src/components/TryIt/OperationParameters.tsx @@ -11,7 +11,7 @@ type OperationParameters = Record<'path' | 'query' | 'headers', readonly Paramet interface OperationParametersProps { operationParameters: OperationParameters; values: Dictionary; - onChangeValues: (newValues: Dictionary) => void; + onChangeValues: (newValues: Dictionary) => void; } export const OperationParameters: React.FC = ({ @@ -21,9 +21,8 @@ export const OperationParameters: React.FC = ({ }) => { const parameters = flattenParameters(operationParameters); - const onChange = (name: string) => (e: React.FormEvent | React.ChangeEvent) => { - const newValue = e.currentTarget.value; - onChangeValues({ ...values, [name]: newValue }); + const onChange = (name: string) => (value: string | File) => { + onChangeValues({ ...values, [name]: value }); }; return ( diff --git a/packages/elements/src/components/TryIt/ParameterEditor.tsx b/packages/elements/src/components/TryIt/ParameterEditor.tsx index 81070048b..c8caa9779 100644 --- a/packages/elements/src/components/TryIt/ParameterEditor.tsx +++ b/packages/elements/src/components/TryIt/ParameterEditor.tsx @@ -1,4 +1,4 @@ -import { Button, Flex, Input, Select, Text } from '@stoplight/mosaic'; +import { Flex, Input, Select, Text } from '@stoplight/mosaic'; import * as React from 'react'; import { @@ -12,8 +12,8 @@ import { interface ParameterProps { parameter: ParameterSpec; - value: string; - onChange: (e: React.FormEvent | React.ChangeEvent) => void; + value: string | File; + onChange: (parameterValue: string | File) => void; } export const ParameterEditor: React.FC = ({ parameter, value, onChange }) => { @@ -22,7 +22,14 @@ export const ParameterEditor: React.FC = ({ parameter, value, on const selectedExample = examples?.find(e => e.value === value) ?? selectExampleOption; const supportsFileUpload = parameterSupportsFileUpload(parameter); - const [files, setFiles] = React.useState(null); + + const handleFileChange = (event: React.ChangeEvent) => { + const file = event.currentTarget.files?.[0]; + + if (file === undefined) return; + + onChange(file); + } return ( @@ -33,8 +40,8 @@ export const ParameterEditor: React.FC = ({ parameter, value, on flexGrow aria-label={parameter.name} options={parameterValueOptions} - value={value} - onChange={onChange} + value={typeof value === 'string' ? value : ''} + onChange={e => onChange(e.currentTarget.value)} /> ) : ( @@ -46,8 +53,8 @@ export const ParameterEditor: React.FC = ({ parameter, value, on placeholder={getPlaceholderForParameter(parameter)} type={parameter.schema?.type === 'number' ? 'number' : 'text'} required - value={files ? files.map(file => file.name).join(', ') : value ?? ''} - onChange={onChange} + value={typeof value === 'string' ? value : value.name} + onChange={e => onChange(e.currentTarget.value)} disabled={supportsFileUpload} /> {examples && ( @@ -56,13 +63,13 @@ export const ParameterEditor: React.FC = ({ parameter, value, on flexGrow value={selectedExample.value} options={examples} - onChange={onChange} + onChange={e => onChange(e.currentTarget.value)} /> )} {supportsFileUpload && (
- setFiles(Array.from(e.currentTarget.files ?? []))} type="file" hidden id="file-upload" /> +
)}
diff --git a/packages/elements/src/components/TryIt/index.tsx b/packages/elements/src/components/TryIt/index.tsx index 13bfcbbc6..2ea549dbd 100644 --- a/packages/elements/src/components/TryIt/index.tsx +++ b/packages/elements/src/components/TryIt/index.tsx @@ -36,7 +36,7 @@ export const TryIt: React.FC = ({ httpOperation }) => { }; const allParameters = flattenParameters(operationParameters); - const [parameterValues, setParameterValues] = React.useState>( + const [parameterValues, setParameterValues] = React.useState>( initialParameterValues(allParameters), ); @@ -127,7 +127,7 @@ const ResponseError: React.FC<{ state: ErrorState }> = ({ state }) => ( interface BuildFetchRequestInput { httpOperation: IHttpOperation; - parameterValues: Dictionary; + parameterValues: Dictionary; bodyParameterValues?: Dictionary; } @@ -140,7 +140,7 @@ function buildFetchRequest({ const queryParams = httpOperation.request?.query ?.map(param => [param.name, parameterValues[param.name] ?? '']) - .filter(([_, value]) => value.length > 0); + .filter(([_, value]) => typeof value !== 'string' || value.length > 0); const expandedPath = uriExpand(httpOperation.path, parameterValues); const url = new URL(server + expandedPath); From 9abf5113f7548db461f941285eb22045f50cf36c Mon Sep 17 00:00:00 2001 From: Mateusz Podlasin Date: Tue, 12 Jan 2021 14:03:47 +0100 Subject: [PATCH 3/9] feat: cleanup operation parameters and alter only body paramteres types --- .../src/components/TryIt/FormDataBody.tsx | 7 +++-- .../components/TryIt/OperationParameters.tsx | 4 +-- .../src/components/TryIt/ParameterEditor.tsx | 28 +++++++++++++++---- .../elements/src/components/TryIt/index.tsx | 8 +++--- .../components/TryIt/request-body-utils.ts | 10 ++++--- 5 files changed, 39 insertions(+), 18 deletions(-) diff --git a/packages/elements/src/components/TryIt/FormDataBody.tsx b/packages/elements/src/components/TryIt/FormDataBody.tsx index c6ac9e5fc..a2a480fc8 100644 --- a/packages/elements/src/components/TryIt/FormDataBody.tsx +++ b/packages/elements/src/components/TryIt/FormDataBody.tsx @@ -7,8 +7,8 @@ import { ParameterEditor } from './ParameterEditor'; interface FormDataBodyProps { specification: IMediaTypeContent; - values: Dictionary; - onChangeValues: (newValues: Dictionary) => void; + values: Dictionary; + onChangeValues: (newValues: Dictionary) => void; } export const FormDataBody: React.FC = ({ specification, values, onChangeValues }) => { @@ -31,10 +31,11 @@ export const FormDataBody: React.FC = ({ specification, value {Object.entries(parameters).map(([name, schema]) => ( onChangeValues({ ...values, [name]: e.currentTarget.value })} + onChange={newValue => onChangeValues({ ...values, [name]: newValue })} /> ))} diff --git a/packages/elements/src/components/TryIt/OperationParameters.tsx b/packages/elements/src/components/TryIt/OperationParameters.tsx index a3b548f8f..8ad05483f 100644 --- a/packages/elements/src/components/TryIt/OperationParameters.tsx +++ b/packages/elements/src/components/TryIt/OperationParameters.tsx @@ -11,7 +11,7 @@ type OperationParameters = Record<'path' | 'query' | 'headers', readonly Paramet interface OperationParametersProps { operationParameters: OperationParameters; values: Dictionary; - onChangeValues: (newValues: Dictionary) => void; + onChangeValues: (newValues: Dictionary) => void; } export const OperationParameters: React.FC = ({ @@ -21,7 +21,7 @@ export const OperationParameters: React.FC = ({ }) => { const parameters = flattenParameters(operationParameters); - const onChange = (name: string) => (value: string | File) => { + const onChange = (name: string) => (value: string) => { onChangeValues({ ...values, [name]: value }); }; diff --git a/packages/elements/src/components/TryIt/ParameterEditor.tsx b/packages/elements/src/components/TryIt/ParameterEditor.tsx index c8caa9779..5aa0bfda1 100644 --- a/packages/elements/src/components/TryIt/ParameterEditor.tsx +++ b/packages/elements/src/components/TryIt/ParameterEditor.tsx @@ -10,20 +10,38 @@ import { selectExampleOption, } from './parameter-utils'; -interface ParameterProps { +type ParameterProps = ParameterPropsWithFileUpload | ParameterPropsWithoutFileUpload; + +interface ParameterPropsWithoutFileUpload { + withFileUpload?: false; + parameter: ParameterSpec; + value: string; + onChange: (parameterValue: string) => void; +} + +interface ParameterPropsWithFileUpload { + withFileUpload: true; parameter: ParameterSpec; value: string | File; onChange: (parameterValue: string | File) => void; } -export const ParameterEditor: React.FC = ({ parameter, value, onChange }) => { +export const supportsFileUpload = (props: ParameterProps): props is ParameterPropsWithFileUpload => { + return props.withFileUpload ?? false; +} + +export const ParameterEditor: React.FC = (props) => { + const parameterSupportsFileUpload = supportsFileUpload(props); + const { parameter, value, onChange } = props; const parameterValueOptions = parameterOptions(parameter); const examples = exampleOptions(parameter); const selectedExample = examples?.find(e => e.value === value) ?? selectExampleOption; - const supportsFileUpload = parameterSupportsFileUpload(parameter); - const handleFileChange = (event: React.ChangeEvent) => { + if (!supportsFileUpload(props)) return; + + const onChange = props.onChange; + const file = event.currentTarget.files?.[0]; if (file === undefined) return; @@ -55,7 +73,7 @@ export const ParameterEditor: React.FC = ({ parameter, value, on required value={typeof value === 'string' ? value : value.name} onChange={e => onChange(e.currentTarget.value)} - disabled={supportsFileUpload} + disabled={parameterSupportsFileUpload} /> {examples && ( + : + + +
+ + +
+
+
+ ); +}; diff --git a/packages/elements/src/components/TryIt/FormDataBody.tsx b/packages/elements/src/components/TryIt/FormDataBody.tsx index a2a480fc8..f7f8b6ff0 100644 --- a/packages/elements/src/components/TryIt/FormDataBody.tsx +++ b/packages/elements/src/components/TryIt/FormDataBody.tsx @@ -2,6 +2,8 @@ import { safeStringify } from '@stoplight/json'; import { Panel } from '@stoplight/mosaic'; import { Dictionary, IMediaTypeContent } from '@stoplight/types'; import * as React from 'react'; +import { FileUploadParamterEditor } from './FileUploadParameterEditors'; +import { parameterSupportsFileUpload } from './parameter-utils'; import { ParameterEditor } from './ParameterEditor'; @@ -29,13 +31,22 @@ export const FormDataBody: React.FC = ({ specification, value Body - {Object.entries(parameters).map(([name, schema]) => ( + {Object.entries(parameters) + .map(([name, schema]) => ({ name, schema, examples: schema?.examples })) + .map((parameter) => ( + parameterSupportsFileUpload(parameter) ? + onChangeValues({ ...values, [parameter.name]: newValue })} + /> + : onChangeValues({ ...values, [name]: newValue })} + key={parameter.name} + parameter={parameter} + value={values[parameter.name] as string} + onChange={newValue => onChangeValues({ ...values, [parameter.name]: newValue })} /> ))} diff --git a/packages/elements/src/components/TryIt/ParameterEditor.tsx b/packages/elements/src/components/TryIt/ParameterEditor.tsx index 5aa0bfda1..7f771a89f 100644 --- a/packages/elements/src/components/TryIt/ParameterEditor.tsx +++ b/packages/elements/src/components/TryIt/ParameterEditor.tsx @@ -5,50 +5,21 @@ import { exampleOptions, getPlaceholderForParameter, parameterOptions, - parameterSupportsFileUpload, ParameterSpec, selectExampleOption, } from './parameter-utils'; - -type ParameterProps = ParameterPropsWithFileUpload | ParameterPropsWithoutFileUpload; - -interface ParameterPropsWithoutFileUpload { - withFileUpload?: false; +interface ParameterProps { parameter: ParameterSpec; value: string; onChange: (parameterValue: string) => void; } -interface ParameterPropsWithFileUpload { - withFileUpload: true; - parameter: ParameterSpec; - value: string | File; - onChange: (parameterValue: string | File) => void; -} - -export const supportsFileUpload = (props: ParameterProps): props is ParameterPropsWithFileUpload => { - return props.withFileUpload ?? false; -} - export const ParameterEditor: React.FC = (props) => { - const parameterSupportsFileUpload = supportsFileUpload(props); const { parameter, value, onChange } = props; const parameterValueOptions = parameterOptions(parameter); const examples = exampleOptions(parameter); const selectedExample = examples?.find(e => e.value === value) ?? selectExampleOption; - const handleFileChange = (event: React.ChangeEvent) => { - if (!supportsFileUpload(props)) return; - - const onChange = props.onChange; - - const file = event.currentTarget.files?.[0]; - - if (file === undefined) return; - - onChange(file); - } - return ( @@ -71,9 +42,8 @@ export const ParameterEditor: React.FC = (props) => { placeholder={getPlaceholderForParameter(parameter)} type={parameter.schema?.type === 'number' ? 'number' : 'text'} required - value={typeof value === 'string' ? value : value.name} + value={value} onChange={e => onChange(e.currentTarget.value)} - disabled={parameterSupportsFileUpload} /> {examples && ( - - )} )} diff --git a/packages/elements/src/components/TryIt/parameter-utils.ts b/packages/elements/src/components/TryIt/parameter-utils.ts index 0c6591cce..fe3846c22 100644 --- a/packages/elements/src/components/TryIt/parameter-utils.ts +++ b/packages/elements/src/components/TryIt/parameter-utils.ts @@ -38,10 +38,6 @@ export function exampleValue(example: INodeExample | INodeExternalExample) { } export function getPlaceholderForParameter(parameter: ParameterSpec) { - if(parameterSupportsFileUpload(parameter)) { - return 'pick a file'; - } - const defaultOrType = retrieveDefaultFromSchema(parameter) ?? parameter.schema?.type; return defaultOrType !== undefined ? String(defaultOrType) : undefined; } From 6466869cea9018b36e15b56022c51a6cc4d1a0c9 Mon Sep 17 00:00:00 2001 From: Mateusz Podlasin Date: Tue, 12 Jan 2021 17:00:51 +0100 Subject: [PATCH 5/9] feat: revert unnecessary changes and add small fixes --- .../operations/multipart-formdata-post.ts | 2 +- .../TryIt/FileUploadParameterEditors.tsx | 44 +++++++++---------- .../src/components/TryIt/FormDataBody.tsx | 44 ++++++++++--------- .../components/TryIt/OperationParameters.tsx | 5 ++- .../src/components/TryIt/ParameterEditor.tsx | 15 +++---- .../elements/src/components/TryIt/index.tsx | 2 +- .../components/TryIt/request-body-utils.ts | 16 +++---- 7 files changed, 64 insertions(+), 64 deletions(-) diff --git a/packages/elements/src/__fixtures__/operations/multipart-formdata-post.ts b/packages/elements/src/__fixtures__/operations/multipart-formdata-post.ts index 8ace5b522..c04b07bab 100644 --- a/packages/elements/src/__fixtures__/operations/multipart-formdata-post.ts +++ b/packages/elements/src/__fixtures__/operations/multipart-formdata-post.ts @@ -36,7 +36,7 @@ export const httpOperation: IHttpOperation = { }, someFile: { type: 'string', - format: 'binary' + format: 'binary', }, }, required: ['name', 'completed'], diff --git a/packages/elements/src/components/TryIt/FileUploadParameterEditors.tsx b/packages/elements/src/components/TryIt/FileUploadParameterEditors.tsx index 31501ebd9..aaec2506b 100644 --- a/packages/elements/src/components/TryIt/FileUploadParameterEditors.tsx +++ b/packages/elements/src/components/TryIt/FileUploadParameterEditors.tsx @@ -1,13 +1,9 @@ -import { Flex, Input, Select, Text } from '@stoplight/mosaic'; +import { Flex, Input, Text } from '@stoplight/mosaic'; import * as React from 'react'; import { - exampleOptions, getPlaceholderForParameter, - parameterOptions, - parameterSupportsFileUpload, ParameterSpec, - selectExampleOption, } from './parameter-utils'; interface FileUploadParamterEditorProps { @@ -23,29 +19,31 @@ export const FileUploadParamterEditor: React.FC = if (file === undefined) return; onChange(file); - } + }; return ( : - - -
- - -
-
+ + +
+ + +
+
); }; diff --git a/packages/elements/src/components/TryIt/FormDataBody.tsx b/packages/elements/src/components/TryIt/FormDataBody.tsx index f7f8b6ff0..89da1a91b 100644 --- a/packages/elements/src/components/TryIt/FormDataBody.tsx +++ b/packages/elements/src/components/TryIt/FormDataBody.tsx @@ -1,16 +1,17 @@ import { safeStringify } from '@stoplight/json'; import { Panel } from '@stoplight/mosaic'; -import { Dictionary, IMediaTypeContent } from '@stoplight/types'; +import { IMediaTypeContent } from '@stoplight/types'; import * as React from 'react'; + import { FileUploadParamterEditor } from './FileUploadParameterEditors'; import { parameterSupportsFileUpload } from './parameter-utils'; - import { ParameterEditor } from './ParameterEditor'; +import { BodyParameterValues } from './request-body-utils'; interface FormDataBodyProps { specification: IMediaTypeContent; - values: Dictionary; - onChangeValues: (newValues: Dictionary) => void; + values: BodyParameterValues; + onChangeValues: (newValues: BodyParameterValues) => void; } export const FormDataBody: React.FC = ({ specification, values, onChangeValues }) => { @@ -32,23 +33,24 @@ export const FormDataBody: React.FC = ({ specification, value Body {Object.entries(parameters) - .map(([name, schema]) => ({ name, schema, examples: schema?.examples })) - .map((parameter) => ( - parameterSupportsFileUpload(parameter) ? - onChangeValues({ ...values, [parameter.name]: newValue })} - /> - : - onChangeValues({ ...values, [parameter.name]: newValue })} - /> - ))} + .map(([name, schema]) => ({ name, schema, examples: schema?.examples })) + .map(parameter => + parameterSupportsFileUpload(parameter) ? ( + onChangeValues({ ...values, [parameter.name]: newValue })} + /> + ) : ( + onChangeValues({ ...values, [parameter.name]: e.currentTarget.value })} + /> + ), + )}
); diff --git a/packages/elements/src/components/TryIt/OperationParameters.tsx b/packages/elements/src/components/TryIt/OperationParameters.tsx index 8ad05483f..379bccc0b 100644 --- a/packages/elements/src/components/TryIt/OperationParameters.tsx +++ b/packages/elements/src/components/TryIt/OperationParameters.tsx @@ -21,8 +21,9 @@ export const OperationParameters: React.FC = ({ }) => { const parameters = flattenParameters(operationParameters); - const onChange = (name: string) => (value: string) => { - onChangeValues({ ...values, [name]: value }); + const onChange = (name: string) => (e: React.FormEvent | React.ChangeEvent) => { + const newValue = e.currentTarget.value; + onChangeValues({ ...values, [name]: newValue }); }; return ( diff --git a/packages/elements/src/components/TryIt/ParameterEditor.tsx b/packages/elements/src/components/TryIt/ParameterEditor.tsx index 7f771a89f..bb467994f 100644 --- a/packages/elements/src/components/TryIt/ParameterEditor.tsx +++ b/packages/elements/src/components/TryIt/ParameterEditor.tsx @@ -8,18 +8,17 @@ import { ParameterSpec, selectExampleOption, } from './parameter-utils'; + interface ParameterProps { parameter: ParameterSpec; value: string; - onChange: (parameterValue: string) => void; + onChange: (e: React.FormEvent | React.ChangeEvent) => void; } -export const ParameterEditor: React.FC = (props) => { - const { parameter, value, onChange } = props; +export const ParameterEditor: React.FC = ({ parameter, value, onChange }) => { const parameterValueOptions = parameterOptions(parameter); const examples = exampleOptions(parameter); const selectedExample = examples?.find(e => e.value === value) ?? selectExampleOption; - return ( @@ -29,8 +28,8 @@ export const ParameterEditor: React.FC = (props) => { flexGrow aria-label={parameter.name} options={parameterValueOptions} - value={typeof value === 'string' ? value : ''} - onChange={e => onChange(e.currentTarget.value)} + value={value} + onChange={onChange} /> ) : ( @@ -43,7 +42,7 @@ export const ParameterEditor: React.FC = (props) => { type={parameter.schema?.type === 'number' ? 'number' : 'text'} required value={value} - onChange={e => onChange(e.currentTarget.value)} + onChange={onChange} /> {examples && ( : - + = placeholder={getPlaceholderForParameter(parameter)} type="text" required - value={value.name} + value={value?.name ?? ''} disabled /> + {value && ( + + )}