-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ui-editor): poc a modal for adding components to form (#13568)
Co-authored-by: David Ovrelid <46874830+framitdavid@users.noreply.github.com>
- Loading branch information
1 parent
3deec23
commit 0808b19
Showing
18 changed files
with
538 additions
and
12 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
39 changes: 39 additions & 0 deletions
39
frontend/packages/ux-editor/src/containers/DesignView/AddItemModal/AddItemContent.module.css
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,39 @@ | ||
.allComponentsWrapper { | ||
display: flex; | ||
flex-direction: row; | ||
flex-wrap: wrap; | ||
height: 100%; | ||
flex: 3; | ||
padding: 20px; | ||
overflow-y: scroll; | ||
} | ||
|
||
.componentButton { | ||
height: 50px; | ||
width: 180px; | ||
margin: 8px; | ||
} | ||
|
||
.componentCategory { | ||
padding-top: 12px; | ||
} | ||
|
||
.componentHelpText { | ||
width: 100%; | ||
} | ||
|
||
.componentsInfoWrapper { | ||
flex: 1; | ||
background-color: var(--fds-semantic-surface-info-subtle); | ||
padding: 20px; | ||
height: 600px; | ||
position: sticky; | ||
} | ||
|
||
.root { | ||
display: flex; | ||
flex-direction: row; | ||
overflow-y: hidden; | ||
height: 100%; | ||
/* min-width: 70vw; */ | ||
} |
60 changes: 60 additions & 0 deletions
60
frontend/packages/ux-editor/src/containers/DesignView/AddItemModal/AddItemContent.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,60 @@ | ||
import React from 'react'; | ||
import type { ComponentType } from 'app-shared/types/ComponentType'; | ||
import type { KeyValuePairs } from 'app-shared/types/KeyValuePairs'; | ||
import type { IToolbarElement } from '../../../types/global'; | ||
import classes from './AddItemContent.module.css'; | ||
import { ItemCategory } from './ItemCategory'; | ||
import type { AddedItem } from './types'; | ||
import { ItemInfo } from './ItemInfo'; | ||
import { useFormLayouts } from '../../../hooks'; | ||
import { generateComponentId } from '../../../utils/generateId'; | ||
import { StudioParagraph } from '@studio/components'; | ||
|
||
export type AddItemContentProps = { | ||
item: AddedItem | null; | ||
setItem: (item: AddedItem | null) => void; | ||
onAddItem: (addedItem: AddedItem) => void; | ||
onCancel: () => void; | ||
availableComponents: KeyValuePairs<IToolbarElement[]>; | ||
}; | ||
|
||
export const AddItemContent = ({ | ||
item, | ||
setItem, | ||
onAddItem, | ||
onCancel, | ||
availableComponents, | ||
}: AddItemContentProps) => { | ||
const layouts = useFormLayouts(); | ||
|
||
return ( | ||
<div className={classes.root}> | ||
<div className={classes.allComponentsWrapper}> | ||
<StudioParagraph spacing size='small' style={{ width: '100%' }}> | ||
Klikk på en komponent for å se mer informasjon om den. | ||
</StudioParagraph> | ||
{Object.keys(availableComponents).map((key) => { | ||
return ( | ||
<ItemCategory | ||
key={key} | ||
category={key} | ||
items={availableComponents[key]} | ||
selectedItemType={item?.componentType} | ||
setAddedItem={setItem} | ||
generateComponentId={(type: ComponentType) => generateComponentId(type, layouts)} | ||
/> | ||
); | ||
})} | ||
</div> | ||
<div className={classes.componentsInfoWrapper}> | ||
<ItemInfo | ||
onAddItem={onAddItem} | ||
onCancel={onCancel} | ||
generateComponentId={(type: ComponentType) => generateComponentId(type, layouts)} | ||
item={item} | ||
setItem={setItem} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
}; |
103 changes: 103 additions & 0 deletions
103
frontend/packages/ux-editor/src/containers/DesignView/AddItemModal/AddItemModal.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,103 @@ | ||
import React, { useCallback, useRef } from 'react'; | ||
import { | ||
addItemOfType, | ||
getAvailableChildComponentsForContainer, | ||
getItem, | ||
} from '../../../utils/formLayoutUtils'; | ||
import { useAddItemToLayoutMutation } from '../../../hooks/mutations/useAddItemToLayoutMutation'; | ||
import { useFormItemContext } from '../../FormItemContext'; | ||
import { useAppContext } from '../../../hooks'; | ||
import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; | ||
import type { IInternalLayout } from '../../../types/global'; | ||
import type { ComponentType } from 'app-shared/types/ComponentType'; | ||
import { StudioButton, StudioModal } from '@studio/components'; | ||
import type { AddedItem } from './types'; | ||
import { BASE_CONTAINER_ID } from 'app-shared/constants'; | ||
import { AddItemContent } from './AddItemContent'; | ||
import { PlusCircleIcon } from '@studio/icons'; | ||
import { usePreviewContext } from 'app-development/contexts/PreviewContext'; | ||
|
||
export type AddItemProps = { | ||
containerId: string; | ||
layout: IInternalLayout; | ||
}; | ||
|
||
export const AddItemModal = ({ containerId, layout }: AddItemProps) => { | ||
const [selectedItem, setSelectedItem] = React.useState<AddedItem | null>(null); | ||
|
||
const { doReloadPreview } = usePreviewContext(); | ||
const handleCloseModal = () => { | ||
setSelectedItem(null); | ||
modalRef.current?.close(); | ||
}; | ||
const { handleEdit } = useFormItemContext(); | ||
|
||
const { org, app } = useStudioEnvironmentParams(); | ||
const { selectedFormLayoutSetName } = useAppContext(); | ||
|
||
const { mutate: addItemToLayout } = useAddItemToLayoutMutation( | ||
org, | ||
app, | ||
selectedFormLayoutSetName, | ||
); | ||
|
||
const modalRef = useRef<HTMLDialogElement>(null); | ||
|
||
const addItem = (type: ComponentType, parentId: string, index: number, newId: string) => { | ||
const updatedLayout = addItemOfType(layout, type, newId, parentId, index); | ||
|
||
addItemToLayout( | ||
{ componentType: type, newId, parentId, index }, | ||
{ | ||
onSuccess: () => { | ||
doReloadPreview(); | ||
}, | ||
}, | ||
); | ||
handleEdit(getItem(updatedLayout, newId)); | ||
}; | ||
|
||
const onAddComponent = (addedItem: AddedItem) => { | ||
addItem( | ||
addedItem.componentType, | ||
containerId, | ||
layout.order[containerId].length, | ||
addedItem.componentId, | ||
); | ||
handleCloseModal(); | ||
}; | ||
|
||
const handleOpenModal = useCallback(() => { | ||
modalRef.current?.showModal(); | ||
}, []); | ||
|
||
return ( | ||
<div style={{ display: 'flex', justifyContent: 'center', marginLeft: 12, marginRight: 12 }}> | ||
<StudioModal.Root> | ||
<StudioButton | ||
icon={<PlusCircleIcon />} | ||
onClick={handleOpenModal} | ||
variant='tertiary' | ||
fullWidth | ||
> | ||
Legg til komponent | ||
</StudioButton> | ||
<StudioModal.Dialog | ||
onClose={handleCloseModal} | ||
heading={'Velg komponent'} | ||
closeButtonTitle='Lukk' | ||
style={{ minWidth: '80vw', overflowY: 'hidden' }} | ||
ref={modalRef} | ||
> | ||
<AddItemContent | ||
item={selectedItem} | ||
setItem={setSelectedItem} | ||
onAddItem={onAddComponent} | ||
onCancel={handleCloseModal} | ||
availableComponents={getAvailableChildComponentsForContainer(layout, BASE_CONTAINER_ID)} | ||
/> | ||
</StudioModal.Dialog> | ||
</StudioModal.Root> | ||
</div> | ||
); | ||
}; |
20 changes: 20 additions & 0 deletions
20
...ges/ux-editor/src/containers/DesignView/AddItemModal/ItemCategory/ItemCategory.module.css
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,20 @@ | ||
.componentButton { | ||
margin: 8px; | ||
justify-content: start; | ||
width: 270px; | ||
} | ||
|
||
.componentsWrapper { | ||
display: flex; | ||
flex-direction: column; | ||
height: 100%; | ||
flex-wrap: wrap; | ||
justify-content: start; | ||
align-items: start; | ||
} | ||
|
||
.itemCategory { | ||
margin-bottom: 12px; | ||
margin-right: 12px; | ||
max-width: calc(50% - 12px); | ||
} |
72 changes: 72 additions & 0 deletions
72
...d/packages/ux-editor/src/containers/DesignView/AddItemModal/ItemCategory/ItemCategory.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,72 @@ | ||
import React from 'react'; | ||
import { StudioButton, StudioCard, StudioHeading } from '@studio/components'; | ||
import classes from './ItemCategory.module.css'; | ||
import { useTranslation } from 'react-i18next'; | ||
import type { IToolbarElement } from '../../../../types/global'; | ||
import type { AddedItem } from '../types'; | ||
import type { ComponentType } from 'app-shared/types/ComponentType'; | ||
import { getComponentTitleByComponentType } from '../../../../utils/language'; | ||
|
||
export type ItemCategoryProps = { | ||
items: IToolbarElement[]; | ||
category: string; | ||
selectedItemType: ComponentType; | ||
setAddedItem(addedItem: AddedItem): void; | ||
generateComponentId: (type: string) => string; | ||
}; | ||
|
||
export const ItemCategory = ({ | ||
items, | ||
category, | ||
selectedItemType, | ||
setAddedItem, | ||
generateComponentId, | ||
}: ItemCategoryProps) => { | ||
const { t } = useTranslation(); | ||
|
||
return ( | ||
<StudioCard color='subtle' className={classes.itemCategory}> | ||
<StudioHeading level={2} size='small'> | ||
{t(`ux_editor.component_category.${category}`)} | ||
</StudioHeading> | ||
<div className={classes.componentsWrapper}> | ||
{items.map((item: IToolbarElement) => ( | ||
<ComponentButton | ||
tooltipContent={getComponentTitleByComponentType(item.type, t) || item.label} | ||
selected={selectedItemType === item.type} | ||
key={item.type} | ||
icon={item.icon} | ||
onClick={() => { | ||
setAddedItem({ | ||
componentType: item.type, | ||
componentId: generateComponentId(item.type), | ||
}); | ||
}} | ||
/> | ||
))} | ||
</div> | ||
</StudioCard> | ||
); | ||
}; | ||
|
||
type ComponentButtonProps = { | ||
tooltipContent: string; | ||
selected: boolean; | ||
icon: React.ComponentType; | ||
onClick: () => void; | ||
}; | ||
function ComponentButton({ tooltipContent, selected, icon, onClick }: ComponentButtonProps) { | ||
return ( | ||
<StudioButton | ||
variant={selected ? 'primary' : 'tertiary'} | ||
onClick={onClick} | ||
size='sm' | ||
aria-label={tooltipContent} | ||
className={classes.componentButton} | ||
title={tooltipContent} | ||
icon={React.createElement(icon, { fontSize: '1.5rem' } as any)} | ||
> | ||
{tooltipContent} | ||
</StudioButton> | ||
); | ||
} |
1 change: 1 addition & 0 deletions
1
frontend/packages/ux-editor/src/containers/DesignView/AddItemModal/ItemCategory/index.ts
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 @@ | ||
export { ItemCategory } from './ItemCategory'; |
38 changes: 38 additions & 0 deletions
38
...nd/packages/ux-editor/src/containers/DesignView/AddItemModal/ItemInfo/ItemInfo.module.css
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 @@ | ||
.allComponentsWrapper { | ||
display: flex; | ||
flex-direction: column; | ||
height: 100%; | ||
flex: 3; | ||
padding: 20px; | ||
} | ||
|
||
.componentButton { | ||
height: 50px; | ||
width: 180px; | ||
margin: 8px; | ||
} | ||
|
||
.componentCategory { | ||
padding-top: 12px; | ||
} | ||
|
||
.componentHelpText { | ||
width: 100%; | ||
} | ||
|
||
.componentsInfoWrapper { | ||
flex: 1; | ||
background-color: var(--fds-semantic-surface-info-subtle); | ||
padding: 20px; | ||
} | ||
|
||
.componentsWrapper { | ||
display: flex; | ||
flex-direction: row; | ||
height: 100%; | ||
flex-wrap: wrap; | ||
} | ||
|
||
.root { | ||
min-width: 360px; | ||
} |
Oops, something went wrong.