mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Add new custom form field for single value selection (#504)
* created BoxSelection component * wip * design changes * integrate with form * refactoring * used inside appearance component * WIP, added space between options * adde new fields to BoxSelection used for chain selection * fixed errors * removed access type option from publish.json * updated component for chain selection * updated for compute type on publish dataset * added component to dockerImageOpions * removed Dotdotdot component from options * remove space * styling updates, fix React warning for terms checkbox Co-authored-by: Norbi <katunanorbert@gmai.com> Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
This commit is contained in:
parent
82acbba5ce
commit
f0ed9f68cb
@ -28,8 +28,8 @@
|
||||
"label": "Docker Image",
|
||||
"placeholder": "e.g. python3.7",
|
||||
"help": "Please select an image to run your algorithm.",
|
||||
"type": "select",
|
||||
"options": ["node:latest", "python:latest", "custom image"],
|
||||
"type": "boxSelection",
|
||||
"options": [],
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
|
@ -34,7 +34,7 @@
|
||||
"name": "access",
|
||||
"label": "Access Type",
|
||||
"help": "Choose how you want your files to be accessible for the specified price.",
|
||||
"type": "select",
|
||||
"type": "boxSelection",
|
||||
"options": ["Download", "Compute"],
|
||||
"required": true
|
||||
},
|
||||
|
@ -4,6 +4,9 @@ import styles from './InputElement.module.css'
|
||||
import { InputProps } from '.'
|
||||
import FilesInput from '../../molecules/FormFields/FilesInput'
|
||||
import Terms from '../../molecules/FormFields/Terms'
|
||||
import BoxSelection, {
|
||||
BoxSelectionOption
|
||||
} from '../../molecules/FormFields/BoxSelection'
|
||||
import Datatoken from '../../molecules/FormFields/Datatoken'
|
||||
import classNames from 'classnames/bind'
|
||||
import AssetSelection, {
|
||||
@ -91,7 +94,7 @@ export default function InputElement({
|
||||
id={slugify(option)}
|
||||
type={type}
|
||||
name={name}
|
||||
checked={props.defaultChecked}
|
||||
defaultChecked={props.defaultChecked}
|
||||
{...props}
|
||||
/>
|
||||
<label className={styles.radioLabel} htmlFor={slugify(option)}>
|
||||
@ -125,6 +128,15 @@ export default function InputElement({
|
||||
return <Datatoken name={name} {...field} {...props} />
|
||||
case 'terms':
|
||||
return <Terms name={name} options={options} {...field} {...props} />
|
||||
case 'boxSelection':
|
||||
return (
|
||||
<BoxSelection
|
||||
name={name}
|
||||
options={(options as unknown) as BoxSelectionOption[]}
|
||||
{...field}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return prefix || postfix ? (
|
||||
<div className={`${prefix ? styles.prefixGroup : styles.postfixGroup}`}>
|
||||
|
57
src/components/molecules/FormFields/BoxSelection.module.css
Normal file
57
src/components/molecules/FormFields/BoxSelection.module.css
Normal file
@ -0,0 +1,57 @@
|
||||
.boxSelectionsWrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0 calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.boxSelectionsWrapper > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.boxSelection {
|
||||
display: block;
|
||||
flex: 1 1 0px;
|
||||
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 2)
|
||||
calc(var(--spacer) / 4) calc(var(--spacer) / 2) !important;
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--color-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: var(--font-weight-bold);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.boxSelectionsWrapper input[type='radio'] {
|
||||
position: fixed;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input[type='radio']:checked + label {
|
||||
color: var(--font-color-text);
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.boxSelection svg {
|
||||
width: var(--font-size-h4);
|
||||
height: var(--font-size-h4);
|
||||
fill: currentColor;
|
||||
margin-bottom: calc(var(--spacer) / 5);
|
||||
}
|
||||
|
||||
.boxSelectionsWrapper label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
color: var(--color-secondary);
|
||||
font-weight: normal;
|
||||
}
|
70
src/components/molecules/FormFields/BoxSelection.tsx
Normal file
70
src/components/molecules/FormFields/BoxSelection.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import React, { ChangeEvent } from 'react'
|
||||
import classNames from 'classnames/bind'
|
||||
import Loader from '../../atoms/Loader'
|
||||
import styles from './BoxSelection.module.css'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export interface BoxSelectionOption {
|
||||
name: string
|
||||
checked: boolean
|
||||
title: JSX.Element | string
|
||||
icon?: JSX.Element
|
||||
text?: JSX.Element | string
|
||||
}
|
||||
|
||||
export default function BoxSelection({
|
||||
name,
|
||||
options,
|
||||
disabled,
|
||||
handleChange,
|
||||
...props
|
||||
}: {
|
||||
name: string
|
||||
options: BoxSelectionOption[]
|
||||
disabled?: boolean
|
||||
handleChange?: (event: ChangeEvent<HTMLInputElement>) => void
|
||||
}): JSX.Element {
|
||||
const styleClassesWrapper = cx({
|
||||
boxSelectionsWrapper: true,
|
||||
[styles.disabled]: disabled
|
||||
})
|
||||
|
||||
const styleClassesInput = cx({
|
||||
input: true,
|
||||
radio: true
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={styleClassesWrapper}>
|
||||
{!options ? (
|
||||
<Loader />
|
||||
) : (
|
||||
options.map((value: BoxSelectionOption) => (
|
||||
<div key={value.name}>
|
||||
<input
|
||||
id={value.name}
|
||||
type="radio"
|
||||
className={styleClassesInput}
|
||||
defaultChecked={value.checked}
|
||||
onChange={(event) => handleChange(event)}
|
||||
{...props}
|
||||
disabled={disabled}
|
||||
value={value.name}
|
||||
name={name}
|
||||
/>
|
||||
<label
|
||||
className={`${styles.boxSelection} ${styles.label}`}
|
||||
htmlFor={value.name}
|
||||
title={value.name}
|
||||
>
|
||||
{value.icon}
|
||||
<span className={styles.title}>{value.title}</span>
|
||||
{value.text}
|
||||
</label>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -30,3 +30,7 @@
|
||||
color: var(--font-color-text);
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.appearances div[class*='boxSelectionsWrapper'] {
|
||||
padding-bottom: calc(var(--spacer) / 8);
|
||||
}
|
||||
|
@ -1,42 +1,44 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import React, { ReactElement, ChangeEvent } from 'react'
|
||||
import { DarkMode } from 'use-dark-mode'
|
||||
import Button from '../../atoms/Button'
|
||||
import FormHelp from '../../atoms/Input/Help'
|
||||
import Label from '../../atoms/Input/Label'
|
||||
import styles from './Appearance.module.css'
|
||||
import { ReactComponent as Moon } from '../../../images/moon.svg'
|
||||
import { ReactComponent as Sun } from '../../../images/sun.svg'
|
||||
|
||||
const buttons = ['Light', 'Dark']
|
||||
import BoxSelection, { BoxSelectionOption } from '../FormFields/BoxSelection'
|
||||
import styles from './Appearance.module.css'
|
||||
|
||||
export default function Appearance({
|
||||
darkMode
|
||||
}: {
|
||||
darkMode: DarkMode
|
||||
}): ReactElement {
|
||||
return (
|
||||
<li>
|
||||
<Label htmlFor="">Appearance</Label>
|
||||
<div className={styles.buttons}>
|
||||
{buttons.map((button) => {
|
||||
const isDark = button === 'Dark'
|
||||
const selected =
|
||||
(isDark && darkMode.value) || (!isDark && !darkMode.value)
|
||||
const options: BoxSelectionOption[] = [
|
||||
{
|
||||
name: 'Light',
|
||||
checked: !darkMode.value,
|
||||
title: 'Light',
|
||||
icon: <Sun />
|
||||
},
|
||||
{
|
||||
name: 'Dark',
|
||||
checked: darkMode.value,
|
||||
title: 'Dark',
|
||||
icon: <Moon />
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={button}
|
||||
className={`${styles.button} ${selected ? styles.selected : ''}`}
|
||||
size="small"
|
||||
style="text"
|
||||
onClick={() => (isDark ? darkMode.enable() : darkMode.disable())}
|
||||
>
|
||||
{isDark ? <Moon /> : <Sun />}
|
||||
{button}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
function handleChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
event.target.value === 'Dark' ? darkMode.enable() : darkMode.disable()
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={styles.appearances}>
|
||||
<Label htmlFor="">Appearance</Label>
|
||||
<BoxSelection
|
||||
options={options}
|
||||
name="appearanceMode"
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
<FormHelp>Defaults to your OS setting, select to override.</FormHelp>
|
||||
</li>
|
||||
)
|
||||
|
@ -17,3 +17,14 @@
|
||||
.selected {
|
||||
composes: selected from './Appearance.module.css';
|
||||
}
|
||||
|
||||
.chains div[class*='boxSelectionsWrapper'] {
|
||||
display: grid;
|
||||
gap: calc(var(--spacer) / 4);
|
||||
grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
|
||||
padding-bottom: calc(var(--spacer) / 8);
|
||||
}
|
||||
|
||||
.chains label[class*='boxSelection'] {
|
||||
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 4) !important;
|
||||
}
|
||||
|
@ -1,53 +1,58 @@
|
||||
import { ConfigHelperConfig } from '@oceanprotocol/lib'
|
||||
import React, { ReactElement } from 'react'
|
||||
import React, { ReactElement, ChangeEvent } from 'react'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../providers/Web3'
|
||||
import { getOceanConfig } from '../../../utils/ocean'
|
||||
import Button from '../../atoms/Button'
|
||||
import FormHelp from '../../atoms/Input/Help'
|
||||
import Label from '../../atoms/Input/Label'
|
||||
import BoxSelection, { BoxSelectionOption } from '../FormFields/BoxSelection'
|
||||
import styles from './Chain.module.css'
|
||||
|
||||
export default function Chain(): ReactElement {
|
||||
const { web3 } = useWeb3()
|
||||
const { config, connect } = useOcean()
|
||||
|
||||
async function connectOcean(networkName: string) {
|
||||
const config = getOceanConfig(networkName)
|
||||
async function connectOcean(event: ChangeEvent<HTMLInputElement>) {
|
||||
const config = getOceanConfig(event.target.value)
|
||||
await connect(config)
|
||||
}
|
||||
|
||||
const chains = [
|
||||
{ name: 'ETH', oceanConfig: 'mainnet', label: 'Mainnet' },
|
||||
{ name: 'Polygon/Matic', oceanConfig: 'polygon', label: 'Mainnet' },
|
||||
{ name: 'Moonbase Alpha', oceanConfig: 'moonbeamalpha', label: 'Testnet' }
|
||||
function isNetworkSelected(oceanConfig: string) {
|
||||
return (config as ConfigHelperConfig).network === oceanConfig
|
||||
}
|
||||
|
||||
const options: BoxSelectionOption[] = [
|
||||
{
|
||||
name: 'mainnet',
|
||||
checked: isNetworkSelected('mainnet'),
|
||||
title: 'ETH',
|
||||
text: 'Mainnet'
|
||||
},
|
||||
{
|
||||
name: 'polygon',
|
||||
checked: isNetworkSelected('polygon'),
|
||||
title: 'Polygon/Matic',
|
||||
text: 'Mainnet'
|
||||
},
|
||||
{
|
||||
name: 'moonbeamalpha',
|
||||
checked: isNetworkSelected('moonbeamalpha'),
|
||||
title: 'Moonbase Alpha',
|
||||
text: 'Testnet'
|
||||
}
|
||||
]
|
||||
|
||||
// TODO: to fully solve https://github.com/oceanprotocol/market/issues/432
|
||||
// there are more considerations for users with a wallet connected (wallet network vs. setting network).
|
||||
// For now, only show the setting for non-wallet users.
|
||||
return !web3 ? (
|
||||
<li>
|
||||
<li className={styles.chains}>
|
||||
<Label htmlFor="">Chain</Label>
|
||||
<div className={styles.buttons}>
|
||||
{chains.map((button) => {
|
||||
const selected =
|
||||
(config as ConfigHelperConfig).network === button.oceanConfig
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={button.name}
|
||||
className={`${styles.button} ${selected ? styles.selected : ''}`}
|
||||
size="small"
|
||||
style="text"
|
||||
onClick={() => connectOcean(button.oceanConfig)}
|
||||
>
|
||||
{button.name}
|
||||
<span>{button.label}</span>
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<BoxSelection
|
||||
options={options}
|
||||
name="chain"
|
||||
handleChange={connectOcean}
|
||||
/>
|
||||
<FormHelp>Switch the data source for the interface.</FormHelp>
|
||||
</li>
|
||||
) : null
|
||||
|
@ -14,7 +14,6 @@ import Button from '../../atoms/Button'
|
||||
import { FormContent, FormFieldProps } from '../../../@types/Form'
|
||||
import { MetadataPublishFormAlgorithm } from '../../../@types/MetaData'
|
||||
import { initialValues as initialValuesAlgorithm } from '../../../models/FormAlgoPublish'
|
||||
|
||||
import stylesIndex from './index.module.css'
|
||||
|
||||
const query = graphql`
|
||||
@ -63,6 +62,24 @@ export default function FormPublish(): ReactElement {
|
||||
initialValues.dockerImage
|
||||
)
|
||||
|
||||
const dockerImageOptions = [
|
||||
{
|
||||
name: 'node:latest',
|
||||
title: 'node:latest',
|
||||
checked: true
|
||||
},
|
||||
{
|
||||
name: 'python:latest',
|
||||
title: 'python:latest',
|
||||
checked: false
|
||||
},
|
||||
{
|
||||
name: 'custom image',
|
||||
title: 'custom image',
|
||||
checked: false
|
||||
}
|
||||
]
|
||||
|
||||
// reset form validation on every mount
|
||||
useEffect(() => {
|
||||
setErrors({})
|
||||
@ -135,6 +152,11 @@ export default function FormPublish(): ReactElement {
|
||||
<Field
|
||||
key={field.name}
|
||||
{...field}
|
||||
options={
|
||||
field.type === 'boxSelection'
|
||||
? dockerImageOptions
|
||||
: field.options
|
||||
}
|
||||
component={Input}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
handleFieldChange(e, field)
|
||||
|
@ -7,6 +7,8 @@ import { FormContent, FormFieldProps } from '../../../@types/Form'
|
||||
import { MetadataPublishFormDataset } from '../../../@types/MetaData'
|
||||
import { initialValues as initialValuesDataset } from '../../../models/FormAlgoPublish'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import { ReactComponent as Download } from '../../../images/download.svg'
|
||||
import { ReactComponent as Compute } from '../../../images/compute.svg'
|
||||
import stylesIndex from './index.module.css'
|
||||
import styles from './FormPublish.module.css'
|
||||
|
||||
@ -61,6 +63,19 @@ export default function FormPublish(): ReactElement {
|
||||
// setSubmitting(false)
|
||||
}, [setErrors, setTouched])
|
||||
|
||||
const accessTypeOptions = [
|
||||
{
|
||||
name: 'Download',
|
||||
title: 'Download',
|
||||
icon: <Download />
|
||||
},
|
||||
{
|
||||
name: 'Compute',
|
||||
title: 'Compute',
|
||||
icon: <Compute />
|
||||
}
|
||||
]
|
||||
|
||||
// Manually handle change events instead of using `handleChange` from Formik.
|
||||
// Workaround for default `validateOnChange` not kicking in
|
||||
function handleFieldChange(
|
||||
@ -94,6 +109,9 @@ export default function FormPublish(): ReactElement {
|
||||
<Field
|
||||
key={field.name}
|
||||
{...field}
|
||||
options={
|
||||
field.type === 'boxSelection' ? accessTypeOptions : field.options
|
||||
}
|
||||
component={Input}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
handleFieldChange(e, field)
|
||||
|
Loading…
Reference in New Issue
Block a user