-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #56 from IQSS/feature/create-the-form-elements-of-…
…the-design-system 31 - Create the form elements of the design system
- Loading branch information
Showing
37 changed files
with
1,302 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { FormEvent, PropsWithChildren } from 'react' | ||
import { FormGroup } from './form-group/FormGroup' | ||
import { Form as FormBS } from 'react-bootstrap' | ||
import { FormGroupWithMultipleFields } from './form-group-multiple-fields/FormGroupWithMultipleFields' | ||
|
||
interface FormProps { | ||
validated?: boolean | ||
onSubmit?: (event: FormEvent<HTMLFormElement>) => void | ||
} | ||
|
||
function Form({ validated, onSubmit, children }: PropsWithChildren<FormProps>) { | ||
return ( | ||
<FormBS validated={validated} onSubmit={onSubmit}> | ||
{children} | ||
</FormBS> | ||
) | ||
} | ||
|
||
Form.Group = FormGroup | ||
Form.GroupWithMultipleFields = FormGroupWithMultipleFields | ||
|
||
export { Form } |
7 changes: 7 additions & 0 deletions
7
src/sections/ui/form/form-group-multiple-fields/FormGroupWithMultipleFields.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
@import "src/sections/ui/assets/styles/design-tokens/typography.module"; | ||
|
||
.title { | ||
padding-top: calc(0.375rem + 1px); | ||
padding-bottom: calc(0.375rem + 1px); | ||
font-weight: $dv-font-weight-bold; | ||
} |
58 changes: 58 additions & 0 deletions
58
src/sections/ui/form/form-group-multiple-fields/FormGroupWithMultipleFields.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { Row } from '../../grid/Row' | ||
import { Col } from '../../grid/Col' | ||
import { PropsWithChildren } from 'react' | ||
import styles from './FormGroupWithMultipleFields.module.scss' | ||
import { RequiredInputSymbol } from '../required-input-symbol/RequiredInputSymbol' | ||
import { DynamicFieldsButtons } from './dynamic-fields-buttons/DynamicFieldsButtons' | ||
import { useFields } from './useFields' | ||
import { Tooltip } from '../../tooltip/Tooltip' | ||
|
||
interface FormGroupWithMultipleFieldsProps { | ||
title: string | ||
withDynamicFields?: boolean | ||
required?: boolean | ||
message?: string | ||
} | ||
|
||
const Title = ({ title, required, message }: Partial<FormGroupWithMultipleFieldsProps>) => ( | ||
<span className={styles.title}> | ||
{title} {required && <RequiredInputSymbol />}{' '} | ||
{message && <Tooltip placement="right" message={message}></Tooltip>} | ||
</span> | ||
) | ||
|
||
export function FormGroupWithMultipleFields({ | ||
title, | ||
withDynamicFields, | ||
required, | ||
message, | ||
children | ||
}: PropsWithChildren<FormGroupWithMultipleFieldsProps>) { | ||
const { fields, addField, removeField } = useFields(children, withDynamicFields) | ||
|
||
return ( | ||
<> | ||
{fields.map((field, index) => { | ||
const isFirstField = index == 0 | ||
|
||
return ( | ||
<Row key={index}> | ||
<Col sm={3}> | ||
{isFirstField && <Title title={title} required={required} message={message} />} | ||
</Col> | ||
<Col sm={6}>{field}</Col> | ||
<Col sm={3}> | ||
{withDynamicFields && ( | ||
<DynamicFieldsButtons | ||
originalField={isFirstField} | ||
onAddButtonClick={() => addField(field)} | ||
onRemoveButtonClick={() => removeField(index)} | ||
/> | ||
)} | ||
</Col> | ||
</Row> | ||
) | ||
})} | ||
</> | ||
) | ||
} |
13 changes: 13 additions & 0 deletions
13
...i/form/form-group-multiple-fields/dynamic-fields-buttons/DynamicFieldsButtons.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
.container { | ||
display: flex; | ||
margin: 1.8em; | ||
} | ||
|
||
.icon { | ||
display: inline-block; | ||
vertical-align: -0.125em; | ||
} | ||
|
||
.overlay-container { | ||
width: fit-content; | ||
} |
38 changes: 38 additions & 0 deletions
38
...ctions/ui/form/form-group-multiple-fields/dynamic-fields-buttons/DynamicFieldsButtons.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { Button } from '../../../button/Button' | ||
import styles from './DynamicFieldsButtons.module.scss' | ||
import { MouseEvent } from 'react' | ||
import { Dash, Plus } from 'react-bootstrap-icons' | ||
import { OverlayTrigger } from '../../../tooltip/overlay-trigger/OverlayTrigger' | ||
|
||
interface AddFieldButtonsProps { | ||
originalField?: boolean | ||
onAddButtonClick: (event: MouseEvent<HTMLButtonElement>) => void | ||
onRemoveButtonClick: (event: MouseEvent<HTMLButtonElement>) => void | ||
} | ||
|
||
export function DynamicFieldsButtons({ | ||
originalField, | ||
onAddButtonClick, | ||
onRemoveButtonClick | ||
}: AddFieldButtonsProps) { | ||
return ( | ||
<div className={styles.container}> | ||
<OverlayTrigger placement="top" message="Add"> | ||
<div className={styles['overlay-container']}> | ||
<Button variant="secondary" onClick={onAddButtonClick}> | ||
<Plus className={styles.icon} title="Add" /> | ||
</Button> | ||
</div> | ||
</OverlayTrigger> | ||
{!originalField && ( | ||
<OverlayTrigger placement="top" message="Delete"> | ||
<div className={styles['overlay-container']}> | ||
<Button variant="secondary" withSpacing onClick={onRemoveButtonClick}> | ||
<Dash className={styles.icon} title="Delete" /> | ||
</Button> | ||
</div> | ||
</OverlayTrigger> | ||
)} | ||
</div> | ||
) | ||
} |
52 changes: 52 additions & 0 deletions
52
src/sections/ui/form/form-group-multiple-fields/useFields.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import React, { ReactElement, ReactNode, useState } from 'react' | ||
import { FormGroup } from '../form-group/FormGroup' | ||
|
||
function getFieldsWithIndex(fields: Array<ReactNode>) { | ||
return fields.map((field, index) => getFieldWithIndex(field, index)) | ||
} | ||
|
||
function getFieldWithIndex(field: ReactNode, fieldIndex: number) { | ||
return React.Children.map(field, (child: ReactNode) => { | ||
if (!React.isValidElement(child)) { | ||
return child | ||
} | ||
|
||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ | ||
const childProps = getPropsWithFieldIndex(child, fieldIndex) | ||
|
||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ | ||
if (child.props.children) { | ||
/* eslint-disable @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument */ | ||
childProps.children = getFieldWithIndex(child.props.children, fieldIndex) | ||
} | ||
|
||
/* eslint-disable @typescript-eslint/no-unsafe-argument */ | ||
return React.cloneElement(child, childProps) | ||
}) | ||
} | ||
|
||
function getPropsWithFieldIndex(child: ReactElement, fieldIndex: number) { | ||
const isFormGroup = (child: ReactNode) => { | ||
return React.isValidElement(child) && child.type === FormGroup | ||
} | ||
|
||
/* eslint-disable @typescript-eslint/no-unsafe-return */ | ||
return isFormGroup(child) | ||
? { ...child.props, fieldIndex: fieldIndex.toString() } | ||
: { ...child.props } | ||
} | ||
|
||
export function useFields(initialField: ReactNode | undefined, withDynamicFields?: boolean) { | ||
const initialFieldWithIndex = withDynamicFields | ||
? getFieldWithIndex(initialField, 0) | ||
: initialField | ||
const [fields, setFields] = useState([initialFieldWithIndex]) | ||
|
||
const addField = (field: ReactNode | undefined) => | ||
setFields(getFieldsWithIndex([...fields, field])) | ||
|
||
const removeField = (fieldIndex: number) => | ||
setFields(getFieldsWithIndex(fields.filter((_, i) => i !== fieldIndex))) | ||
|
||
return { fields, addField, removeField } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import React, { PropsWithChildren } from 'react' | ||
import { Form as FormBS } from 'react-bootstrap' | ||
import { FormInput } from './form-element/FormInput' | ||
import { FormLabel } from './form-element/FormLabel' | ||
import { FormText } from './form-element/FormText' | ||
import { FormSelect } from './form-element/FormSelect' | ||
import { FormTextArea } from './form-element/FormTextArea' | ||
import { Col, ColProps } from '../../grid/Col' | ||
import { Row } from '../../grid/Row' | ||
import { FormCheckbox } from './form-element/FormCheckbox' | ||
|
||
interface FormGroupProps extends ColProps { | ||
as?: typeof Col | typeof Row | ||
required?: boolean | ||
controlId: string | ||
fieldIndex?: string | ||
} | ||
|
||
function FormGroup({ | ||
as = Row, | ||
required, | ||
controlId, | ||
fieldIndex, | ||
children, | ||
...props | ||
}: PropsWithChildren<FormGroupProps>) { | ||
const childrenWithRequiredProp = React.Children.map(children as JSX.Element, (child) => { | ||
return React.cloneElement(child, { | ||
required: required, | ||
withinMultipleFieldsGroup: as === Col | ||
}) | ||
}) | ||
|
||
return ( | ||
<FormBS.Group | ||
controlId={fieldIndex ? `${controlId}-${fieldIndex}` : controlId} | ||
className="mb-3" | ||
as={as} | ||
{...props}> | ||
{childrenWithRequiredProp} | ||
</FormBS.Group> | ||
) | ||
} | ||
|
||
FormGroup.Label = FormLabel | ||
FormGroup.Input = FormInput | ||
FormGroup.Select = FormSelect | ||
FormGroup.TextArea = FormTextArea | ||
FormGroup.Text = FormText | ||
FormGroup.Checkbox = FormCheckbox | ||
|
||
export { FormGroup } |
12 changes: 12 additions & 0 deletions
12
src/sections/ui/form/form-group/form-element/FormCheckbox.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Form as FormBS } from 'react-bootstrap' | ||
import * as React from 'react' | ||
|
||
interface FormCheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> { | ||
id: string | ||
label: string | ||
name: string | ||
} | ||
|
||
export function FormCheckbox({ label, name, id, ...props }: FormCheckboxProps) { | ||
return <FormBS.Check label={label} name={name} type="checkbox" id={id} {...props} /> | ||
} |
12 changes: 12 additions & 0 deletions
12
src/sections/ui/form/form-group/form-element/FormElementLayout.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { PropsWithChildren } from 'react' | ||
import { Col } from '../../../grid/Col' | ||
|
||
interface LayoutFormGroupElementProps { | ||
withinMultipleFieldsGroup?: boolean | ||
} | ||
export function FormElementLayout({ | ||
withinMultipleFieldsGroup, | ||
children | ||
}: PropsWithChildren<LayoutFormGroupElementProps>) { | ||
return withinMultipleFieldsGroup ? <>{children}</> : <Col sm={9}>{children}</Col> | ||
} |
40 changes: 40 additions & 0 deletions
40
src/sections/ui/form/form-group/form-element/FormInput.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { Form as FormBS, InputGroup } from 'react-bootstrap' | ||
import { FormElementLayout } from './FormElementLayout' | ||
import { PropsWithChildren } from 'react' | ||
import * as React from 'react' | ||
|
||
export type FormInputElement = HTMLInputElement | HTMLTextAreaElement | ||
|
||
interface FormInputProps extends React.HTMLAttributes<FormInputElement> { | ||
type?: 'text' | 'email' | 'password' | ||
readOnly?: boolean | ||
prefix?: string | ||
withinMultipleFieldsGroup?: boolean | ||
} | ||
|
||
export function FormInput({ | ||
type = 'text', | ||
readOnly, | ||
prefix, | ||
withinMultipleFieldsGroup, | ||
...props | ||
}: FormInputProps) { | ||
const FormInputPrefix = ({ children }: PropsWithChildren) => { | ||
return prefix ? ( | ||
<InputGroup className="mb-3"> | ||
<InputGroup.Text>{prefix}</InputGroup.Text> | ||
{children} | ||
</InputGroup> | ||
) : ( | ||
<>{children}</> | ||
) | ||
} | ||
|
||
return ( | ||
<FormElementLayout withinMultipleFieldsGroup={withinMultipleFieldsGroup}> | ||
<FormInputPrefix> | ||
<FormBS.Control type={type} readOnly={readOnly} plaintext={readOnly} {...props} /> | ||
</FormInputPrefix> | ||
</FormElementLayout> | ||
) | ||
} |
27 changes: 27 additions & 0 deletions
27
src/sections/ui/form/form-group/form-element/FormLabel.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { PropsWithChildren } from 'react' | ||
import { Form as FormBS } from 'react-bootstrap' | ||
import { RequiredInputSymbol } from '../../required-input-symbol/RequiredInputSymbol' | ||
import { Tooltip } from '../../../tooltip/Tooltip' | ||
|
||
interface FormLabelProps { | ||
required?: boolean | ||
message?: string | ||
withinMultipleFieldsGroup?: boolean | ||
} | ||
|
||
export function FormLabel({ | ||
required, | ||
message, | ||
withinMultipleFieldsGroup, | ||
children | ||
}: PropsWithChildren<FormLabelProps>) { | ||
const layoutProps = withinMultipleFieldsGroup ? {} : { column: true, sm: 3 } | ||
|
||
return ( | ||
<FormBS.Label {...layoutProps}> | ||
{children} | ||
{required && <RequiredInputSymbol />}{' '} | ||
{message && <Tooltip placement="right" message={message}></Tooltip>} | ||
</FormBS.Label> | ||
) | ||
} |
Oops, something went wrong.