diff --git a/src/components/Atomic/Modal/Modal.styles.ts b/src/components/Atomic/Modal/Modal.styles.ts index 01d05377..d531a359 100644 --- a/src/components/Atomic/Modal/Modal.styles.ts +++ b/src/components/Atomic/Modal/Modal.styles.ts @@ -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`)}; diff --git a/src/components/Atomic/TimeoutControl/TimeoutControl.tsx b/src/components/Atomic/TimeoutControl/TimeoutControl.tsx index 03bf2ced..a2b8b573 100644 --- a/src/components/Atomic/TimeoutControl/TimeoutControl.tsx +++ b/src/components/Atomic/TimeoutControl/TimeoutControl.tsx @@ -33,6 +33,7 @@ export const TimeoutControlCore: FC = (props) => { size, smallMode, unitClassName, + unitMenuPortalTarget, watchUnitChange, } = props const closestUnit = useMemo(() => findClosestUnit(defaultValue), [defaultValue]) @@ -165,6 +166,7 @@ export const TimeoutControlCore: FC = (props) => { defaultValue={units.filter((option) => option.value === unit)} disabled={disabled} inlineStyle={inlineStyle} + menuPortalTarget={unitMenuPortalTarget} name='unit' onChange={(e) => { handleOnUnitChange(e) diff --git a/src/components/Atomic/TimeoutControl/TimeoutControl.types.ts b/src/components/Atomic/TimeoutControl/TimeoutControl.types.ts index 66b1966d..f2fcf6c1 100644 --- a/src/components/Atomic/TimeoutControl/TimeoutControl.types.ts +++ b/src/components/Atomic/TimeoutControl/TimeoutControl.types.ts @@ -25,5 +25,6 @@ export type Props = { smallMode?: boolean ttlHasError?: boolean unitClassName?: string + unitMenuPortalTarget?: HTMLElement watchUnitChange?: boolean } & Pick diff --git a/src/components/Atomic/Tooltip/TooltipUtils.tsx b/src/components/Atomic/Tooltip/TooltipUtils.tsx index b4c0f3d8..8aebd565 100644 --- a/src/components/Atomic/Tooltip/TooltipUtils.tsx +++ b/src/components/Atomic/Tooltip/TooltipUtils.tsx @@ -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 ( {c}}> @@ -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={ diff --git a/src/components/Layout/LeftPanel/LeftPanel.tsx b/src/components/Layout/LeftPanel/LeftPanel.tsx index d6a11c85..869c78e7 100644 --- a/src/components/Layout/LeftPanel/LeftPanel.tsx +++ b/src/components/Layout/LeftPanel/LeftPanel.tsx @@ -222,15 +222,7 @@ const LeftPanel: FC = (props) => { , document.getElementById(headerIconCollapsePortalTargetId!) as Element )} - {logo && ( -
- {logo} - {/* {logo &&*/} - {/* cloneElement(logo as ReactElement, {*/} - {/* css: [styles.logoSvg, collapsed && styles.logoSvgCollapsed],*/} - {/* })}*/} -
- )} + {logo &&
{logo}
}
    {menu?.map( diff --git a/src/components/Organisms/DevicesResourcesModal/DevicesResourcesModal.styles.ts b/src/components/Organisms/DevicesResourcesModal/DevicesResourcesModal.styles.ts new file mode 100644 index 00000000..1207420b --- /dev/null +++ b/src/components/Organisms/DevicesResourcesModal/DevicesResourcesModal.styles.ts @@ -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; +` diff --git a/src/components/Organisms/DevicesResourcesModal/DevicesResourcesModal.tsx b/src/components/Organisms/DevicesResourcesModal/DevicesResourcesModal.tsx index f0d369c8..9bdc5a3d 100644 --- a/src/components/Organisms/DevicesResourcesModal/DevicesResourcesModal.tsx +++ b/src/components/Organisms/DevicesResourcesModal/DevicesResourcesModal.tsx @@ -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 @@ -179,6 +181,7 @@ const DevicesResourcesModal: FC = (props) => { = (props) => { <> - - {i18n.content} + + + {i18n.content} + + {hasGeneratedForm && setAdvancedView(!advancedView)} />} - {hasGeneratedForm && ( - - setAdvancedView(!advancedView)} /> - - )} - @@ -236,7 +236,7 @@ const DevicesResourcesModal: FC = (props) => { resetFormKey={reset} setFormError={setFormError} setIsEditable={setIsEditable} - values={resourceData?.data?.content} + values={href === knownResourceHref.WELL_KNOW_WOT ? resourceData?.data?.content : resourceData?.data?.content?.properties} /> ) : (
    diff --git a/src/components/Organisms/GeneratedResourceForm/FormGenerator/FormGenerator.tsx b/src/components/Organisms/GeneratedResourceForm/FormGenerator/FormGenerator.tsx index b2650919..3fdb0817 100644 --- a/src/components/Organisms/GeneratedResourceForm/FormGenerator/FormGenerator.tsx +++ b/src/components/Organisms/GeneratedResourceForm/FormGenerator/FormGenerator.tsx @@ -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 @@ -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) => { const { href: topHref, properties, resetFormKey, schema, values, onChange, setFormError } = props @@ -46,7 +47,9 @@ const FormGenerator: FC = (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([]) + const [expandedGroups, setExpandedGroups] = useState([]) const { control, @@ -59,7 +62,10 @@ const FormGenerator: FC = (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] ) @@ -104,12 +110,14 @@ const FormGenerator: FC = (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) @@ -117,7 +125,7 @@ const FormGenerator: FC = (props) => { 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 ( = (props) => { key={key} name={href} render={({ field }) => ( - = (props) => { /> } + isLast={isLast} label={property.title || property.href} /> )} @@ -165,8 +173,7 @@ const FormGenerator: FC = (props) => { key={key} name={href} render={({ field }) => ( - = (props) => { />
    } + isLast={isLast} label={property.title || property.href} /> )} @@ -187,14 +195,51 @@ const FormGenerator: FC = (props) => { ) } 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 ( - - - - {property.title || property.href} - - - {buildProperties(property.properties, href, readOnlyP, depth + 1)} + + + { + if (expandedGroups.includes(key)) { + setExpandedGroups(expandedGroups.filter((group) => group !== key)) + } else { + setExpandedGroups([...expandedGroups, key]) + } + }} + /> + + {show && ( + + {buildProperties(property.properties, href, readOnlyP, depth + 1)} + + )} + + + } + isLast={isLast} + /> ) } else { @@ -205,8 +250,7 @@ const FormGenerator: FC = (props) => { key={key} name={href} render={({ field }) => ( - = (props) => { if (properties) { setComponents(buildProperties(properties)) + setFirstRender(false) } - }, [control, errors, getDefaultValue, handleEditorChange, handleValueChange, properties]) + }, [control, errors, expandedGroups, firstRender, getDefaultValue, handleEditorChange, handleValueChange, properties]) return <>{components} } diff --git a/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/FormGeneratorLine.styles.ts b/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/FormGeneratorLine.styles.ts new file mode 100644 index 00000000..df864516 --- /dev/null +++ b/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/FormGeneratorLine.styles.ts @@ -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; +` diff --git a/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/FormGeneratorLine.tsx b/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/FormGeneratorLine.tsx new file mode 100644 index 00000000..62c2fe9e --- /dev/null +++ b/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/FormGeneratorLine.tsx @@ -0,0 +1,32 @@ +import React, { FC } from 'react' + +import Row from '../../../../Atomic/Grid/Row' +import Column from '../../../../Atomic/Grid/Column' +import Spacer from '../../../../Atomic/Spacer' +import * as ModalStrippedLineStyles from '../../../../Atomic/Modal/ModalStrippedLine/ModalStrippedLine.styles' +import * as styles from './FormGeneratorLine.styles' +import { Props } from './FormGeneratorLine.types' + +const FormGeneratorLine: FC = (props) => ( + +
    +
    + {props.label ? ( + + + {props.label} + + + {props.component} + + + ) : ( + props.component + )} +
    +
    +) + +FormGeneratorLine.displayName = 'FormGeneratorLine' + +export default FormGeneratorLine diff --git a/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/FormGeneratorLine.types.ts b/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/FormGeneratorLine.types.ts new file mode 100644 index 00000000..e9afdd2e --- /dev/null +++ b/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/FormGeneratorLine.types.ts @@ -0,0 +1,7 @@ +import { ReactNode } from 'react' + +export type Props = { + component: ReactNode + isLast?: boolean + label?: string +} diff --git a/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/SubHeadline.styles.ts b/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/SubHeadline.styles.ts new file mode 100644 index 00000000..bf50fdf8 --- /dev/null +++ b/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/SubHeadline.styles.ts @@ -0,0 +1,60 @@ +import { css } from '@emotion/react' +import { getTheme, getThemeColor, ThemeType } from '../../../../Atomic/_theme' + +export const wrapper = css` + position: relative; +` + +export const headline = (theme: ThemeType) => css` + text-transform: capitalize; + background: ${getThemeColor(theme, `Modal.content.background`)}; + position: relative; + z-index: 2; +` + +export const headlineLine = css` + display: inline-flex; + align-items: center; + gap: 12px; + + &:hover { + text-decoration: none !important; + } +` + +export const line = css` + position: absolute; + bottom: calc(50% + 8px) !important; + left: -20px; + background: #f2f3f5; + height: calc(100% + 20px); + width: 2px; + z-index: 1; + + &:after { + content: ''; + position: absolute; + width: 14px; + height: 10px; + bottom: -8px; + left: 0; + border-left: 2px solid #f2f3f5; + border-bottom: 2px solid #f2f3f5; + border-bottom-left-radius: 20px; + } +` + +export const icon = (theme: ThemeType) => css` + transform: rotate(180deg); + transition: all 0.3s; + display: block; + color: ${getTheme(theme, `Global.iconColor`)}; + + &:hover { + color: ${getTheme(theme, `colorPalette.primary`)}; + } +` + +export const iconExpanded = css` + transform: rotate(0deg); +` diff --git a/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/SubHeadline.tsx b/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/SubHeadline.tsx new file mode 100644 index 00000000..39f7d1ec --- /dev/null +++ b/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/SubHeadline.tsx @@ -0,0 +1,36 @@ +import React, { FC } from 'react' +import isFunction from 'lodash/isFunction' + +import Spacer from '../../../../Atomic/Spacer' +import Headline from '../../../../Atomic/Headline' +import { Props } from './SubHeadline.types' +import * as styles from './SubHeadline.styles' +import IconArrowDown from '../../../../Atomic/Icon/components/IconArrowDown' +import { convertSize } from '../../../../Atomic/Icon' + +const SubHeadline: FC = (props) => ( + + {props.hasLine &&
    } + { + e.preventDefault() + isFunction(props.onToggle) && props.onToggle() + }} + > + + {props.headline} + + {isFunction(props.onToggle) && ( + + + + )} + +
    +) + +SubHeadline.displayName = 'SubHeadline' + +export default SubHeadline diff --git a/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/SubHeadline.types.ts b/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/SubHeadline.types.ts new file mode 100644 index 00000000..c72a3a6f --- /dev/null +++ b/src/components/Organisms/GeneratedResourceForm/FormGenerator/components/SubHeadline.types.ts @@ -0,0 +1,6 @@ +export type Props = { + expanded?: boolean + headline: string + hasLine?: boolean + onToggle?: () => void +} diff --git a/tests/components.spec.ts-snapshots/Pages-NotFoundPage-default-1-Google-Chrome-darwin.png b/tests/components.spec.ts-snapshots/Pages-NotFoundPage-default-1-Google-Chrome-darwin.png new file mode 100644 index 00000000..b89490c8 Binary files /dev/null and b/tests/components.spec.ts-snapshots/Pages-NotFoundPage-default-1-Google-Chrome-darwin.png differ diff --git a/tests/components.spec.ts-snapshots/Pages-NotFoundPage-default-1-chromium-darwin.png b/tests/components.spec.ts-snapshots/Pages-NotFoundPage-default-1-chromium-darwin.png new file mode 100644 index 00000000..b89490c8 Binary files /dev/null and b/tests/components.spec.ts-snapshots/Pages-NotFoundPage-default-1-chromium-darwin.png differ