├── src ├── global.d.ts ├── Interfaces │ ├── ErrorValue.ts │ ├── FieldParent.ts │ ├── Units.ts │ ├── MappedFields.ts │ ├── FieldProps.ts │ ├── State.ts │ ├── index.ts │ ├── Store.ts │ ├── FormBuilder.ts │ ├── Actions.ts │ └── Field.ts ├── index.ts ├── Fields │ ├── index.ts │ ├── RangeSliderField.tsx │ ├── CheckboxField.tsx │ ├── SelectField.tsx │ ├── TextField.tsx │ ├── GroupField.tsx │ ├── SimpleWeightField.tsx │ ├── ChoiceField.tsx │ ├── SimpleMoneyField.tsx │ ├── PercentageField.tsx │ ├── RepeaterField.tsx │ ├── WeightField.tsx │ ├── MoneyField.tsx │ ├── ComboboxField.tsx │ └── AutocompleteField.tsx ├── Components │ ├── RepeaterRows.tsx │ ├── FieldContainer.tsx │ └── RepeaterRow.tsx ├── PolarisFormBuilder.tsx ├── Utils │ └── index.ts └── Store │ └── FormStore.ts ├── .eslintrc.js ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── package.json ├── CONTRIBUTING.md ├── tsconfig.json └── README.md /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'meiosis-setup/mergerino'; 2 | -------------------------------------------------------------------------------- /src/Interfaces/ErrorValue.ts: -------------------------------------------------------------------------------- 1 | export type ErrorValue = string | string[] | undefined; 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: '@additionapps/eslint-config-addition', 3 | } 4 | 5 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { PolarisFormBuilder } from './PolarisFormBuilder'; 2 | export * from './Interfaces'; 3 | export * from './Utils'; 4 | -------------------------------------------------------------------------------- /src/Interfaces/FieldParent.ts: -------------------------------------------------------------------------------- 1 | import { Field } from '.'; 2 | 3 | export interface FieldParent { 4 | field: Field; 5 | index: number; 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | test 4 | src/**.js 5 | .idea/* 6 | .vscode/* 7 | 8 | coverage 9 | .nyc_output 10 | *.log 11 | 12 | package.lock 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /src/Interfaces/Units.ts: -------------------------------------------------------------------------------- 1 | export type WeightUnits = 'kg' | 'g' | 'lb' | 'oz'; 2 | 3 | export interface Units { 4 | locale?: string; 5 | currency?: string; 6 | weight?: WeightUnits; 7 | } 8 | -------------------------------------------------------------------------------- /src/Interfaces/MappedFields.ts: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import { FieldProps } from './FieldProps'; 3 | 4 | export interface MappedFields { 5 | [name: string]: FunctionComponent; 6 | } 7 | -------------------------------------------------------------------------------- /src/Interfaces/FieldProps.ts: -------------------------------------------------------------------------------- 1 | import { Actions, Field, FieldParent, State } from '.'; 2 | 3 | export interface FieldProps { 4 | field: Field; 5 | state: State; 6 | actions: Actions; 7 | ancestors?: FieldParent[]; 8 | } 9 | -------------------------------------------------------------------------------- /src/Interfaces/State.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ErrorValue, 3 | Field, 4 | MappedFields, 5 | Units, 6 | } from '../Interfaces'; 7 | 8 | export interface State { 9 | model: any; 10 | errors: Record; 11 | fields: Field[]; 12 | inputs: MappedFields; 13 | units: Units; 14 | focus: string | null; 15 | } 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to Polaris Form Builder will be documented in this file 4 | 5 | ## 2.0.0 - 2020-09-23 6 | 7 | - Update Shopify Polaris to v5.x. 8 | 9 | ## 1.4.0 - 2020-06-25 10 | 11 | - Add support for `maxRows` and `minRows` to control repeater field UI. 12 | 13 | ## 1.3.0 - 2020-06-15 14 | 15 | - First stable release - note that earlier versions were experimental and not suitable for production usage 16 | 17 | -------------------------------------------------------------------------------- /src/Interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export { Actions } from './Actions'; 2 | export { ErrorValue } from './ErrorValue'; 3 | export { Field } from './Field'; 4 | export { FieldParent } from './FieldParent'; 5 | export { FieldProps } from './FieldProps'; 6 | export { FormBuilder } from './FormBuilder'; 7 | export { MappedFields } from './MappedFields'; 8 | export { Units } from './Units'; 9 | export { WeightUnits } from './Units'; 10 | export { State } from './State'; 11 | export { Store } from './Store'; 12 | -------------------------------------------------------------------------------- /src/Interfaces/Store.ts: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import { ErrorValue, Field, Units } from '.'; 3 | 4 | export interface Store { 5 | model: any; 6 | fields: Field[]; 7 | units: Units; 8 | errors: Record; 9 | focus: string | null; 10 | customFields: FunctionComponent[]; 11 | onModelUpdate: (model: any) => void; 12 | onErrorUpdate: (errors: Record) => void; 13 | onFocusUpdate: (focus: string | null) => void; 14 | } 15 | -------------------------------------------------------------------------------- /src/Interfaces/FormBuilder.ts: -------------------------------------------------------------------------------- 1 | import { ErrorValue, Field, Units } from '.'; 2 | import { FunctionComponent } from 'react'; 3 | 4 | export interface FormBuilder { 5 | model: any; 6 | fields: Field[]; 7 | units?: Units; 8 | errors?: Record; 9 | focus?: string | null; 10 | customFields?: FunctionComponent[]; 11 | onModelUpdate: (model: any) => void; 12 | onErrorUpdate?: (errors: Record) => void; 13 | onFocusUpdate?: (focus: string | null) => void; 14 | } 15 | -------------------------------------------------------------------------------- /src/Interfaces/Actions.ts: -------------------------------------------------------------------------------- 1 | import { Field, FieldParent } from '.'; 2 | 3 | export interface Actions { 4 | updateField: (value: any, field: Field, ancestors?: FieldParent[]) => void; 5 | setFocus: (field: Field, ancestors?: FieldParent[]) => void; 6 | addRepeaterRow: ( 7 | rowIndex: number, 8 | model: any, 9 | field: Field, 10 | ancestors?: FieldParent[], 11 | ) => void; 12 | removeRepeaterRow: ( 13 | rowIndex: number, 14 | model: any, 15 | field: Field, 16 | ancestors?: FieldParent[], 17 | ) => void; 18 | } 19 | -------------------------------------------------------------------------------- /src/Interfaces/Field.ts: -------------------------------------------------------------------------------- 1 | import { TextFieldProps } from '@shopify/polaris'; 2 | import { ListOption } from '../Fields'; 3 | 4 | export type GroupLayout = 'stacked' | 'grouped' | 'condensed'; 5 | 6 | export interface Field { 7 | key: string; 8 | input: string; 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | config?: any; 11 | subFields?: Field[]; 12 | emptyMessage?: string; 13 | layout?: GroupLayout; 14 | addButtonText?: string; 15 | title?: string; 16 | defaultValue?: string | number; 17 | warning?: string; 18 | minRows?: number; 19 | maxRows?: number; 20 | text_field_config?: TextFieldProps; 21 | options?: ListOption[]; 22 | } 23 | -------------------------------------------------------------------------------- /src/Fields/index.ts: -------------------------------------------------------------------------------- 1 | export { AutocompleteField, AutocompleteProps } from './AutocompleteField'; 2 | export { CheckboxField, CheckboxProps } from './CheckboxField'; 3 | export { ChoiceField, ChoiceListProps } from './ChoiceField'; 4 | export { ComboboxField, ComboboxFieldProps, ListOption } from './ComboboxField'; 5 | export { GroupField } from './GroupField'; 6 | export { MoneyField } from './MoneyField'; 7 | export { SimpleMoneyField } from './SimpleMoneyField'; 8 | export { PercentageField } from './PercentageField'; 9 | export { RangeSliderField, RangeSliderProps } from './RangeSliderField'; 10 | export { RepeaterField } from './RepeaterField'; 11 | export { SelectField, SelectProps } from './SelectField'; 12 | export { TextField, TextFieldProps } from './TextField'; 13 | export { WeightField } from './WeightField'; 14 | export { SimpleWeightField } from './SimpleWeightField'; 15 | -------------------------------------------------------------------------------- /src/Fields/RangeSliderField.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | RangeSlider, 4 | RangeSliderProps as PolarisRangeSliderProps, 5 | } from '@shopify/polaris'; 6 | import { Field, FieldProps } from '../Interfaces'; 7 | import { getErrors, getValue } from '../Utils'; 8 | 9 | interface LocalField extends Field { 10 | config: PolarisRangeSliderProps; 11 | } 12 | 13 | export interface RangeSliderProps extends FieldProps { 14 | field: LocalField; 15 | } 16 | 17 | export const RangeSliderField = ({ 18 | field, 19 | state, 20 | actions, 21 | ancestors, 22 | }: RangeSliderProps) => { 23 | const value = getValue(state.model, field, ancestors) as number; 24 | 25 | const fieldProps = { 26 | ...field.config, 27 | value: value, 28 | error: getErrors(state.errors, field, ancestors), 29 | label: field.config.label, 30 | onChange: (value: number) => 31 | actions.updateField(value, field, ancestors), 32 | }; 33 | 34 | return ; 35 | }; 36 | -------------------------------------------------------------------------------- /src/Components/RepeaterRows.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FieldProps } from '../Interfaces'; 3 | import { getValue } from '../Utils'; 4 | import { RepeaterRow } from './RepeaterRow'; 5 | import { Stack } from '@shopify/polaris'; 6 | 7 | export const RepeaterRows = ({ 8 | field, 9 | state, 10 | actions, 11 | ancestors, 12 | }: FieldProps) => { 13 | const rows = getValue(state.model, field, ancestors) as Record< 14 | string, 15 | unknown 16 | >[]; 17 | 18 | const rowLayout = rows 19 | ? rows.map((row: Record, index: number) => { 20 | return ( 21 | 29 | ); 30 | }) 31 | : null; 32 | 33 | return {rowLayout}; 34 | }; 35 | -------------------------------------------------------------------------------- /src/Fields/CheckboxField.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent } from 'react'; 2 | import { 3 | Checkbox, 4 | CheckboxProps as PolarisCheckboxProps, 5 | } from '@shopify/polaris'; 6 | import { Field, FieldProps } from '../Interfaces'; 7 | import { getErrors, getValue } from '../Utils'; 8 | 9 | interface LocalField extends Field { 10 | config: PolarisCheckboxProps; 11 | } 12 | 13 | export interface CheckboxProps extends FieldProps { 14 | field: LocalField; 15 | } 16 | 17 | export const CheckboxField: FunctionComponent = ({ 18 | field, 19 | state, 20 | actions, 21 | ancestors, 22 | }: CheckboxProps) => { 23 | const fieldProps = { 24 | ...field.config, 25 | checked: getValue(state.model, field, ancestors) as 26 | | boolean 27 | | 'indeterminate', 28 | label: field.config.label, 29 | error: getErrors(state.errors, field, ancestors), 30 | onChange: (value: boolean) => 31 | actions.updateField(value, field, ancestors), 32 | }; 33 | 34 | return ; 35 | }; 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Addition pty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/Fields/SelectField.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Select, SelectProps as PolarisSelectProps } from '@shopify/polaris'; 3 | import _get from 'lodash.get'; 4 | import { Field, FieldProps } from '../Interfaces'; 5 | import { getErrors, getValue } from '../Utils'; 6 | 7 | interface LocalField extends Field { 8 | config: PolarisSelectProps; 9 | } 10 | 11 | export interface SelectProps extends FieldProps { 12 | field: LocalField; 13 | } 14 | 15 | export const SelectField = ({ 16 | field, 17 | state, 18 | actions, 19 | ancestors, 20 | }: SelectProps) => { 21 | let value = getValue(state.model, field, ancestors) as string | null; 22 | 23 | if (value === null) { 24 | value = _get(field, 'field.config.options.0.value'); 25 | } 26 | 27 | const fieldProps = { 28 | ...field.config, 29 | value: value || undefined, 30 | error: getErrors(state.errors, field, ancestors), 31 | label: field.config.label, 32 | onChange: (value: string) => { 33 | actions.updateField(value, field, ancestors); 34 | }, 35 | }; 36 | 37 | return