@remoteoss/json-schema-form
Version:
Headless UI form powered by JSON Schemas
2,148 lines (2,055 loc) • 54.7 kB
JavaScript
// -------------------------------------
// ----------- Inputs Schema -----------
// -------------------------------------
export const mockTextInput = {
title: 'Username',
description: 'Your username (max 10 characters)',
maxLength: 10,
'x-jsf-presentation': {
inputType: 'text',
maskSecret: 2,
},
type: 'string',
};
export const mockTextInputDeprecated = {
title: 'Username',
description: 'Your username (max 10 characters)',
maxLength: 10,
'x-jsf-presentation': {
inputType: 'text',
maskSecret: 2,
},
type: 'string',
};
export const mockTextareaInput = {
title: 'Comment',
description: 'Explain how was the organization of the event.',
'x-jsf-presentation': {
inputType: 'textarea',
placeholder: 'Leave your comment...',
},
maximum: 250,
type: 'string',
};
export const mockNumberInput = {
title: 'Tabs',
description: 'How many open tabs do you have?',
'x-jsf-presentation': {
inputType: 'number',
},
minimum: 1,
maximum: 10,
type: 'number',
};
export const schemaInputTypeNumberZeroMaximum = {
properties: {
tabs: {
title: 'Tabs',
description: 'How many open tabs do you have?',
'x-jsf-presentation': {
inputType: 'number',
},
minimum: -100,
maximum: 0,
type: 'number',
},
},
};
export const schemaInputTypeIntegerNumber = {
properties: {
tabs: {
title: 'Tabs',
description: 'How many open tabs do you have?',
'x-jsf-presentation': {
inputType: 'number',
},
minimum: 1,
maximum: 10,
type: 'integer',
},
},
};
export const mockNumberInputWithPercentage = {
title: 'Shares',
description: 'What % of shares do you own?',
'x-jsf-presentation': {
inputType: 'number',
percentage: true,
},
minimum: 1,
maximum: 100,
type: 'number',
};
export const mockNumberInputWithPercentageAndCustomRange = {
...mockNumberInputWithPercentage,
minimum: 50,
maximum: 70,
};
export const mockTextPatternInput = {
...mockTextInput,
maxLength: 11,
pattern: '^[0-9]{3}-[0-9]{2}-(?!0{4})[0-9]{4}$',
};
export const mockTextMaxLengthInput = {
...mockTextInput,
maxLength: 10,
};
export const mockRadioInputDeprecated = {
title: 'Has siblings',
description: 'Do you have any siblings?',
type: 'string',
enum: ['yes', 'no'],
'x-jsf-presentation': {
inputType: 'radio',
options: [
{
label: 'Yes',
value: 'yes',
},
{
label: 'No',
value: 'no',
},
],
},
};
export const mockRadioInputString = {
title: 'Has siblings',
description: 'Do you have any siblings?',
oneOf: [
{
const: 'yes',
title: 'Yes',
},
{
const: 'no',
title: 'No',
},
],
'x-jsf-presentation': {
inputType: 'radio',
},
type: 'string',
};
export const mockRadioInputBoolean = {
title: 'Over 18',
description: 'Are you over 18 years old?',
oneOf: [
{
title: 'Yes',
const: true,
},
{
title: 'No',
const: false,
},
],
'x-jsf-presentation': {
inputType: 'radio',
},
type: 'boolean',
};
export const mockRadioInputStringY = {
title: 'Has siblings',
description: 'Do you have any siblings?',
oneOf: [
{
title: 'Yes',
const: 'true',
},
{
title: 'No',
const: 'false',
},
],
'x-jsf-presentation': {
inputType: 'radio',
},
type: 'string',
};
export const mockRadioInputNumber = {
title: 'Number of siblings',
description: 'How many siblings do you have?',
oneOf: [
{
title: 'One',
const: 1,
},
{
title: 'Two',
const: 2,
},
{
title: 'Three',
const: 3,
},
],
'x-jsf-presentation': {
inputType: 'radio',
},
type: 'number',
};
export const mockRadioCardExpandableInput = {
title: 'Experience level',
description:
'Please select the experience level that aligns with this role based on the job description (not the employees overall experience)',
oneOf: [
{
const: 'junior',
title: 'Junior level',
description:
'Entry level employees who perform tasks under the supervision of a more experienced employee.',
},
{
const: 'mid',
title: 'Mid level',
description:
'Employees who perform tasks with a good degree of autonomy and/or with coordination and control functions.',
},
{
const: 'senior',
title: 'Senior level',
description:
'Employees who perform tasks with a high degree of autonomy and/or with coordination and control functions.',
},
],
'x-jsf-presentation': {
inputType: 'radio',
variant: 'card-expandable',
},
type: 'string',
};
export const mockRadioCardInput = {
title: 'Payment method',
description: 'Chose how you want to be paid',
oneOf: [
{
const: 'cc',
title: 'Credit Card',
description: 'Plastic money, which is still money',
},
{
const: 'cash',
title: 'Cash',
description: 'Rules Everything Around Me',
},
],
'x-jsf-presentation': {
inputType: 'radio',
variant: 'card',
},
type: 'string',
};
export const mockSelectInputSoloDeprecated = {
title: 'Benefits (solo)',
description: 'Life Insurance',
items: {
enum: ['Medical Insurance, Health Insurance', 'Travel Bonus'],
},
'x-jsf-presentation': {
inputType: 'select',
options: [
{
label: 'Medical Insurance',
value: 'Medical Insurance',
},
{
label: 'Health Insurance',
value: 'Health Insurance',
},
{
label: 'Travel Bonus',
value: 'Travel Bonus',
disabled: true,
},
],
placeholder: 'Select...',
},
};
export const mockSelectInputMultipleDeprecated = {
...mockSelectInputSoloDeprecated,
title: 'Benefits (multiple)',
type: 'array',
};
export const mockSelectInputSolo = {
title: 'Browsers (solo)',
description: 'This solo select also includes a disabled option.',
type: 'string',
oneOf: [
{
const: 'chr',
title: 'Chrome',
},
{
const: 'ff',
title: 'Firefox',
},
{
const: 'ie',
title: 'Internet Explorer',
disabled: true,
},
],
'x-jsf-presentation': {
inputType: 'select',
},
};
export const mockSelectInputMultiple = {
title: 'Browsers (multiple)',
description: 'This multi-select also includes a disabled option.',
type: 'array',
uniqueItems: true,
items: {
anyOf: [
{
const: 'chr',
title: 'Chrome',
},
{
const: 'ff',
title: 'Firefox',
},
{
value: 'ie',
label: 'Internet Explorer',
disabled: true,
},
],
},
'x-jsf-presentation': {
inputType: 'select',
},
};
export const mockSelectInputMultipleOptional = {
...mockSelectInputMultiple,
title: 'Browsers (multiple) (optional)',
description: 'This optional multi-select also includes a disabled option.',
type: ['array', 'null'],
};
export const mockFileInput = {
description: 'File Input Description',
'x-jsf-presentation': {
inputType: 'file',
accept: '.png,.jpg,.jpeg,.pdf',
maxFileSize: 20480,
fileDownload: 'http://some.domain.com/file-name.pdf',
fileName: 'My File',
},
title: 'File Input',
type: 'string',
};
export const mockFileInputWithSkippable = {
...mockFileInput,
'x-jsf-presentation': {
...mockFileInput['x-jsf-presentation'],
skippableLabel: 'This document does not apply to my profile.',
},
};
export const mockFileInputWithAllowLaterUpload = {
...mockFileInput,
title: 'File skippable',
'x-jsf-presentation': {
...mockFileInput['x-jsf-presentation'],
skippableLabel: "I don't have this document yet.",
description:
'File input, with attribute "allowLaterUpload". This tells the API to mark the file as skipped so that it is asked again later in the process.',
allowLaterUpload: true,
},
};
export const mockFieldset = {
title: 'Fieldset title',
description: 'Fieldset description',
'x-jsf-presentation': {
inputType: 'fieldset',
},
properties: {
username: mockTextInput,
tabs: mockNumberInput,
},
required: ['username'],
type: 'object',
};
export const mockFocusedFieldset = {
title: 'Focused fieldset title',
description: 'Focused fieldset description',
'x-jsf-presentation': {
inputType: 'fieldset',
variant: 'focused',
},
properties: {
username: mockTextInput,
tabs: mockNumberInput,
},
required: ['username'],
type: 'object',
};
export const mockNestedFieldset = {
title: 'Nested fieldset title',
description: 'Nested fieldset description',
'x-jsf-presentation': {
inputType: 'fieldset',
},
properties: {
innerFieldset: mockFieldset,
},
type: 'object',
};
export const mockGroupArrayInput = {
items: {
properties: {
birthdate: {
description: 'Enter your child’s date of birth',
format: 'date',
'x-jsf-presentation': {
inputType: 'date',
},
title: 'Child Birthdate',
type: 'string',
maxLength: 255,
},
full_name: {
description: 'Enter your child’s full name',
'x-jsf-presentation': {
inputType: 'text',
},
title: 'Child Full Name',
type: 'string',
maxLength: 255,
},
sex: {
description:
'We know sex is non-binary but for insurance and payroll purposes, we need to collect this information.',
'x-jsf-presentation': {
inputType: 'radio',
},
oneOf: [
{
const: 'male',
title: 'Male',
},
{
const: 'female',
title: 'Female',
},
],
title: 'Child Sex',
type: 'string',
},
},
'x-jsf-order': ['full_name', 'birthdate', 'sex'],
required: ['full_name', 'birthdate', 'sex'],
type: 'object',
},
'x-jsf-presentation': {
inputType: 'group-array',
addFieldText: 'Add new field',
},
title: 'Child details',
description: 'Add the dependents you claim below',
type: 'array',
};
const simpleGroupArrayInput = {
items: {
properties: {
full_name: {
description: 'Enter your child’s full name',
'x-jsf-presentation': {
inputType: 'text',
},
title: 'Child Full Name',
type: 'string',
maxLength: 255,
},
},
required: ['full_name'],
type: 'object',
},
'x-jsf-presentation': {
inputType: 'group-array',
},
title: 'Child names',
description: 'Add the dependents names',
type: 'array',
};
export const mockOptionalGroupArrayInput = {
...mockGroupArrayInput,
title: 'Child details (optional)',
description:
'This is an optional group-array. For a better UX, this Component asks a Yes/No question before allowing to add new field entries.',
};
export const mockEmailInput = {
title: 'Email address',
description: 'Enter your email address',
maxLength: 255,
format: 'email',
'x-jsf-presentation': {
inputType: 'email',
},
type: 'string',
};
export const mockCheckboxInput = {
const: 'Permanent',
description: 'I acknowledge that all employees in France will be hired on indefinite contracts.',
'x-jsf-presentation': {
inputType: 'checkbox',
},
title: 'Contract duration',
type: 'string',
};
export const mockTelWithPattern = {
properties: {
phone_number: {
title: 'Phone number',
description: 'Enter your telephone number',
type: 'string',
'x-jsf-presentation': {
inputType: 'tel',
},
oneOf: [
{
title: 'Portugal',
pattern: '^(\\+351)[0-9]{9,}$',
'x-jsf-presentation': { meta: { countryCode: '351' } },
},
{
title: 'United Kingdom (UK)',
pattern: '^(\\+44)[0-9]{1,}$',
'x-jsf-presentation': { meta: { countryCode: '44' } },
},
{
title: 'Bolivia',
pattern: '^(\\+591)[0-9]{9,}$',
'x-jsf-presentation': { meta: { countryCode: '591' } },
},
{
title: 'Canada',
pattern: '^(\\+1)(206|224)[0-9]{1,}$',
'x-jsf-presentation': { meta: { countryCode: '1' } },
},
{
title: 'United States',
pattern: '^(\\+1)[0-9]{1,}$',
'x-jsf-presentation': { meta: { countryCode: '1' } },
},
],
},
},
};
/**
* Compose a schema with lower chance of human error
* @param {Object} schema version
* @returns {Object} A JSON schema
* @example
* JSONSchemaBuilder().addInput({
username: mockTextInput,
})
.build();
*/
/**
* @deprecated in favor of normal JSON schema.
* Why? This adds extra complexity to read and to
* copy-paste into the Playground, validators, etc
* */
export function JSONSchemaBuilder() {
return {
addInput: function addInput(input) {
this.properties = {
...this.properties,
...input,
};
return this;
},
setRequiredFields: function setRequiredFields(required) {
this.requiredFields = required;
return this;
},
setOrder: function setOrder(order) {
this['x-jsf-order'] = order;
return this;
},
addAnyOf: function addAnyOf(items) {
this.anyOf = items;
return this;
},
addAllOf: function addAllOf(items) {
this.allOf = items;
return this;
},
addCondition: function addCondition(ifCondition, thenBranch, elseBranch) {
this.if = ifCondition;
this.then = thenBranch;
this.else = elseBranch;
return this;
},
build: function build() {
return {
type: 'object',
additionalProperties: false,
properties: this.properties,
...(this['x-jsf-order'] ? { 'x-jsf-order': this['x-jsf-order'] } : {}),
required: this.requiredFields || [],
anyOf: this.anyOf,
allOf: this.allOf,
if: this.if,
then: this.then,
else: this.else,
};
},
};
}
// -------------------------------------
// --------- Schemas pre-built ---------
// -------------------------------------
export const schemaWithoutInputTypes = {
properties: {
a_string: {
title: 'A string -> text',
type: 'string',
},
a_string_oneOf: {
title: 'A string with oneOf -> radio',
type: 'string',
oneOf: [
{ const: 'yes', title: 'Yes' },
{ const: 'no', title: 'No' },
],
},
a_string_email: {
title: 'A string with format:email -> email',
type: 'string',
format: 'email',
},
a_string_date: {
title: 'A string with format:email -> date',
type: 'string',
format: 'date',
},
a_string_file: {
title: 'A string with format:data-url -> file',
type: 'string',
format: 'data-url',
},
a_number: {
title: 'A number -> number',
type: 'number',
},
a_integer: {
title: 'A integer -> number',
type: 'integer',
},
a_boolean: {
title: 'A boolean -> checkbox',
type: 'boolean',
},
a_object: {
title: 'An object -> fieldset',
type: 'object',
properties: {
foo: { type: 'string' },
bar: { type: 'string' },
},
},
a_array_items: {
title: 'An array items.anyOf -> select',
type: 'array',
items: {
anyOf: [
{
const: 'chr',
title: 'Chrome',
},
{
const: 'ff',
title: 'Firefox',
},
{
value: 'ie',
label: 'Internet Explorer',
},
],
},
},
a_array_properties: {
title: 'An array items.properties -> group-array',
items: {
properties: {
role: { title: 'Role', type: 'string' },
years: { title: 'Years', type: 'number' },
},
},
type: 'array',
},
a_void: {
title: 'A void -> text',
description: 'Given no type, returns text',
},
},
required: ['a_array_properties'],
};
export const schemaWithoutTypes = {
properties: {
default: {
title: 'Default -> text',
},
with_oneOf: {
title: 'With oneOf -> radio',
oneOf: [
{ const: 'yes', title: 'Yes' },
{ const: 'no', title: 'No' },
],
},
with_email: {
title: 'With format:email -> email',
format: 'email',
},
with_object: {
title: 'With properties -> fieldset',
properties: {
foo: {},
bar: {},
},
},
with_items_anyOf: {
title: 'With items.anyOf -> select',
items: {
anyOf: [
{
const: 'chr',
title: 'Chrome',
},
{
const: 'ff',
title: 'Firefox',
},
{
value: 'ie',
label: 'Internet Explorer',
},
],
},
},
with_items_properties: {
title: 'With items.properties -> group-array',
items: {
properties: {
role: { title: 'Role' },
years: { title: 'Years' },
},
},
},
},
};
export const schemaInputTypeText = {
properties: {
username: mockTextInput,
},
required: ['username'],
};
export const schemaInputWithStatement = {
properties: {
bonus: {
title: 'Bonus',
'x-jsf-presentation': {
inputType: 'text',
statement: {
description: 'This is a custom statement message.',
inputType: 'statement',
severity: 'info',
},
},
},
role: {
title: 'Role',
'x-jsf-presentation': {
inputType: 'text',
statement: {
description: 'This is another statement message, but more severe.',
inputType: 'statement',
severity: 'warning',
},
},
},
},
};
export const schemaHiddenInputWithStatement = {
properties: {
a_field_statement: {
type: 'null',
title: 'Company statement',
'x-jsf-presentation': {
inputType: 'hidden',
statement: {
title: 'Important statement',
description:
"This statement message will be shown at all times, irrespective of this field's visibility.",
inputType: 'statement',
severity: 'warning',
},
},
},
},
};
export const schemaInputWithExtra = {
properties: {
bonus: {
title: 'Bonus',
'x-jsf-presentation': {
inputType: 'text',
description: 'Remote lives around <strong>core values</strong> across the company.',
extra: `They are:
<ul>
<li>Kindness</li>
<li>Ownership</li>
<li>Excellence</li>
<li>Transparency</li>
<li>Ambition</li>
</ul>
<p>You can read more at <a href="https://www.notion.so/remotecom/Handbook-a3439c6ccaac4d5f8c7515c357345c11" target="_blank">our public handbook</a>. They are also referred as <em>KOETA</em>.</p>
`,
},
},
},
};
export const schemaInputWithCustomDescription = {
properties: {
other: {
title: 'Other',
'x-jsf-presentation': {
inputType: 'text',
description: 'Some other information might still be <strong>relevant</strong> for you.',
},
type: 'string',
},
},
};
export const schemaInputDeprecated = JSONSchemaBuilder()
.addInput({
age_empty: {
title: 'Age (Empty) (Deprecated)',
'x-jsf-presentation': {
inputType: 'number',
description: 'What is your age?',
deprecated: {
description: 'Field deprecated empty.',
},
},
deprecated: true,
readOnly: true,
type: 'number',
},
})
.addInput({
age_filled: {
title: 'Age (Filled) (Deprecated)',
'x-jsf-presentation': {
inputType: 'number',
description: 'What is your age?',
deprecated: {
description: 'Field deprecated and readOnly with a default value.',
},
},
default: 18,
deprecated: true,
readOnly: true,
type: 'number',
},
})
.addInput({
age_editable: {
title: 'Age (Editable) (Deprecated)',
'x-jsf-presentation': {
inputType: 'number',
description: 'What is your age?',
deprecated: {
description: 'Field deprecated but editable.',
},
},
deprecated: true,
type: 'number',
},
})
.build();
/** @deprecated */
export const schemaInputTypeRadioDeprecated = {
properties: {
has_siblings: mockRadioInputDeprecated,
},
required: ['has_siblings'],
};
export const schemaInputTypeRadioString = {
properties: {
has_siblings: mockRadioInputString,
},
required: ['has_siblings'],
};
export const schemaInputTypeRadioStringY = {
properties: {
has_siblings: mockRadioInputStringY,
},
required: ['has_siblings'],
};
export const schemaInputTypeRadioBoolean = {
properties: {
over_18: mockRadioInputBoolean,
},
required: ['over_18'],
};
export const schemaInputTypeRadioNumber = {
properties: {
siblings_count: mockRadioInputNumber,
},
required: ['siblings_count'],
};
export const mockRadioInputOptionalNull = {
title: 'Has car',
oneOf: [
{ const: 'yes', title: 'Yes' },
{ const: 'no', title: 'No' },
// JSF excludes the null option from the field output
// But keeps null as an accepted value
{ const: null, title: 'N/A' },
],
'x-jsf-presentation': { inputType: 'radio' },
type: ['string', 'null'], // Yes, the JSON Schema spec is 'null', not null.
};
export const schemaInputTypeRadioRequiredAndOptional = {
properties: {
has_siblings: mockRadioInputString,
has_car: {
...mockRadioInputOptionalNull,
description: 'Do you have a car? (optional field, check oneOf)',
},
},
required: ['has_siblings'],
};
export const schemaInputRadioOptionalNull = {
properties: {
has_car: mockRadioInputOptionalNull,
},
};
export const schemaInputRadioOptionalConventional = {
properties: {
has_car: {
title: 'Has car',
oneOf: [
{ const: 'yes', title: 'Yes' },
{ const: 'no', title: 'No' },
],
'x-jsf-presentation': { inputType: 'radio' },
type: 'string',
},
},
};
export const schemaInputTypeRadioCard = {
properties: {
experience_level: mockRadioCardExpandableInput,
payment_method: mockRadioCardInput,
},
required: ['experience_level'],
};
export const schemaInputTypeRadioOptionsWithDetails = {
properties: {
health_perks: {
title: 'Health perks',
description:
'This example contains options with more custom details, under the x-jsf-presentation key',
oneOf: [
{
const: 'basic',
title: 'Basic',
'x-jsf-presentation': {
meta: {
displayCost: '$30.00/mo',
},
},
'x-another': 'extra-thing',
},
{
const: 'standard',
title: 'Standard',
'x-jsf-presentation': {
meta: {
displayCost: '$50.00/mo',
},
},
},
],
'x-jsf-presentation': {
inputType: 'radio',
},
type: 'string',
},
},
};
export const schemaInputTypeRadioWithoutOptions = {
properties: {
health_perks: {
title: 'Health perks',
description:
'This example contains options with more custom details, under the x-jsf-presentation key',
'x-jsf-presentation': { inputType: 'radio' },
type: 'string',
},
},
};
/** @deprecated */
export const schemaInputTypeSelectSoloDeprecated = JSONSchemaBuilder()
.addInput({
benefits: mockSelectInputSoloDeprecated,
})
.setRequiredFields(['benefits'])
.build();
export const schemaInputTypeSelectSolo = JSONSchemaBuilder()
.addInput({
browsers: mockSelectInputSolo,
})
.setRequiredFields(['browsers'])
.build();
/** @deprecated */
export const schemaInputTypeSelectMultipleDeprecated = JSONSchemaBuilder()
.addInput({
benefits_multi: mockSelectInputMultipleDeprecated,
})
.setRequiredFields(['benefits_multi'])
.build();
export const schemaInputTypeSelectMultiple = JSONSchemaBuilder()
.addInput({
browsers_multi: mockSelectInputMultiple,
})
.setRequiredFields(['browsers_multi'])
.build();
export const schemaInputTypeSelectMultipleOptional = JSONSchemaBuilder()
.addInput({
browsers_multi_optional: mockSelectInputMultipleOptional,
})
.build();
export const schemaInputTypeNumber = JSONSchemaBuilder()
.addInput({
tabs: mockNumberInput,
})
.setRequiredFields(['tabs'])
.build();
export const schemaInputTypeNumberWithPercentage = JSONSchemaBuilder()
.addInput({
shares: mockNumberInputWithPercentage,
})
.setRequiredFields(['shares'])
.build();
export const schemaInputTypeDate = {
type: 'object',
additionalProperties: false,
properties: {
birthdate: {
'x-jsf-presentation': { inputType: 'date', maxDate: '2022-03-17', minDate: '1922-03-01' },
title: 'Birthdate',
type: 'string',
format: 'date',
},
},
required: ['birthdate'],
};
export const schemaInputTypeEmail = JSONSchemaBuilder()
.addInput({
email_address: mockEmailInput,
})
.setRequiredFields(['email_address'])
.build();
export const schemaInputTypeFile = JSONSchemaBuilder()
.addInput({
a_file: mockFileInput,
})
.setRequiredFields(['a_file'])
.build();
export const schemaInputTypeFileWithSkippable = JSONSchemaBuilder()
.addInput({
b_file: mockFileInputWithSkippable,
})
.build();
export const schemaInputTypeFieldset = {
properties: {
a_fieldset: mockFieldset,
},
required: ['a_fieldset'],
};
export const schemaInputTypeFocusedFieldset = JSONSchemaBuilder()
.addInput({
focused_fieldset: mockFocusedFieldset,
})
.setRequiredFields(['focused_fieldset'])
.build();
export const schemaInputTypeGroupArray = JSONSchemaBuilder()
.addInput({
dependent_details: mockGroupArrayInput,
optional_dependent_details: mockOptionalGroupArrayInput,
})
.setRequiredFields(['dependent_details'])
.build();
export const schemaInputTypeCheckbox = JSONSchemaBuilder()
.addInput({
contract_duration: mockCheckboxInput,
contract_duration_checked: {
...mockCheckboxInput,
title: 'Checkbox (checked by default)',
default: 'Permanent',
},
})
.setRequiredFields(['contract_duration'])
.build();
export const schemaInputTypeCheckboxBooleans = JSONSchemaBuilder()
.addInput({
boolean_empty: {
title: 'It is christmas',
description: 'This one is optional.',
type: 'boolean',
'x-jsf-presentation': {
inputType: 'checkbox',
},
},
boolean_required: {
title: 'Is it rainy (required)',
description: 'This one is required. Is must have const: true to work properly.',
type: 'boolean',
const: true, // Must be explicit that `true` (checked) is the only accepted value.
'x-jsf-presentation': {
inputType: 'checkbox',
},
},
boolean_checked: {
title: 'It is sunny (Default checked)',
description: 'This is checked by default thanks to `default: true`.',
type: 'boolean',
default: true,
'x-jsf-presentation': {
inputType: 'checkbox',
},
},
})
.setRequiredFields(['boolean_required'])
.build();
export const schemaInputTypeCheckboxBooleanConditional = {
type: 'object',
additionalProperties: false,
properties: {
has_pet: {
title: 'Has Pet',
'x-jsf-presentation': {
inputType: 'checkbox',
},
type: 'boolean',
},
pet_is_cat: {
title: 'Pet is cat',
'x-jsf-presentation': {
inputType: 'checkbox',
},
type: 'boolean',
},
},
allOf: [
{
if: {
properties: {
has_pet: { const: true },
},
required: ['has_pet'],
},
then: {},
else: {
properties: {
pet_is_cat: false,
},
},
},
],
};
export const schemaInputTypeNull = {
additionalProperties: false,
type: 'object',
properties: {
name: {
type: 'null',
title: '(Optional) Name',
},
username: {
type: 'string',
title: 'Username',
maxLength: 4,
},
},
required: ['username'],
};
export const schemaCustomErrorMessageByField = {
properties: {
tabs: {
title: 'Tabs',
description: 'How many open tabs do you have?',
'x-jsf-presentation': {
inputType: 'number',
position: 0,
},
minimum: 1,
maximum: 99,
type: 'number',
'x-jsf-errorMessage': {
required: 'This is required.',
minimum: 'You must have at least 1 open tab.',
maximum: 'Your browser does not support more than 99 tabs.',
},
},
},
required: ['tabs'],
};
// The custom error message is below at jsfConfigForErrorMessageSpecificity
export const schemaForErrorMessageSpecificity = {
properties: {
weekday: {
title: 'Weekday',
description: "This text field has the traditional error message. 'Required field'",
type: 'string',
'x-jsf-presentation': { inputType: 'text' },
},
day: {
title: 'Day',
type: 'number',
description:
'The remaining fields are numbers and were customized to say "This cannot be empty." instead of "Required field".',
maximum: 31,
'x-jsf-presentation': { inputType: 'number' },
},
month: {
title: 'Month',
type: 'number',
minimum: 1,
maximum: 12,
'x-jsf-presentation': { inputType: 'number' },
},
year: {
title: 'Year',
description:
"This number field has a custom error message declared in the json schema, which has a higher specificity than the one declared in createHeadlessForm's configuration.",
type: 'number',
'x-jsf-presentation': { inputType: 'number' },
'x-jsf-errorMessage': {
required: 'The year is mandatory.',
},
minimum: 1900,
maximum: 2023,
},
},
required: ['weekday', 'day', 'month', 'year'],
};
export const jsfConfigForErrorMessageSpecificity = {
inputTypes: {
number: {
errorMessage: {
required: 'This cannot be empty.',
},
},
},
};
export const schemaWithPositionDeprecated = JSONSchemaBuilder()
.addInput({
age: {
title: 'age',
'x-jsf-presentation': { inputType: 'number', position: 1 },
},
street: {
title: 'street',
'x-jsf-presentation': { inputType: 'fieldset', position: 2 },
properties: {
line_one: {
title: 'Street',
'x-jsf-presentation': { inputType: 'text', position: 0 },
},
postal_code: {
title: 'Postal code',
'x-jsf-presentation': { inputType: 'text', position: 2 },
},
number: {
title: 'Number',
'x-jsf-presentation': { inputType: 'number', position: 1 },
},
},
},
username: {
title: 'Username',
'x-jsf-presentation': { inputType: 'text', position: 0 },
},
})
.build();
export const schemaWithOrderKeyword = JSONSchemaBuilder()
.addInput({
age: {
title: 'Age',
'x-jsf-presentation': { inputType: 'number' },
},
street: {
title: 'Street',
'x-jsf-presentation': { inputType: 'fieldset' },
properties: {
line_one: {
title: 'Street',
'x-jsf-presentation': { inputType: 'text' },
},
postal_code: {
title: 'Postal code',
'x-jsf-presentation': { inputType: 'text' },
},
number: {
title: 'Number',
'x-jsf-presentation': { inputType: 'number' },
},
},
'x-jsf-order': ['line_one', 'number', 'postal_code'],
},
username: {
title: 'Username',
'x-jsf-presentation': { inputType: 'text' },
},
})
.setOrder(['username', 'age', 'street'])
.build();
export const schemaDynamicValidationConst = {
properties: {
a_fieldset: mockFieldset,
a_group_array: simpleGroupArrayInput,
validate_tabs: {
title: 'Should "Tabs" value be required?',
description: 'Toggle this radio for changing the validation of the fieldset bellow',
oneOf: [
{
title: 'Yes',
value: 'yes',
},
{
title: 'No',
value: 'no',
},
],
'x-jsf-presentation': {
inputType: 'radio',
},
},
mandatory_group_array: {
title: 'Add required group array field',
description: 'Toggle this radio for displaying a mandatory group array field',
oneOf: [
{
title: 'Yes',
value: 'yes',
},
{
title: 'No',
value: 'no',
},
],
'x-jsf-presentation': {
inputType: 'radio',
},
},
},
allOf: [
{
if: {
properties: {
mandatory_group_array: {
const: 'yes',
},
},
required: ['mandatory_group_array'],
},
then: {
required: ['a_group_array'],
},
else: {
properties: {
a_group_array: false,
},
},
},
],
if: {
properties: {
validate_tabs: {
const: 'yes',
},
},
required: ['validate_tabs'],
},
then: {
properties: {
a_fieldset: {
required: ['username', 'tabs'],
},
},
},
required: ['a_fieldset', 'validate_tabs', 'mandatory_group_array'],
'x-jsf-order': ['validate_tabs', 'a_fieldset', 'mandatory_group_array', 'a_group_array'],
};
export const schemaDynamicValidationMinimumMaximum = JSONSchemaBuilder()
.addInput({
a_number: mockNumberInput,
a_conditional_text: mockTextInput,
})
.addCondition(
{
properties: {
a_number: {
minimum: 1,
},
},
required: ['a_number'],
},
{
if: {
properties: {
a_number: {
maximum: 10,
},
},
required: ['a_number'],
},
then: {
required: [],
},
else: {
required: ['a_conditional_text'],
},
},
{
required: ['a_conditional_text'],
}
)
.build();
export const schemaDynamicValidationMinLengthMaxLength = JSONSchemaBuilder()
.addInput({
a_text: mockTextInput,
a_conditional_text: mockTextInput,
})
.addCondition(
// if a_text is between 3 and 5 chars, a_conditional_text is optional.
{
properties: {
a_text: {
minLength: 3,
maxLength: 5,
},
},
required: ['a_text'],
},
{
required: [],
},
{
required: ['a_conditional_text'],
}
)
.build();
export const schemaDynamicValidationContains = JSONSchemaBuilder()
.addInput({
a_fieldset: mockFieldset,
validate_fieldset: {
title: 'Fieldset validation',
type: 'array',
description: 'Select what fieldset fields are required',
items: {
enum: ['all', 'username'],
},
'x-jsf-presentation': {
inputType: 'select',
multiple: true,
options: [
{
label: 'All',
value: 'all',
},
{
label: 'Username',
value: 'username',
},
],
placeholder: 'Select...',
},
},
})
.addCondition(
{
properties: {
validate_fieldset: {
contains: {
pattern: '^all$',
},
},
},
required: ['validate_fieldset'],
},
{
properties: {
a_fieldset: {
required: ['username', 'tabs'],
},
},
}
)
.setRequiredFields(['a_fieldset', 'validate_fieldset'])
.setOrder(['validate_fieldset', 'a_fieldset'])
.build();
export const schemaAnyOfValidation = JSONSchemaBuilder()
.addInput({
field_a: {
...mockTextInput,
title: 'Field A',
description: 'Field A is needed if B and C are empty',
},
field_b: {
...mockTextInput,
title: 'Field B',
description: 'Field B is needed if A is empty and C is not empty',
},
field_c: {
...mockTextInput,
title: 'Field C',
description: 'Field C is needed if A is empty and B is not empty',
},
})
.addAnyOf([
{
required: ['field_a'],
},
{
required: ['field_b', 'field_c'],
},
])
.build();
export const schemaWithConditionalPresentationProperties = JSONSchemaBuilder()
.addInput({
mock_radio: mockRadioInputString,
})
.addAllOf([
{
if: {
properties: {
mock_radio: {
const: 'no',
},
},
required: ['mock_radio'],
},
then: {
properties: {
mock_radio: {
'x-jsf-presentation': {
statement: {
description: '<a href="">conditional statement markup</a>',
severity: 'info',
},
},
},
},
},
else: {
properties: {
'x-jsf-presentation': {
mock_radio: null,
},
},
},
},
])
.setRequiredFields(['mock_radio'])
.build();
export const schemaWithConditionalReadOnlyProperty = JSONSchemaBuilder()
.addInput({ field_a: mockRadioInputString })
.addInput({ field_b: mockTextInput })
.addAllOf([
{
if: {
properties: {
field_a: {
const: 'yes',
},
},
required: ['field_a'],
},
then: {
properties: {
field_b: {
readOnly: false,
},
},
required: ['field_b'],
},
else: {
if: {
properties: {
field_a: {
const: 'no',
},
},
required: ['field_a'],
},
then: {
properties: {
field_b: {
readOnly: true,
},
},
required: ['field_b'],
},
else: {
properties: {
field_b: false,
},
},
},
},
])
.setRequiredFields(['field_a'])
.build();
export const schemaWithConditionalAcknowledgementProperty = JSONSchemaBuilder()
.addInput({ field_a: mockRadioInputString })
.addInput({ field_b: mockCheckboxInput })
.addAllOf([
{
if: {
properties: {
field_a: {
const: 'yes',
},
},
required: ['field_a'],
},
then: {
required: ['field_b'],
},
else: {
properties: {
field_b: false,
},
},
},
])
.setRequiredFields(['field_a'])
.build();
// Note: The second conditional (field_a_wrong) is incorrect,
// it's used to test/catch the scenario where devs forget to add the if.required[]
export const schemaWithWrongConditional = JSONSchemaBuilder()
.addInput({ field_a: mockRadioInputString })
.addInput({ field_b: mockTextInput })
.addInput({ field_a_wrong: mockRadioInputString })
.addInput({ field_b_wrong: mockTextInput })
.addAllOf([
{
if: {
properties: {
field_a: {
const: 'yes',
},
},
required: ['field_a'],
},
then: {
required: ['field_b'],
},
else: {
properties: {
field_b: false,
},
},
},
{
if: {
properties: {
field_a_wrong: {
const: 'yes',
},
},
// it's missing this "required" keyword, for field_b_wrong to be visible.
// required: ['field_a_wrong'],
},
then: {
required: ['field_b_wrong'],
},
else: {
properties: {
field_b_wrong: false,
},
},
},
])
.setRequiredFields(['field_a', 'field_a_wrong'])
.build();
export const schemaFieldsetScopedCondition = {
additionalProperties: false,
properties: {
child: {
type: 'object',
title: 'Child details',
description:
'In the JSON Schema, you will notice the if/then/else is inside the property, not in the root.',
'x-jsf-presentation': {
inputType: 'fieldset',
},
properties: {
has_child: {
description: 'If yes, it will show its age.',
'x-jsf-presentation': {
inputType: 'radio',
options: [
{
label: 'Yes',
value: 'yes',
},
{
label: 'No',
value: 'no',
},
],
},
title: 'Do you have a child?',
type: 'string',
},
age: {
description: 'This age is required, but the "age" at the root level is still optional.',
'x-jsf-presentation': {
inputType: 'number',
},
title: 'Age',
type: 'number',
},
passport_id: {
description: 'Passport ID is optional',
'x-jsf-presentation': {
inputType: 'text',
},
title: 'Passport ID',
type: 'string',
},
},
required: ['has_child'],
allOf: [
{
if: {
properties: {
has_child: {
const: 'yes',
},
},
required: ['has_child'],
},
then: {
required: ['age'],
},
else: {
properties: {
age: false,
passport_id: false,
},
},
},
],
},
age: {
type: 'number',
title: 'Age',
'x-jsf-presentation': {
inputType: 'number',
description: 'This field is optional, always.',
},
},
},
required: ['child'],
type: 'object',
};
export const schemaWithConditionalToFieldset = {
additionalProperties: false,
type: 'object',
properties: {
work_hours_per_week: {
title: 'Hours per week',
type: 'number',
description: 'Above 30 hours, the Perk>Food options change, and PTO is required.',
'x-jsf-presentation': {
inputType: 'number',
},
},
pto: {
title: 'Time-off (days)',
type: 'number',
'x-jsf-presentation': {
inputType: 'number',
},
},
perks: {
additionalProperties: false,
properties: {
food: {
oneOf: [
{
const: 'lunch',
title: 'Lunch',
},
{
const: 'dinner',
title: 'Dinner',
},
{
const: 'all',
title: 'All',
description: 'Every meal',
},
{
const: 'no',
title: 'No food',
},
],
title: 'Food',
type: 'string',
'x-jsf-presentation': {
inputType: 'radio',
},
},
retirement: {
oneOf: [
{
const: 'basic',
title: 'Basic',
},
{
const: 'plus',
title: 'Plus',
},
],
title: 'Retirement',
type: 'string',
'x-jsf-presentation': {
inputType: 'radio',
},
},
},
required: ['food', 'retirement'],
title: 'Perks',
type: 'object',
'x-jsf-presentation': {
inputType: 'fieldset',
},
},
},
allOf: [
{
if: {
properties: {
work_hours_per_week: {
minimum: 30,
},
},
required: ['work_hours_per_week'],
},
then: {
properties: {
pto: {
$comment: '@BUG: This description does not disappear once activated.',
description: 'Above 30 hours, the PTO needs to be at least 20 days.',
minimum: 20,
},
perks: {
properties: {
food: {
description: "Above 30 hours, the 'no' option disappears.",
oneOf: [
{
const: 'lunch',
title: 'Lunch',
},
{
const: 'dinner',
title: 'Dinner',
},
{
const: 'all',
title: 'all',
},
],
},
},
},
},
required: ['pto'],
},
},
],
required: ['perks', 'work_hours_per_week'],
};
export const schemaWorkSchedule = {
type: 'object',
properties: {
employee_schedule: {
title: 'Employee Schedule',
'x-jsf-presentation': {
inputType: 'fieldset',
position: 0,
},
properties: {
schedule_type: {
type: 'string',
title: 'Employee Schedule Type',
oneOf: [
{ const: 'flexible', title: "Employee's hours are flexible" },
{
const: 'core_business_hours',
title: "Employee works employer's core business hours",
},
{ const: 'fixed_hours', title: 'Employee works fixed hours' },
],
'x-jsf-presentation': {
inputType: 'select',
position: 0,
},
},
daily_schedule: {
type: 'array',
items: {
type: 'object',
properties: {
day: {
type: 'string',
enum: [
'monday',
'tuesday',
'wednesday',
'thursday',
'friday',
'saturday',
'sunday',
],
},
start_time: {
type: 'string',
pattern: '^([01]\\d|2[0-3]):([0-5]\\d)$',
},
end_time: {
type: 'string',
pattern: '^([01]\\d|2[0-3]):([0-5]\\d)$',
},
hours: {
type: 'number',
minimum: 0,
},
break_duration_minutes: {
type: 'integer',
minimum: 0,
},
},
required: ['day', 'start_time', 'end_time', 'hours', 'break_duration_minutes'],
},
'x-jsf-presentation': {
position: 1,
inputType: 'work-schedule',
},
default: [],
},
work_hours_per_week: {
type: 'number',
title: 'Work Hours Per Week',
maximum: 50,
minimum: 1,
'x-jsf-errorMessage': {
minimum: 'You must enter work hours to equal more than 0.',
},
'x-jsf-presentation': {
inputType: 'number',
position: 2,
},
},
exclude_breaks_in_work_hours: {
const: true,
readOnly: true,
'x-jsf-presentation': {
inputType: 'hidden',
},
type: 'boolean',
},
},
allOf: [
{
if: {
properties: {
schedule_type: {
enum: ['flexible', 'core_business_hours', 'fixed_hours'],
},
},
required: ['schedule_type'],
},
then: {
required: ['work_hours_per_week'],
},
else: {
properties: {
work_hours_per_week: false,
},
},
},
{
if: {
properties: {
schedule_type: {
enum: ['core_business_hours', 'fixed_hours'],
},
},
required: ['schedule_type'],
},
then: {
required: ['daily_schedule'],
},
else: {
properties: {
daily_schedule: false,
},
},
},
{
if: {
properties: {
schedule_type: {
const: 'flexible',
},
},
required: ['schedule_type'],
},
then: {
required: ['work_hours_per_week'],
properties: {
work_hours_per_week: {
readOnly: false,
},
},
},
},
{
if: {
properties: {
schedule_type: {
const: 'core_business_hours',
},
},
required: ['schedule_type'],
},
then: {
required: ['work_hours_per_week'],
properties: {
daily_schedule: {
title: 'Core business hours',
default: [
{
day: 'monday',
start_time: '10:00',
end_time: '17:30',
hours: 8.5,
break_duration_minutes: 60,
},
{
day: 'wednesday',
start_time: '10:00',
end_time: '17:30',
hours: 7.5,
break_duration_minutes: 45,