Skip to content

Commit

Permalink
FormGenerator - expandable groups
Browse files Browse the repository at this point in the history
  • Loading branch information
PatrikMatiasko committed May 15, 2024
1 parent 5656b5c commit e139a8b
Show file tree
Hide file tree
Showing 16 changed files with 289 additions and 40 deletions.
1 change: 1 addition & 0 deletions src/components/Atomic/Modal/Modal.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const content = (theme: ThemeType) => css`
export const contentPadding = css`
padding-top: 24px;
`

export const footer = (theme: ThemeType) => css`
padding: 24px 24px 0 24px;
background: ${getThemeColor(theme, `Modal.footer.background`)};
Expand Down
2 changes: 2 additions & 0 deletions src/components/Atomic/TimeoutControl/TimeoutControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const TimeoutControlCore: FC<Props> = (props) => {
size,
smallMode,
unitClassName,
unitMenuPortalTarget,
watchUnitChange,
} = props
const closestUnit = useMemo(() => findClosestUnit(defaultValue), [defaultValue])
Expand Down Expand Up @@ -165,6 +166,7 @@ export const TimeoutControlCore: FC<Props> = (props) => {
defaultValue={units.filter((option) => option.value === unit)}
disabled={disabled}
inlineStyle={inlineStyle}
menuPortalTarget={unitMenuPortalTarget}
name='unit'
onChange={(e) => {
handleOnUnitChange(e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ export type Props = {
smallMode?: boolean
ttlHasError?: boolean
unitClassName?: string
unitMenuPortalTarget?: HTMLElement
watchUnitChange?: boolean
} & Pick<InputProps, 'inlineStyle' | 'align' | 'size'>
6 changes: 3 additions & 3 deletions src/components/Atomic/Tooltip/TooltipUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export const TooltipContent = forwardRef(function TooltipContent(
arrowElement.current.style[staticSide] = '-4px'
}

const { portalTarget, error, maxWidth, ...rest } = props
const { portalTarget, error, maxWidth, zIndex, ...rest } = props

return (
<ConditionalWrapper condition={!!portalTarget} wrapper={(c) => <FloatingPortal root={portalTarget}>{c}</FloatingPortal>}>
Expand All @@ -153,8 +153,8 @@ export const TooltipContent = forwardRef(function TooltipContent(
position: state.strategy,
top: state.y ?? 0,
left: state.x ?? 0,
maxWidth: maxWidth,
zIndex: props.zIndex,
maxWidth,
zIndex,
...rest.style,
}}
transition={
Expand Down
10 changes: 1 addition & 9 deletions src/components/Layout/LeftPanel/LeftPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,15 +222,7 @@ const LeftPanel: FC<Props> = (props) => {
</a>,
document.getElementById(headerIconCollapsePortalTargetId!) as Element
)}
{logo && (
<div css={[styles.logo, collapsed && styles.logoCollapsed]}>
{logo}
{/* {logo &&*/}
{/* cloneElement(logo as ReactElement, {*/}
{/* css: [styles.logoSvg, collapsed && styles.logoSvgCollapsed],*/}
{/* })}*/}
</div>
)}
{logo && <div css={[styles.logo, collapsed && styles.logoCollapsed]}>{logo}</div>}
<div css={[styles.menu, collapsed && styles.menuCollapsed]}>
<ul css={styles.menuList}>
{menu?.map(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { css } from '@emotion/react'
import { getThemeColor, ThemeType } from '../../Atomic/_theme'

export const flexLine = css`
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
`

export const contentHeadline = (theme: ThemeType) => css`
background: ${getThemeColor(theme, `Modal.content.background`)};
position: relative;
z-index: 2;
`
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import GeneratedResourceForm from '../GeneratedResourceForm'
import Spacer from '../../Atomic/Spacer'
import Headline from '../../Atomic/Headline'
import Loadable from '../../Atomic/Loadable'
import * as styles from './DevicesResourcesModal.styles'
import { knownResourceHref } from '../GeneratedResourceForm/constants'

const { UPDATE_RESOURCE } = resourceModalTypes

Expand Down Expand Up @@ -179,6 +181,7 @@ const DevicesResourcesModal: FC<Props> = (props) => {
<FormSelect
disabled={disabled || !isDeviceOnline}
menuPortalTarget={document.body}
menuZIndex={100}
onChange={setSelectedInterface}
options={interfaces}
size='small'
Expand All @@ -199,16 +202,13 @@ const DevicesResourcesModal: FC<Props> = (props) => {
<>
<InnerContent />

<Spacer type='pt-4'>
<Headline type='h5'>{i18n.content}</Headline>
<Spacer css={styles.flexLine} type='py-4'>
<Headline css={styles.contentHeadline} type='h5'>
{i18n.content}
</Headline>
{hasGeneratedForm && <Switch checked={advancedView} label={i18n.advancedView} onChange={() => setAdvancedView(!advancedView)} />}
</Spacer>

{hasGeneratedForm && (
<Spacer type='py-4'>
<Switch checked={advancedView} label={i18n.advancedView} onChange={() => setAdvancedView(!advancedView)} />
</Spacer>
)}

<Loadable condition={advancedView !== undefined}>
<ContentSwitch activeItem={advancedView ? 0 : 1}>
<Spacer style={{ height: 'calc(100% - 20px)' }} type='pt-5'>
Expand Down Expand Up @@ -236,7 +236,7 @@ const DevicesResourcesModal: FC<Props> = (props) => {
resetFormKey={reset}
setFormError={setFormError}
setIsEditable={setIsEditable}
values={resourceData?.data?.content}
values={href === knownResourceHref.WELL_KNOW_WOT ? resourceData?.data?.content : resourceData?.data?.content?.properties}
/>
) : (
<div />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@ import get from 'lodash/get'
import isObject from 'lodash/isObject'
import isFunction from 'lodash/isFunction'
import set from 'lodash/set'
import { AnimatePresence, motion } from 'framer-motion'

import { PropertiesType, Property } from '../GeneratedResourceForm.types'
import FormInput from '../../../Atomic/FormInput'
import IconNumbers from '../../../Atomic/Icon/components/IconNumbers'
import Switch from '../../../Atomic/Switch'
import Spacer from '../../../Atomic/Spacer'
import Headline from '../../../Atomic/Headline'
import { ModalStrippedLine } from '../../../Atomic/Modal'
import FormGroup from '../../../Atomic/FormGroup'
import { Props } from './FormGenerator.types'
import { knownResourceHref } from '../constants'
import FormGeneratorLine from './components/FormGeneratorLine'
import Editor from '../../../Atomic/Editor'
import SubHeadline from './components/SubHeadline'

export const sortProperties = (properties: PropertiesType) =>
properties
Expand All @@ -36,7 +37,7 @@ export const sortProperties = (properties: PropertiesType) =>
)
: []

export const getHref = (parentHref: string, href: string) => `${parentHref !== '' ? parentHref + '/' : parentHref}${href}`
export const getHref = (parentHref: string, href: string) => `${parentHref !== '' && !parentHref.startsWith('/') ? parentHref + '/' : parentHref}${href}`

const FormGenerator: FC<Props> = (props) => {
const { href: topHref, properties, resetFormKey, schema, values, onChange, setFormError } = props
Expand All @@ -46,7 +47,9 @@ const FormGenerator: FC<Props> = (props) => {
const isSingleValueMode = useMemo(() => !isObject(values), [values])
const formDefaultValues = useMemo(() => (isSingleValueMode ? values : { [topHref]: values }), [isSingleValueMode, topHref, values])
const [hasError, setHasError] = useState(false)
const [firstRender, setFirstRender] = useState(true)
const [editorErrors, setEditorErrors] = useState<string[]>([])
const [expandedGroups, setExpandedGroups] = useState<string[]>([])

const {
control,
Expand All @@ -59,7 +62,10 @@ const FormGenerator: FC<Props> = (props) => {
}, [reset, resetFormKey])

const getDefaultValue = useCallback(
(href: string) => (isSingleValueMode ? values : get(values, href.replace(topHref, '').substring(1).replace('/', '.'), '')),
(href: string) => {
const valuesBase = href === knownResourceHref.WELL_KNOW_WOT ? values.properties : values
return isSingleValueMode ? values : get(valuesBase, href.replace(topHref, '').substring(1).replace('/', '.'), '')
},
[isSingleValueMode, topHref, values]
)

Expand Down Expand Up @@ -104,20 +110,22 @@ const FormGenerator: FC<Props> = (props) => {
}, [editorErrors, hasError, isValid, setFormError])

useEffect(() => {
let firstGroup = true
const buildProperties: any = (properties: PropertiesType, parentHref = '', readOnly = false, depth = 1) =>
sortProperties(properties).map((property: Property & { href: string }, k) => {
const href = getHref(parentHref, property.href)
const key = `${href}-${k}-${depth}-${property.title}`
const readOnlyP = property.readOnly || readOnly
const defaultValue = getDefaultValue(href)
const isLast = k === Object.keys(properties).length - 1

if (property.href === knownResourceHref.WELL_KNOW_WOT) {
return buildProperties(property.properties, href, readOnlyP, depth)
}

if (['string', 'integer', 'number'].includes(property.type)) {
const isNumber = ['integer', 'number'].includes(property.type)
const formatValue = (value?: string) => (isNumber && value ? parseInt(value, 10) : value)
const formatValue = (value?: string) => (isNumber && value ? parseFloat(value) : value)

return (
<Controller
Expand All @@ -126,8 +134,7 @@ const FormGenerator: FC<Props> = (props) => {
key={key}
name={href}
render={({ field }) => (
<ModalStrippedLine
smallPadding
<FormGeneratorLine
component={
<FormGroup
errorTooltip
Expand All @@ -152,6 +159,7 @@ const FormGenerator: FC<Props> = (props) => {
/>
</FormGroup>
}
isLast={isLast}
label={property.title || property.href}
/>
)}
Expand All @@ -165,8 +173,7 @@ const FormGenerator: FC<Props> = (props) => {
key={key}
name={href}
render={({ field }) => (
<ModalStrippedLine
smallPadding
<FormGeneratorLine
component={
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Switch
Expand All @@ -180,21 +187,59 @@ const FormGenerator: FC<Props> = (props) => {
/>
</div>
}
isLast={isLast}
label={property.title || property.href}
/>
)}
/>
)
} else if (property.type === 'object') {
if (property.properties) {
const show = expandedGroups.includes(key)

if (firstGroup && expandedGroups.length === 0 && firstRender) {
firstGroup = false
setExpandedGroups([...expandedGroups, key])
}

return (
<Spacer key={key} type='mb-5 mt-8'>
<Spacer type='mb-4'>
<Headline style={{ textTransform: 'capitalize' }} type='h5'>
{property.title || property.href}
</Headline>
</Spacer>
<Spacer type={`pl-${5 * depth}`}>{buildProperties(property.properties, href, readOnlyP, depth + 1)}</Spacer>
<Spacer key={key} type='mb-4 mt-4'>
<FormGeneratorLine
component={
<>
<SubHeadline
hasLine
expanded={show}
headline={property.title || property.href}
onToggle={() => {
if (expandedGroups.includes(key)) {
setExpandedGroups(expandedGroups.filter((group) => group !== key))
} else {
setExpandedGroups([...expandedGroups, key])
}
}}
/>
<AnimatePresence>
{show && (
<motion.div
animate={{ opacity: 1, height: 'auto' }}
exit={{
opacity: 0,
height: 0,
}}
initial={{ opacity: 0, height: 0 }}
transition={{
duration: 0.3,
}}
>
{buildProperties(property.properties, href, readOnlyP, depth + 1)}
</motion.div>
)}
</AnimatePresence>
</>
}
isLast={isLast}
/>
</Spacer>
)
} else {
Expand All @@ -205,8 +250,7 @@ const FormGenerator: FC<Props> = (props) => {
key={key}
name={href}
render={({ field }) => (
<ModalStrippedLine
smallPadding
<FormGeneratorLine
component={
<Editor
height='200px'
Expand All @@ -233,8 +277,9 @@ const FormGenerator: FC<Props> = (props) => {

if (properties) {
setComponents(buildProperties(properties))
setFirstRender(false)
}
}, [control, errors, getDefaultValue, handleEditorChange, handleValueChange, properties])
}, [control, errors, expandedGroups, firstRender, getDefaultValue, handleEditorChange, handleValueChange, properties])

return <>{components}</>
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { css } from '@emotion/react'

export const wrapper = css`
position: relative;
`

export const line = css`
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0;
`

export const lineImg = css`
position: absolute;
bottom: -15px;
left: -20px;
background: #f2f3f5;
height: calc(100% + 30px);
width: 2px;
&:after {
content: '';
position: absolute;
width: 14px;
height: 10px;
bottom: 50%;
left: 0;
border-left: 2px solid #f2f3f5;
border-bottom: 2px solid #f2f3f5;
border-bottom-left-radius: 20px;
}
`

export const isLast = css`
height: calc(50% + 20px) !important;
bottom: calc(50% + 8px) !important;
&:after {
bottom: -8px !important;
}
`

export const noBefore = css`
&:after {
display: none;
}
`

export const lastGroup = css`
height: 0 !important;
`
Loading

0 comments on commit e139a8b

Please sign in to comment.