mirror of
https://github.com/IT4Change/ohmyform-ui.git
synced 2025-12-13 09:45:50 +00:00
add license and more frontend field types
This commit is contained in:
parent
ec0f6e9572
commit
34d154b4dd
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
## License
|
||||||
|
(The MIT License)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
'Software'), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@ -1,4 +1,5 @@
|
|||||||
@import "variables";
|
@import "variables";
|
||||||
|
@import "node_modules/swiper/swiper.scss";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--backgroundColor: #{$background-color};
|
--backgroundColor: #{$background-color};
|
||||||
@ -26,3 +27,11 @@
|
|||||||
.ant-spin-nested-loading > div > .ant-spin {
|
.ant-spin-nested-loading > div > .ant-spin {
|
||||||
max-height: unset;
|
max-height: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.swiper-container {
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
.swiper-wrapper {
|
||||||
|
position: fixed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ export const DesignTab: React.FC<TabPaneProps> = props => {
|
|||||||
{name: 'questionColor', label: 'Question Color'},
|
{name: 'questionColor', label: 'Question Color'},
|
||||||
{name: 'answerColor', label: 'Answer Color'},
|
{name: 'answerColor', label: 'Answer Color'},
|
||||||
{name: 'buttonColor', label: 'Button Color'},
|
{name: 'buttonColor', label: 'Button Color'},
|
||||||
|
{name: 'buttonActiveColor', label: 'Button Active Color'},
|
||||||
{name: 'buttonTextColor', label: 'Button Text Color'},
|
{name: 'buttonTextColor', label: 'Button Text Color'},
|
||||||
].map(({label, name}) => (
|
].map(({label, name}) => (
|
||||||
<Form.Item key={name} label={label} name={['form', 'design', 'colors', name]}>
|
<Form.Item key={name} label={label} name={['form', 'design', 'colors', name]}>
|
||||||
|
|||||||
@ -55,7 +55,14 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
|
|||||||
<DeleteOutlined key={'delete'} onClick={() => remove(index)} />
|
<DeleteOutlined key={'delete'} onClick={() => remove(index)} />
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Form.Item label={'Url'} name={[field.key, 'url']} labelCol={{ span: 6 }}>
|
<Form.Item
|
||||||
|
label={'Url'}
|
||||||
|
name={[field.key, 'url']}
|
||||||
|
rules={[
|
||||||
|
{type: 'url', message: 'Must be a valid url'}
|
||||||
|
]}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={'Action'} name={[field.key, 'action']} labelCol={{ span: 6 }}>
|
<Form.Item label={'Action'} name={[field.key, 'action']} labelCol={{ span: 6 }}>
|
||||||
@ -67,6 +74,9 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
|
|||||||
<Form.Item label={'Background Color'} name={[field.key, 'bgColor']} labelCol={{ span: 6 }}>
|
<Form.Item label={'Background Color'} name={[field.key, 'bgColor']} labelCol={{ span: 6 }}>
|
||||||
<InputColor />
|
<InputColor />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item label={'Active Color'} name={[field.key, 'activeColor']} labelCol={{ span: 6 }}>
|
||||||
|
<InputColor />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item label={'Color'} name={[field.key, 'color']} labelCol={{ span: 6 }}>
|
<Form.Item label={'Color'} name={[field.key, 'color']} labelCol={{ span: 6 }}>
|
||||||
<InputColor />
|
<InputColor />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@ -4,31 +4,8 @@ import {FormInstance} from 'antd/lib/form'
|
|||||||
import {FieldData} from 'rc-field-form/lib/interface'
|
import {FieldData} from 'rc-field-form/lib/interface'
|
||||||
import React, {useEffect, useState} from 'react'
|
import React, {useEffect, useState} from 'react'
|
||||||
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
|
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
|
||||||
import {DateType} from './types/date.type'
|
import {adminTypes} from './types'
|
||||||
import {DropdownType} from './types/dropdown.type'
|
|
||||||
import {EmailType} from './types/email.type'
|
|
||||||
import {HiddenType} from './types/hidden.type'
|
|
||||||
import {LinkType} from './types/link.type'
|
|
||||||
import {NumberType} from './types/number.type'
|
|
||||||
import {RadioType} from './types/radio.type'
|
|
||||||
import {RatingType} from './types/rating.type'
|
|
||||||
import {TextType} from './types/text.type'
|
import {TextType} from './types/text.type'
|
||||||
import {TextareaType} from './types/textarea.type'
|
|
||||||
import {YesNoType} from './types/yes_no.type'
|
|
||||||
|
|
||||||
export const availableTypes = {
|
|
||||||
'textfield': TextType,
|
|
||||||
'date': DateType,
|
|
||||||
'email': EmailType,
|
|
||||||
'textarea': TextareaType,
|
|
||||||
'link': LinkType,
|
|
||||||
'dropdown': DropdownType,
|
|
||||||
'rating': RatingType,
|
|
||||||
'radio': RadioType,
|
|
||||||
'hidden': HiddenType,
|
|
||||||
'yes_no': YesNoType,
|
|
||||||
'number': NumberType,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
form: FormInstance
|
form: FormInstance
|
||||||
@ -50,7 +27,7 @@ export const FieldCard: React.FC<Props> = props => {
|
|||||||
} = props
|
} = props
|
||||||
|
|
||||||
const type = form.getFieldValue(['form', 'fields', field.name as string, 'type'])
|
const type = form.getFieldValue(['form', 'fields', field.name as string, 'type'])
|
||||||
const TypeComponent: React.FC<any> = availableTypes[type] || TextType
|
const TypeComponent = adminTypes[type] || TextType
|
||||||
|
|
||||||
const [nextTitle, setNextTitle] = useState(form.getFieldValue(['form', 'fields', field.name as string, 'title']))
|
const [nextTitle, setNextTitle] = useState(form.getFieldValue(['form', 'fields', field.name as string, 'title']))
|
||||||
|
|
||||||
@ -120,6 +97,7 @@ export const FieldCard: React.FC<Props> = props => {
|
|||||||
name={[field.name as string, 'required']}
|
name={[field.name as string, 'required']}
|
||||||
labelCol={{ span: 6 }}
|
labelCol={{ span: 6 }}
|
||||||
valuePropName={'checked'}
|
valuePropName={'checked'}
|
||||||
|
extra={type === 'hidden' && 'If required, default value must be set to enable users to submit form!'}
|
||||||
>
|
>
|
||||||
<Checkbox />
|
<Checkbox />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@ -4,7 +4,8 @@ import {FormInstance} from 'antd/lib/form'
|
|||||||
import {TabPaneProps} from 'antd/lib/tabs'
|
import {TabPaneProps} from 'antd/lib/tabs'
|
||||||
import React, {useCallback, useState} from 'react'
|
import React, {useCallback, useState} from 'react'
|
||||||
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
|
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
|
||||||
import {availableTypes, FieldCard} from './field.card'
|
import {FieldCard} from './field.card'
|
||||||
|
import {adminTypes} from './types'
|
||||||
|
|
||||||
interface Props extends TabPaneProps {
|
interface Props extends TabPaneProps {
|
||||||
form: FormInstance
|
form: FormInstance
|
||||||
@ -40,7 +41,7 @@ export const FieldsTab: React.FC<Props> = props => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Select value={nextType} onChange={e => setNextType(e)} style={{ minWidth: 200 }}>
|
<Select value={nextType} onChange={e => setNextType(e)} style={{ minWidth: 200 }}>
|
||||||
{Object.keys(availableTypes).map(type => <Select.Option value={type} key={type}>{type}</Select.Option> )}
|
{Object.keys(adminTypes).map(type => <Select.Option value={type} key={type}>{type}</Select.Option> )}
|
||||||
</Select>
|
</Select>
|
||||||
<Button
|
<Button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
|
|||||||
@ -55,7 +55,14 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
|
|||||||
<DeleteOutlined key={'delete'} onClick={() => remove(index)} />
|
<DeleteOutlined key={'delete'} onClick={() => remove(index)} />
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Form.Item label={'Url'} name={[field.key, 'url']} labelCol={{ span: 6 }}>
|
<Form.Item
|
||||||
|
label={'Url'}
|
||||||
|
name={[field.key, 'url']}
|
||||||
|
rules={[
|
||||||
|
{type: 'url', message: 'Must be a valid url'}
|
||||||
|
]}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={'Action'} name={[field.key, 'action']} labelCol={{ span: 6 }}>
|
<Form.Item label={'Action'} name={[field.key, 'action']} labelCol={{ span: 6 }}>
|
||||||
@ -67,6 +74,9 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
|
|||||||
<Form.Item label={'Background Color'} name={[field.key, 'bgColor']} labelCol={{ span: 6 }}>
|
<Form.Item label={'Background Color'} name={[field.key, 'bgColor']} labelCol={{ span: 6 }}>
|
||||||
<InputColor />
|
<InputColor />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item label={'Active Color'} name={[field.key, 'activeColor']} labelCol={{ span: 6 }}>
|
||||||
|
<InputColor />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item label={'Color'} name={[field.key, 'color']} labelCol={{ span: 6 }}>
|
<Form.Item label={'Color'} name={[field.key, 'color']} labelCol={{ span: 6 }}>
|
||||||
<InputColor />
|
<InputColor />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import {Form, Input} from 'antd'
|
import {DatePicker, Form} from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {AdminFieldTypeProps} from './type.props'
|
||||||
|
|
||||||
interface Props {
|
export const DateType: React.FC<AdminFieldTypeProps> = props => {
|
||||||
field: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DateType: React.FC<Props> = props => {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@ -13,21 +10,21 @@ export const DateType: React.FC<Props> = props => {
|
|||||||
name={[props.field.name, 'value']}
|
name={[props.field.name, 'value']}
|
||||||
labelCol={{ span: 6 }}
|
labelCol={{ span: 6 }}
|
||||||
>
|
>
|
||||||
<Input type={'date'} />
|
<DatePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={'Min Date'}
|
label={'Min Date'}
|
||||||
name={[props.field.name, 'value']}
|
name={[props.field.name, 'min']}
|
||||||
labelCol={{ span: 6 }}
|
labelCol={{ span: 6 }}
|
||||||
>
|
>
|
||||||
<Input />
|
<DatePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={'Max Date'}
|
label={'Max Date'}
|
||||||
name={[props.field.name, 'value']}
|
name={[props.field.name, 'max']}
|
||||||
labelCol={{ span: 6 }}
|
labelCol={{ span: 6 }}
|
||||||
>
|
>
|
||||||
<Input />
|
<DatePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import {Form, Input} from 'antd'
|
import {Form, Input} from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {AdminFieldTypeProps} from './type.props'
|
||||||
|
|
||||||
interface Props {
|
export const DropdownType: React.FC<AdminFieldTypeProps> = props => {
|
||||||
field: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DropdownType: React.FC<Props> = props => {
|
|
||||||
// TODO add dropdown options
|
// TODO add dropdown options
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import {Form, Input} from 'antd'
|
import {Form, Input} from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {AdminFieldTypeProps} from './type.props'
|
||||||
|
|
||||||
interface Props {
|
export const EmailType: React.FC<AdminFieldTypeProps> = props => {
|
||||||
field: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export const EmailType: React.FC<Props> = props => {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import {Form, Input} from 'antd'
|
import {Form, Input} from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {AdminFieldTypeProps} from './type.props'
|
||||||
|
|
||||||
interface Props {
|
export const HiddenType: React.FC<AdminFieldTypeProps> = props => {
|
||||||
field: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export const HiddenType: React.FC<Props> = props => {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
29
components/form/admin/types/index.ts
Normal file
29
components/form/admin/types/index.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {DateType} from './date.type'
|
||||||
|
import {DropdownType} from './dropdown.type'
|
||||||
|
import {EmailType} from './email.type'
|
||||||
|
import {HiddenType} from './hidden.type'
|
||||||
|
import {LinkType} from './link.type'
|
||||||
|
import {NumberType} from './number.type'
|
||||||
|
import {RadioType} from './radio.type'
|
||||||
|
import {RatingType} from './rating.type'
|
||||||
|
import {TextType} from './text.type'
|
||||||
|
import {TextareaType} from './textarea.type'
|
||||||
|
import {AdminFieldTypeProps} from './type.props'
|
||||||
|
import {YesNoType} from './yes_no.type'
|
||||||
|
|
||||||
|
export const adminTypes: {
|
||||||
|
[key: string]: React.FC<AdminFieldTypeProps>
|
||||||
|
} = {
|
||||||
|
'textfield': TextType,
|
||||||
|
'date': DateType,
|
||||||
|
'email': EmailType,
|
||||||
|
'textarea': TextareaType,
|
||||||
|
'link': LinkType,
|
||||||
|
'dropdown': DropdownType,
|
||||||
|
'rating': RatingType,
|
||||||
|
'radio': RadioType,
|
||||||
|
'hidden': HiddenType,
|
||||||
|
'yes_no': YesNoType,
|
||||||
|
'number': NumberType,
|
||||||
|
}
|
||||||
@ -1,11 +1,8 @@
|
|||||||
import {Form, Input} from 'antd'
|
import {Form, Input} from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {AdminFieldTypeProps} from './type.props'
|
||||||
|
|
||||||
interface Props {
|
export const LinkType: React.FC<AdminFieldTypeProps> = props => {
|
||||||
field: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LinkType: React.FC<Props> = props => {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import {Form, InputNumber} from 'antd'
|
import {Form, InputNumber} from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {AdminFieldTypeProps} from './type.props'
|
||||||
|
|
||||||
interface Props {
|
export const NumberType: React.FC<AdminFieldTypeProps> = props => {
|
||||||
field: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NumberType: React.FC<Props> = props => {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import {Form, Input} from 'antd'
|
import {Form, Input} from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {AdminFieldTypeProps} from './type.props'
|
||||||
|
|
||||||
interface Props {
|
export const RadioType: React.FC<AdminFieldTypeProps> = props => {
|
||||||
field: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RadioType: React.FC<Props> = props => {
|
|
||||||
// TODO Add radio support
|
// TODO Add radio support
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
import {Form, Input} from 'antd'
|
import {Form, Input} from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {AdminFieldTypeProps} from './type.props'
|
||||||
|
|
||||||
interface Props {
|
export const RatingType: React.FC<AdminFieldTypeProps> = props => {
|
||||||
field: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RatingType: React.FC<Props> = props => {
|
|
||||||
// TODO add ratings
|
// TODO add ratings
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import {Form, Input} from 'antd'
|
import {Form, Input} from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {AdminFieldTypeProps} from './type.props'
|
||||||
|
|
||||||
interface Props {
|
export const TextType: React.FC<AdminFieldTypeProps> = props => {
|
||||||
field: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TextType: React.FC<Props> = props => {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import {Form, Input} from 'antd'
|
import {Form, Input} from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {AdminFieldTypeProps} from './type.props'
|
||||||
|
|
||||||
interface Props {
|
export const TextareaType: React.FC<AdminFieldTypeProps> = props => {
|
||||||
field: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TextareaType: React.FC<Props> = props => {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
3
components/form/admin/types/type.props.ts
Normal file
3
components/form/admin/types/type.props.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface AdminFieldTypeProps {
|
||||||
|
field: any
|
||||||
|
}
|
||||||
@ -1,11 +1,8 @@
|
|||||||
import {Form, Input} from 'antd'
|
import {Form, Input} from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {AdminFieldTypeProps} from './type.props'
|
||||||
|
|
||||||
interface Props {
|
export const YesNoType: React.FC<AdminFieldTypeProps> = props => {
|
||||||
field: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export const YesNoType: React.FC<Props> = props => {
|
|
||||||
// TODO add switch
|
// TODO add switch
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
88
components/form/field.tsx
Normal file
88
components/form/field.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import {Form, message} from 'antd'
|
||||||
|
import {useForm} from 'antd/lib/form/Form'
|
||||||
|
import React from 'react'
|
||||||
|
import {FormDesignFragment, FormFieldFragment} from '../../graphql/fragment/form.fragment'
|
||||||
|
import {StyledButton} from '../styled/button'
|
||||||
|
import {StyledH1} from '../styled/h1'
|
||||||
|
import {StyledP} from '../styled/p'
|
||||||
|
import {fieldTypes} from './types'
|
||||||
|
import {TextType} from './types/text.type'
|
||||||
|
import {FieldTypeProps} from './types/type.props'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
field: FormFieldFragment
|
||||||
|
design: FormDesignFragment
|
||||||
|
|
||||||
|
next: () => any
|
||||||
|
prev: () => any
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Field: React.FC<Props> = ({field, design, children, next, prev, ...props}) => {
|
||||||
|
const [form] = useForm()
|
||||||
|
|
||||||
|
const FieldInput: React.FC<FieldTypeProps> = fieldTypes[field.type] || TextType
|
||||||
|
|
||||||
|
const finish = (data) => {
|
||||||
|
console.log('received field data', data)
|
||||||
|
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = () => {
|
||||||
|
message.error('Check inputs!')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
onFinish={finish}
|
||||||
|
onFinishFailed={error}
|
||||||
|
{...props}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
padding: 32,
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
}}>
|
||||||
|
<pre style={{
|
||||||
|
opacity: 0.3
|
||||||
|
}}>{JSON.stringify(field, null, 2)}</pre>
|
||||||
|
|
||||||
|
<StyledH1 design={design} type={'question'}>{field.title}</StyledH1>
|
||||||
|
{field.description && <StyledP design={design} type={'question'}>{field.description}</StyledP>}
|
||||||
|
|
||||||
|
<FieldInput
|
||||||
|
design={design}
|
||||||
|
field={field}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
padding: 32,
|
||||||
|
display: 'flex',
|
||||||
|
}}>
|
||||||
|
<StyledButton
|
||||||
|
background={design.colors.buttonColor}
|
||||||
|
color={design.colors.buttonTextColor}
|
||||||
|
highlight={design.colors.buttonActiveColor}
|
||||||
|
onClick={prev}
|
||||||
|
>{'Previous'}</StyledButton>
|
||||||
|
|
||||||
|
<div style={{flex: 1}} />
|
||||||
|
|
||||||
|
<StyledButton
|
||||||
|
background={design.colors.buttonColor}
|
||||||
|
color={design.colors.buttonTextColor}
|
||||||
|
highlight={design.colors.buttonActiveColor}
|
||||||
|
size={'large'}
|
||||||
|
onClick={form.submit}
|
||||||
|
>{'Next'}</StyledButton>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
70
components/form/page.tsx
Normal file
70
components/form/page.tsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import {Space} from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import {FormDesignFragment, FormPageFragment} from '../../graphql/fragment/form.fragment'
|
||||||
|
import {StyledButton} from '../styled/button'
|
||||||
|
import {StyledH1} from '../styled/h1'
|
||||||
|
import {StyledP} from '../styled/p'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type: 'start' | 'end'
|
||||||
|
page: FormPageFragment
|
||||||
|
design: FormDesignFragment
|
||||||
|
|
||||||
|
next: () => any
|
||||||
|
prev: () => any
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FormPage: React.FC<Props> = ({page, design, next, prev, type, children, ...props}) => {
|
||||||
|
if (!page.show) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}} {...props}>
|
||||||
|
<div style={{
|
||||||
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}>
|
||||||
|
<StyledH1 design={design} type={'question'}>{page.title}</StyledH1>
|
||||||
|
<StyledP design={design} type={'question'}>{page.paragraph}</StyledP>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
padding: 32,
|
||||||
|
display: 'flex',
|
||||||
|
}}>
|
||||||
|
{page.buttons.length > 0 && (
|
||||||
|
<Space>
|
||||||
|
{page.buttons.map((button, key) => {
|
||||||
|
return (
|
||||||
|
<StyledButton
|
||||||
|
background={button.bgColor}
|
||||||
|
color={button.color}
|
||||||
|
highlight={button.activeColor}
|
||||||
|
key={key}
|
||||||
|
href={button.url}
|
||||||
|
target={'_blank'}
|
||||||
|
>{button.text}</StyledButton>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{flex: 1}} />
|
||||||
|
|
||||||
|
<StyledButton
|
||||||
|
background={design.colors.buttonColor}
|
||||||
|
color={design.colors.buttonTextColor}
|
||||||
|
highlight={design.colors.buttonActiveColor}
|
||||||
|
size={'large'}
|
||||||
|
onClick={next}
|
||||||
|
>{page.buttonText || 'Continue'}</StyledButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
22
components/form/types/date.type.tsx
Normal file
22
components/form/types/date.type.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {DatePicker, Form} from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import {FieldTypeProps} from './type.props'
|
||||||
|
|
||||||
|
export const DateType: React.FC<FieldTypeProps> = ({field}) => {
|
||||||
|
// TODO check min and max
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form.Item
|
||||||
|
label={'Default Date'}
|
||||||
|
name={[field.id, 'value']}
|
||||||
|
rules={[
|
||||||
|
{ required: field.required, message: 'Please provide Information' },
|
||||||
|
]}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
>
|
||||||
|
<DatePicker autoFocus />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
21
components/form/types/dropdown.type.tsx
Normal file
21
components/form/types/dropdown.type.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import {Form, Input} from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import {FieldTypeProps} from './type.props'
|
||||||
|
|
||||||
|
export const DropdownType: React.FC<FieldTypeProps> = ({field}) => {
|
||||||
|
// TODO add dropdown options
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form.Item
|
||||||
|
label={'Default Value'}
|
||||||
|
name={[field.id, 'value']}
|
||||||
|
rules={[
|
||||||
|
{ required: field.required, message: 'Please provide Information' },
|
||||||
|
]}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
21
components/form/types/email.type.tsx
Normal file
21
components/form/types/email.type.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import {Form, Input} from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import {FieldTypeProps} from './type.props'
|
||||||
|
|
||||||
|
export const EmailType: React.FC<FieldTypeProps> = ({field}) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form.Item
|
||||||
|
label={'Default Email'}
|
||||||
|
name={[field.id, 'value']}
|
||||||
|
rules={[
|
||||||
|
{ required: field.required, message: 'Please provide Information' },
|
||||||
|
{ type: 'email', message: 'Must be a valid email' }
|
||||||
|
]}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
>
|
||||||
|
<Input type={'email'} />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
20
components/form/types/hidden.type.tsx
Normal file
20
components/form/types/hidden.type.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import {Form, Input} from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import {FieldTypeProps} from './type.props'
|
||||||
|
|
||||||
|
export const HiddenType: React.FC<FieldTypeProps> = ({field}) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form.Item
|
||||||
|
label={'Default Value'}
|
||||||
|
name={[field.id, 'value']}
|
||||||
|
rules={[
|
||||||
|
{ required: field.required, message: 'Please provide Information' },
|
||||||
|
]}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
29
components/form/types/index.ts
Normal file
29
components/form/types/index.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {DateType} from './date.type'
|
||||||
|
import {DropdownType} from './dropdown.type'
|
||||||
|
import {EmailType} from './email.type'
|
||||||
|
import {HiddenType} from './hidden.type'
|
||||||
|
import {LinkType} from './link.type'
|
||||||
|
import {NumberType} from './number.type'
|
||||||
|
import {RadioType} from './radio.type'
|
||||||
|
import {RatingType} from './rating.type'
|
||||||
|
import {TextType} from './text.type'
|
||||||
|
import {TextareaType} from './textarea.type'
|
||||||
|
import {FieldTypeProps} from './type.props'
|
||||||
|
import {YesNoType} from './yes_no.type'
|
||||||
|
|
||||||
|
export const fieldTypes: {
|
||||||
|
[key: string]: React.FC<FieldTypeProps>
|
||||||
|
} = {
|
||||||
|
'textfield': TextType,
|
||||||
|
'date': DateType,
|
||||||
|
'email': EmailType,
|
||||||
|
'textarea': TextareaType,
|
||||||
|
'link': LinkType,
|
||||||
|
'dropdown': DropdownType,
|
||||||
|
'rating': RatingType,
|
||||||
|
'radio': RadioType,
|
||||||
|
'hidden': HiddenType,
|
||||||
|
'yes_no': YesNoType,
|
||||||
|
'number': NumberType,
|
||||||
|
}
|
||||||
21
components/form/types/link.type.tsx
Normal file
21
components/form/types/link.type.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import {Form, Input} from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import {FieldTypeProps} from './type.props'
|
||||||
|
|
||||||
|
export const LinkType: React.FC<FieldTypeProps> = ({field}) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form.Item
|
||||||
|
label={'Default Link'}
|
||||||
|
name={[field.id, 'value']}
|
||||||
|
rules={[
|
||||||
|
{ required: field.required, message: 'Please provide Information' },
|
||||||
|
{ type: 'url', message: 'Must be a valid URL' }
|
||||||
|
]}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
>
|
||||||
|
<Input type={'url'} />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
21
components/form/types/number.type.tsx
Normal file
21
components/form/types/number.type.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import {Form, InputNumber} from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import {FieldTypeProps} from './type.props'
|
||||||
|
|
||||||
|
export const NumberType: React.FC<FieldTypeProps> = ({field}) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form.Item
|
||||||
|
label={'Default Number'}
|
||||||
|
name={[field.id, 'value']}
|
||||||
|
rules={[
|
||||||
|
{ type: 'number', message: 'Must be a valid URL' },
|
||||||
|
{ required: field.required, message: 'Please provide Information' },
|
||||||
|
]}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
>
|
||||||
|
<InputNumber />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
22
components/form/types/radio.type.tsx
Normal file
22
components/form/types/radio.type.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {Form, Input} from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import {FieldTypeProps} from './type.props'
|
||||||
|
|
||||||
|
export const RadioType: React.FC<FieldTypeProps> = ({field}) => {
|
||||||
|
// TODO Add radio support
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form.Item
|
||||||
|
label={'Default Value'}
|
||||||
|
name={[field.id, 'value']}
|
||||||
|
rules={[
|
||||||
|
{ required: field.required, message: 'Please provide Information' },
|
||||||
|
]}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
22
components/form/types/rating.type.tsx
Normal file
22
components/form/types/rating.type.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {Form, Input} from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import {FieldTypeProps} from './type.props'
|
||||||
|
|
||||||
|
export const RatingType: React.FC<FieldTypeProps> = ({field}) => {
|
||||||
|
// TODO add ratings
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form.Item
|
||||||
|
label={'Default Value'}
|
||||||
|
name={[field.id, 'value']}
|
||||||
|
rules={[
|
||||||
|
{ required: field.required, message: 'Please provide Information' },
|
||||||
|
]}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
28
components/form/types/text.type.tsx
Normal file
28
components/form/types/text.type.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import {Form} from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import {StyledInput} from '../../styled/input'
|
||||||
|
import {FieldTypeProps} from './type.props'
|
||||||
|
|
||||||
|
export const TextType: React.FC<FieldTypeProps> = ({field, design}) => {
|
||||||
|
// TODO focus when becomes visible
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form.Item
|
||||||
|
name={[field.id, 'value']}
|
||||||
|
rules={[
|
||||||
|
{ required: field.required, message: 'Please provide Information' }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<StyledInput
|
||||||
|
design={design}
|
||||||
|
allowClear
|
||||||
|
size={'large'}
|
||||||
|
defaultValue={field.value}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
20
components/form/types/textarea.type.tsx
Normal file
20
components/form/types/textarea.type.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import {Form, Input} from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import {FieldTypeProps} from './type.props'
|
||||||
|
|
||||||
|
export const TextareaType: React.FC<FieldTypeProps> = ({field}) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form.Item
|
||||||
|
label={'Default Value'}
|
||||||
|
name={[field.id, 'value']}
|
||||||
|
rules={[
|
||||||
|
{ required: field.required, message: 'Please provide Information' },
|
||||||
|
]}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
>
|
||||||
|
<Input.TextArea autoSize />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
6
components/form/types/type.props.ts
Normal file
6
components/form/types/type.props.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import {FormDesignFragment, FormFieldFragment} from '../../../graphql/fragment/form.fragment'
|
||||||
|
|
||||||
|
export interface FieldTypeProps {
|
||||||
|
field: FormFieldFragment
|
||||||
|
design: FormDesignFragment
|
||||||
|
}
|
||||||
21
components/form/types/yes_no.type.tsx
Normal file
21
components/form/types/yes_no.type.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import {Form, Input} from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import {FieldTypeProps} from './type.props'
|
||||||
|
|
||||||
|
export const YesNoType: React.FC<FieldTypeProps> = ({field}) => {
|
||||||
|
// TODO add switch
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form.Item
|
||||||
|
label={'Default Value'}
|
||||||
|
name={[field.id, 'value']}
|
||||||
|
rules={[
|
||||||
|
{ required: field.required, message: 'Please provide Information' },
|
||||||
|
]}
|
||||||
|
labelCol={{ span: 6 }}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
29
components/styled/button.tsx
Normal file
29
components/styled/button.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import {Button} from 'antd'
|
||||||
|
import {ButtonProps} from 'antd/lib/button/button'
|
||||||
|
import React from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import {darken, lighten} from './color.change'
|
||||||
|
|
||||||
|
interface Props extends ButtonProps {
|
||||||
|
background: any
|
||||||
|
highlight: any
|
||||||
|
color: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StyledButton: React.FC<Props> = ({background, highlight, color, children, ...props}) => {
|
||||||
|
const StyledButton = styled(Button)`
|
||||||
|
background: ${background};
|
||||||
|
color: ${color};
|
||||||
|
border-color: ${darken(background, 10)};
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
color: ${highlight};
|
||||||
|
background-color: ${lighten(background, 10)};
|
||||||
|
border-color: ${darken(highlight, 10)};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledButton {...props}>{children}</StyledButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
35
components/styled/color.change.ts
Normal file
35
components/styled/color.change.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* @link https://css-tricks.com/snippets/javascript/lighten-darken-color/
|
||||||
|
*
|
||||||
|
* @author Chris Coyier
|
||||||
|
*/
|
||||||
|
function LightenDarkenColor(col, amt) {
|
||||||
|
let usePound = false;
|
||||||
|
|
||||||
|
if (col[0] == "#") {
|
||||||
|
col = col.slice(1);
|
||||||
|
usePound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const num = parseInt(col, 16)
|
||||||
|
|
||||||
|
let r = (num >> 16) + amt;
|
||||||
|
|
||||||
|
if (r > 255) r = 255;
|
||||||
|
else if (r < 0) r = 0;
|
||||||
|
|
||||||
|
let b = ((num >> 8) & 0x00FF) + amt;
|
||||||
|
|
||||||
|
if (b > 255) b = 255;
|
||||||
|
else if (b < 0) b = 0;
|
||||||
|
|
||||||
|
let g = (num & 0x0000FF) + amt;
|
||||||
|
|
||||||
|
if (g > 255) g = 255;
|
||||||
|
else if (g < 0) g = 0;
|
||||||
|
|
||||||
|
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const lighten = (color: string, amount: number) => LightenDarkenColor(color, amount)
|
||||||
|
export const darken = (color: string, amount: number) => LightenDarkenColor(color, -amount)
|
||||||
18
components/styled/h1.tsx
Normal file
18
components/styled/h1.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type: 'question' | 'answer'
|
||||||
|
design: FormDesignFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StyledH1: React.FC<Props> = ({design, type, children, ...props}) => {
|
||||||
|
const Header = styled.h1`
|
||||||
|
color: ${type === 'question' ? design.colors.questionColor : design.colors.answerColor}
|
||||||
|
`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Header {...props}>{children}</Header>
|
||||||
|
)
|
||||||
|
}
|
||||||
18
components/styled/h2.tsx
Normal file
18
components/styled/h2.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type: 'question' | 'answer'
|
||||||
|
design: FormDesignFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StyledH2: React.FC<Props> = ({design, type, children, ...props}) => {
|
||||||
|
const Header = styled.h2`
|
||||||
|
color: ${type === 'question' ? design.colors.questionColor : design.colors.answerColor}
|
||||||
|
`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Header {...props}>{children}</Header>
|
||||||
|
)
|
||||||
|
}
|
||||||
57
components/styled/input.tsx
Normal file
57
components/styled/input.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import {Input} from 'antd'
|
||||||
|
import {InputProps} from 'antd/lib/input/Input'
|
||||||
|
import React, {useEffect, useState} from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
|
||||||
|
|
||||||
|
interface Props extends InputProps{
|
||||||
|
design: FormDesignFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StyledInput: React.FC<Props> = ({design, children, ...props}) => {
|
||||||
|
const [Field, setField] = useState()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setField(
|
||||||
|
styled(Input)`
|
||||||
|
color: ${design.colors.answerColor};
|
||||||
|
border-color: ${design.colors.answerColor};
|
||||||
|
background: none !important;
|
||||||
|
border-right: none;
|
||||||
|
border-top: none;
|
||||||
|
border-left: none;
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
:focus {
|
||||||
|
outline: ${design.colors.answerColor} auto 5px
|
||||||
|
}
|
||||||
|
|
||||||
|
:hover,
|
||||||
|
:active {
|
||||||
|
border-color: ${design.colors.answerColor};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ant-input-affix-wrapper {
|
||||||
|
box-shadow: none
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
background: none !important;
|
||||||
|
color: ${design.colors.answerColor};
|
||||||
|
}
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
color: ${design.colors.answerColor};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)
|
||||||
|
}, [design])
|
||||||
|
|
||||||
|
if (!Field) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field {...props}>{children}</Field>
|
||||||
|
)
|
||||||
|
}
|
||||||
18
components/styled/p.tsx
Normal file
18
components/styled/p.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import {FormDesignFragment} from '../../graphql/fragment/form.fragment'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type: 'question' | 'answer'
|
||||||
|
design: FormDesignFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StyledP: React.FC<Props> = ({design, type, children, ...props}) => {
|
||||||
|
const Paragraph = styled.p`
|
||||||
|
color: ${type === 'question' ? design.colors.questionColor : design.colors.answerColor}
|
||||||
|
`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paragraph {...props}>{children}</Paragraph>
|
||||||
|
)
|
||||||
|
}
|
||||||
0
graphql/fragment/admin.user.fragment.ts
Normal file
0
graphql/fragment/admin.user.fragment.ts
Normal file
109
graphql/fragment/form.fragment.ts
Normal file
109
graphql/fragment/form.fragment.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import {gql} from 'apollo-boost'
|
||||||
|
|
||||||
|
export interface FormPageFragment {
|
||||||
|
show: boolean
|
||||||
|
title?: string
|
||||||
|
paragraph?: string
|
||||||
|
buttonText?: string
|
||||||
|
buttons: {
|
||||||
|
url?: string
|
||||||
|
action?: string
|
||||||
|
text?: string
|
||||||
|
bgColor?: string
|
||||||
|
activeColor?: string
|
||||||
|
color?: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormFieldFragment {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
type: string
|
||||||
|
description: string
|
||||||
|
required: boolean
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormDesignFragment {
|
||||||
|
colors: {
|
||||||
|
backgroundColor: string
|
||||||
|
questionColor: string
|
||||||
|
answerColor: string
|
||||||
|
buttonColor: string
|
||||||
|
buttonActiveColor: string
|
||||||
|
buttonTextColor: string
|
||||||
|
}
|
||||||
|
font?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormFragment {
|
||||||
|
id?: string
|
||||||
|
title: string
|
||||||
|
created: string
|
||||||
|
language: string
|
||||||
|
showFooter: boolean
|
||||||
|
fields: FormFieldFragment[]
|
||||||
|
design: FormDesignFragment
|
||||||
|
startPage: FormPageFragment
|
||||||
|
endPage: FormPageFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FORM_FRAGMENT = gql`
|
||||||
|
fragment Form on Form {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
language
|
||||||
|
showFooter
|
||||||
|
|
||||||
|
fields {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
type
|
||||||
|
description
|
||||||
|
required
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
design {
|
||||||
|
colors {
|
||||||
|
backgroundColor
|
||||||
|
questionColor
|
||||||
|
answerColor
|
||||||
|
buttonColor
|
||||||
|
buttonActiveColor
|
||||||
|
buttonTextColor
|
||||||
|
}
|
||||||
|
font
|
||||||
|
}
|
||||||
|
|
||||||
|
startPage {
|
||||||
|
show
|
||||||
|
title
|
||||||
|
paragraph
|
||||||
|
buttonText
|
||||||
|
buttons {
|
||||||
|
url
|
||||||
|
action
|
||||||
|
text
|
||||||
|
bgColor
|
||||||
|
activeColor
|
||||||
|
color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endPage {
|
||||||
|
show
|
||||||
|
title
|
||||||
|
paragraph
|
||||||
|
buttonText
|
||||||
|
buttons {
|
||||||
|
url
|
||||||
|
action
|
||||||
|
text
|
||||||
|
bgColor
|
||||||
|
activeColor
|
||||||
|
color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
20
graphql/query/form.query.ts
Normal file
20
graphql/query/form.query.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import {gql} from 'apollo-boost'
|
||||||
|
import {FORM_FRAGMENT, FormFragment} from '../fragment/form.fragment'
|
||||||
|
|
||||||
|
export interface FormQueryData {
|
||||||
|
form: FormFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormQueryVariables {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FORM_QUERY = gql`
|
||||||
|
query form($id: ID!){
|
||||||
|
form:getFormById(id: $id) {
|
||||||
|
...Form
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${FORM_FRAGMENT}
|
||||||
|
`
|
||||||
@ -13,6 +13,7 @@
|
|||||||
"@ant-design/icons": "^4.1.0",
|
"@ant-design/icons": "^4.1.0",
|
||||||
"@apollo/react-hooks": "^3.1.5",
|
"@apollo/react-hooks": "^3.1.5",
|
||||||
"@lifeomic/axios-fetch": "^1.4.2",
|
"@lifeomic/axios-fetch": "^1.4.2",
|
||||||
|
"@types/swiper": "^5.3.1",
|
||||||
"antd": "^4.2.2",
|
"antd": "^4.2.2",
|
||||||
"apollo-boost": "^0.4.9",
|
"apollo-boost": "^0.4.9",
|
||||||
"axios": "^0.19.2",
|
"axios": "^0.19.2",
|
||||||
@ -27,11 +28,14 @@
|
|||||||
"react-color": "^2.18.1",
|
"react-color": "^2.18.1",
|
||||||
"react-dom": "16.13.1",
|
"react-dom": "16.13.1",
|
||||||
"react-icons": "^3.10.0",
|
"react-icons": "^3.10.0",
|
||||||
|
"react-id-swiper": "^3.0.0",
|
||||||
"react-redux": "^7.2.0",
|
"react-redux": "^7.2.0",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"redux-devtools-extension": "^2.13.8",
|
"redux-devtools-extension": "^2.13.8",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"sass": "^1.26.5"
|
"sass": "^1.26.5",
|
||||||
|
"styled-components": "^5.1.1",
|
||||||
|
"swiper": "^5.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^14.0.1",
|
"@types/node": "^14.0.1",
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {NextPage} from 'next'
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import React, {useState} from 'react'
|
import React, {useState} from 'react'
|
||||||
import {DateTime} from '../../../components/date.time'
|
import {DateTime} from '../../../components/date.time'
|
||||||
import {FormIsLive} from '../../../components/form/is.live'
|
import {FormIsLive} from '../../../components/form/admin/is.live'
|
||||||
import Structure from '../../../components/structure'
|
import Structure from '../../../components/structure'
|
||||||
import {TimeAgo} from '../../../components/time.ago'
|
import {TimeAgo} from '../../../components/time.ago'
|
||||||
import {withAuth} from '../../../components/with.auth'
|
import {withAuth} from '../../../components/with.auth'
|
||||||
|
|||||||
3
pages/form/[id]/index.module.scss
Normal file
3
pages/form/[id]/index.module.scss
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
.page {
|
||||||
|
}
|
||||||
@ -1,11 +1,102 @@
|
|||||||
|
import {useQuery} from '@apollo/react-hooks'
|
||||||
import {NextPage} from 'next'
|
import {NextPage} from 'next'
|
||||||
import React from 'react'
|
import React, {useState} from 'react'
|
||||||
|
import Swiper from 'react-id-swiper'
|
||||||
|
import {ReactIdSwiperProps} from 'react-id-swiper/lib/types'
|
||||||
|
import * as OriginalSwiper from 'swiper'
|
||||||
import {ErrorPage} from '../../../components/error.page'
|
import {ErrorPage} from '../../../components/error.page'
|
||||||
|
import {Field} from '../../../components/form/field'
|
||||||
|
import {FormPage} from '../../../components/form/page'
|
||||||
|
import {LoadingPage} from '../../../components/loading.page'
|
||||||
|
import {useWindowSize} from '../../../components/use.window.size'
|
||||||
|
import {FORM_QUERY, FormQueryData, FormQueryVariables} from '../../../graphql/query/form.query'
|
||||||
|
|
||||||
const Index: NextPage = () => {
|
interface Props {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Index: NextPage<Props> = ({id}) => {
|
||||||
|
const windowSize = useWindowSize()
|
||||||
|
const [swiper, setSwiper] = useState<OriginalSwiper.default>(null)
|
||||||
|
|
||||||
|
const {loading, data, error} = useQuery<FormQueryData, FormQueryVariables>(FORM_QUERY, {
|
||||||
|
variables: {
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<LoadingPage message={'Building Form'} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<ErrorPage/>
|
<ErrorPage/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const design = data.form.design
|
||||||
|
|
||||||
|
const goNext = () => {
|
||||||
|
if (!swiper) return
|
||||||
|
|
||||||
|
swiper.allowSlideNext = true
|
||||||
|
swiper.slideNext()
|
||||||
|
swiper.allowSlideNext = false
|
||||||
|
}
|
||||||
|
const goPrev = () => swiper && swiper.slidePrev()
|
||||||
|
|
||||||
|
const swiperConfig: ReactIdSwiperProps = {
|
||||||
|
getSwiper: setSwiper,
|
||||||
|
direction: 'vertical',
|
||||||
|
allowSlideNext: false,
|
||||||
|
allowSlidePrev: true,
|
||||||
|
updateOnWindowResize: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
background: design.colors.backgroundColor,
|
||||||
|
}}>
|
||||||
|
<Swiper {...swiperConfig}>
|
||||||
|
{[
|
||||||
|
<FormPage
|
||||||
|
key={'start'}
|
||||||
|
type={'start'}
|
||||||
|
page={data.form.startPage}
|
||||||
|
design={design}
|
||||||
|
next={goNext}
|
||||||
|
prev={goPrev}
|
||||||
|
/>,
|
||||||
|
...data.form.fields.map(field => (
|
||||||
|
<Field
|
||||||
|
key={field.id}
|
||||||
|
field={field}
|
||||||
|
design={design}
|
||||||
|
next={goNext}
|
||||||
|
prev={goPrev}
|
||||||
|
/>
|
||||||
|
)),
|
||||||
|
<FormPage
|
||||||
|
key={'end'}
|
||||||
|
type={'end'}
|
||||||
|
page={data.form.endPage}
|
||||||
|
design={design}
|
||||||
|
next={goNext}
|
||||||
|
prev={goPrev}
|
||||||
|
/>
|
||||||
|
]}
|
||||||
|
</Swiper>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Index.getInitialProps = async ({query}) => {
|
||||||
|
return {
|
||||||
|
id: query.id as string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default Index
|
export default Index
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user