It's possible to create custom fields. A field needs a constructor function that users call to create instances of it in their configuration.
Let's create a custom field to demonstrate.
import {FieldOptions, ScalarField, WithoutLabel} from 'alinea/core'
import {InputLabel, useField} from 'alinea/dashboard'
interface RangeFieldOptions extends FieldOptions<number> {
min?: number
max?: number
}
class RangeField extends ScalarField<number, RangeFieldOptions> {
}
// The constructor function is used to create fields in our schema
// later on. It is usually passed a label and options.
export function range(label: string, options: WithoutLabel<RangeFieldOptions> = {}): RangeField {
return new RangeField({
options: {label, ...options},
view: RangeInput
})
}
interface RangeInputProps {
field: RangeField
}
// To view our field we can create a React component.
// This component can call the useInput hook to receive the
// current value and a method to update it.
function RangeInput({field}: RangeInputProps) {
const {value, mutator, options} = useField(field)
const {min = 0, max = 10} = options
return (
<InputLabel {...options}>
<input
type="range"
min={min} max={max}
value={value}
onChange={e => mutator(Number(e.target.value))}
/>
</InputLabel>
)
}
To use the field in your types later call the constructor function:
import {Config} from 'alinea'
Config.type('My type', {
fields: {
// ...
myRangeField: range('A range field', {
min: 0,
max: 20
})
}
})
Alinea ships with four kinds of field primitives that can be nested inside each other. Under the covers these are created via Y.js allowing them to be fully collaborative.
Scalar values hold a single value that is overwritten anytime it is changed.
A record value stores an object with data. The object values can take any shape.
A list holds an array of values, which must be record shapes. Every item in a list has a type, an automatically generated id and an index for sorting added on.
Rich text is an XML like structure for WYSIWYG data.