Skip to content

Commit

Permalink
display component name in designview + refactor retrieving name logic
Browse files Browse the repository at this point in the history
  • Loading branch information
lassopicasso committed Oct 29, 2024
1 parent 42da9d8 commit f81b1fc
Show file tree
Hide file tree
Showing 19 changed files with 137 additions and 188 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import {
paymentLayoutComponents,
subformLayoutComponents,
} from '../../data/formItemConfig';
import { getComponentTitleByComponentType } from '../../utils/language';
import { mapComponentToToolbarElement } from '../../utils/formLayoutUtils';
import { useTranslation } from 'react-i18next';
import type { ConfPageType } from './types/ConfigPageType';
import { useComponentTitle } from '@altinn/ux-editor/hooks';

const getAvailableComponentList = (confPageType: ConfPageType) => {
switch (confPageType) {
Expand All @@ -30,7 +29,7 @@ export type ConfPageToolbarProps = {
};

export const ConfPageToolbar = ({ confPageType }: ConfPageToolbarProps) => {
const { t } = useTranslation();
const getComponentTitle = useComponentTitle();

const componentList: IToolbarElement[] = getAvailableComponentList(confPageType).map(
mapComponentToToolbarElement,
Expand All @@ -40,7 +39,7 @@ export const ConfPageToolbar = ({ confPageType }: ConfPageToolbarProps) => {
<div className={classes.customComponentList}>
{componentList.map((component: IToolbarElement) => (
<ToolbarItem
text={getComponentTitleByComponentType(component.type, t) || component.label}
componentTitle={getComponentTitle(component)}
icon={component.icon}
componentType={component.type}
key={component.type}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ import { useTranslation } from 'react-i18next';
import { schemaComponents, textComponents, advancedItems } from '../../data/formItemConfig';
import type { KeyValuePairs } from 'app-shared/types/KeyValuePairs';
import { Accordion } from '@digdir/designsystemet-react';
import {
getCollapsableMenuTitleByType,
getComponentTitleByComponentType,
} from '../../utils/language';
import { getCollapsableMenuTitleByType } from '../../utils/language';
import { ToolbarItem } from './ToolbarItem';
import { useComponentTitle } from '@altinn/ux-editor/hooks';

export const DefaultToolbar = () => {
const { t } = useTranslation();

const getComponentTitle = useComponentTitle();
const componentList: IToolbarElement[] = schemaComponents.map(mapComponentToToolbarElement);
const textComponentList: IToolbarElement[] = textComponents.map(mapComponentToToolbarElement);
const advancedComponentsList: IToolbarElement[] = advancedItems.map(mapComponentToToolbarElement);
Expand All @@ -39,7 +37,7 @@ export const DefaultToolbar = () => {
<Accordion.Content className={classes.accordionContent}>
{allComponentLists[key].map((component: IToolbarElement) => (
<ToolbarItem
text={getComponentTitleByComponentType(component.type, t) || component.label}
componentTitle={getComponentTitle(component)}
icon={component.icon}
componentType={component.type}
key={component.type}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,29 @@ import type { ComponentType, InternalComponentType } from 'app-shared/types/Comp
import { DragAndDropTree } from 'app-shared/components/DragAndDropTree';

type ToolbarItemProps = {
text: string;
componentTitle: string;
notDraggable?: boolean;
componentType: ComponentType | InternalComponentType;
icon?: React.ComponentType;
};

export const ToolbarItem = ({ notDraggable, componentType, text, icon }: ToolbarItemProps) => {
export const ToolbarItem = ({
notDraggable,
componentType,
componentTitle,
icon,
}: ToolbarItemProps) => {
return (
<div>
<DragAndDropTree.NewItem<ComponentType | InternalComponentType>
notDraggable={notDraggable}
payload={componentType}
>
<ToolbarItemComponent componentType={componentType} thirdPartyLabel={text} icon={icon} />
<ToolbarItemComponent
componentType={componentType}
componentTitle={componentTitle}
icon={icon}
/>
</DragAndDropTree.NewItem>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Summary2OverrideConfig } from 'app-shared/types/ComponentSpecificC
import { useTranslation } from 'react-i18next';
import { Checkbox } from '@digdir/designsystemet-react';
import { getAllLayoutComponents } from '../../../../../utils/formLayoutUtils';
import { useAppContext, useComponentTypeName } from '../../../../../hooks';
import { useAppContext, useComponentTitle } from '@altinn/ux-editor/hooks';
import { useFormLayoutsQuery } from '../../../../../hooks/queries/useFormLayoutsQuery';
import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams';
import { Summmary2ComponentReferenceSelector } from '../Summary2ComponentReferenceSelector';
Expand All @@ -26,7 +26,7 @@ export const Summary2OverrideEntry = ({
const { org, app } = useStudioEnvironmentParams();
const { selectedFormLayoutSetName } = useAppContext();
const { data: formLayoutsData } = useFormLayoutsQuery(org, app, selectedFormLayoutSetName);
const componentTypeName = useComponentTypeName();
const getComponentTitle = useComponentTitle();

const components = Object.values(formLayoutsData).flatMap((layout) =>
getAllLayoutComponents(layout),
Expand All @@ -36,7 +36,7 @@ export const Summary2OverrideEntry = ({

const componentOptions = components.map((e) => ({
id: e.id,
description: componentTypeName(e.type),
description: getComponentTitle(e),
}));

const onChangeOverride = (label: keyof Summary2OverrideConfig, value: string | boolean) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type {
import { ComponentType } from 'app-shared/types/ComponentType';
import { useTranslation } from 'react-i18next';
import type { FormComponent } from '../../../../../types/FormComponent';
import { useAppContext, useComponentTypeName } from '../../../../../hooks';
import { useAppContext, useComponentTitle } from '../../../../../hooks';
import { useFormLayoutsQuery } from '../../../../../hooks/queries/useFormLayoutsQuery';
import { getAllLayoutComponents } from '../../../../../utils/formLayoutUtils';
import { useTargetTypes } from './useTargetTypes';
Expand Down Expand Up @@ -48,7 +48,7 @@ export const Summary2Target = ({ target, onChange }: Summary2TargetProps) => {
const { data: formLayoutsData } = useFormLayoutsQuery(org, app, selectedLayoutSetName);

const targetTypes = useTargetTypes();
const componentTypeName = useComponentTypeName();
const getComponentTitle = useComponentTitle();

const excludedComponents = [
ComponentType.Summary2,
Expand All @@ -62,7 +62,7 @@ export const Summary2Target = ({ target, onChange }: Summary2TargetProps) => {
: [];
const componentOptions = components.map((formComponent: FormComponent) => ({
id: formComponent.id,
description: componentTypeName(formComponent.type),
description: getComponentTitle(formComponent),
}));

const pageOptions = formLayoutsData
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
import React from 'react';
import classNames from 'classnames';
import classes from './InformationPanelComponent.module.css';
import type { ComponentType } from 'app-shared/types/ComponentType';
import {
getComponentHelperTextByComponentType,
getComponentTitleByComponentType,
} from '../../utils/language';
import type { ComponentType, InternalComponentType } from 'app-shared/types/ComponentType';
import { getComponentHelperTextByComponentType } from '../../utils/language';
import { useTranslation } from 'react-i18next';
import { StudioLabelAsParagraph, StudioPopover } from '@studio/components';
import { StudioLabelAsParagraph, StudioParagraph, StudioPopover } from '@studio/components';
import { InformationIcon } from '@studio/icons';
import { Paragraph } from '@digdir/designsystemet-react';

export type InformationPanelProvidedProps = {
isOpen: boolean;
onOpen: () => void;
onClose: () => void;
selectedComponent: ComponentType;
componentTitle: string;
componentType: ComponentType | InternalComponentType;
};

export const InformationPanelComponent = ({
isOpen,
onOpen,
onClose,
selectedComponent,
componentTitle,
componentType,
}: InformationPanelProvidedProps) => {
const { t } = useTranslation();
return (
Expand All @@ -32,14 +30,12 @@ export const InformationPanelComponent = ({
</StudioPopover.Trigger>
<StudioPopover.Content>
<div className={classNames(classes.informationPanelHeader)}>
<StudioLabelAsParagraph size='small'>
{getComponentTitleByComponentType(selectedComponent, t)}
</StudioLabelAsParagraph>
<StudioLabelAsParagraph size='small'>{componentTitle}</StudioLabelAsParagraph>
</div>
<div className={classNames(classes.informationPanelText)}>
<Paragraph size='small'>
{getComponentHelperTextByComponentType(selectedComponent, t)}
</Paragraph>
<StudioParagraph size='small'>
{getComponentHelperTextByComponentType(componentType, t)}
</StudioParagraph>
</div>
</StudioPopover.Content>
</StudioPopover>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
import React, { useState } from 'react';
import classes from './ToolbarItemComponent.module.css';
import { getComponentTitleByComponentType } from '../../utils/language';
import { useTranslation } from 'react-i18next';
import type { ComponentType, InternalComponentType } from 'app-shared/types/ComponentType';
import { InformationPanelComponent } from './InformationPanelComponent';

export type ToolbarItemProvidedProps = {
componentType: ComponentType | InternalComponentType;
thirdPartyLabel?: string;
componentTitle: string;
icon?: React.ComponentType;
};

export const ToolbarItemComponent = ({
componentType,
thirdPartyLabel,
componentTitle,
icon: Icon,
}: ToolbarItemProvidedProps): React.ReactElement => {
const { t } = useTranslation();

const [compInfoPanelOpen, setCompInfoPanelOpen] = useState<boolean>(false);

const handleComponentInformationToggle = () => {
Expand All @@ -27,17 +23,14 @@ export const ToolbarItemComponent = ({
return (
<div className={classes.toolbarItem}>
<div className={classes.componentIcon}>{Icon && <Icon />}</div>
<div className={classes.componentLabel}>
{thirdPartyLabel == null
? getComponentTitleByComponentType(componentType, t)
: thirdPartyLabel}
</div>
<div className={classes.componentLabel}>{componentTitle}</div>
<div className={classes.componentHelpIcon}>
<InformationPanelComponent
isOpen={compInfoPanelOpen}
onOpen={handleComponentInformationToggle}
onClose={handleComponentInformationToggle}
selectedComponent={componentType}
componentTitle={componentTitle}
componentType={componentType}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { renderItemList } from '../renderItemList';
import { DragAndDropTree } from 'app-shared/components/DragAndDropTree';
import { FormItemTitle } from './FormItemTitle';
import { formItemConfigs } from '../../../../data/formItemConfig';
import { useItemTitle } from '../../../../hooks/useItemTitle';
import { useTranslation } from 'react-i18next';
import { UnknownReferencedItem } from '../UnknownReferencedItem';
import { QuestionmarkDiamondIcon } from '@studio/icons';
import { useComponentTitle } from '@altinn/ux-editor/hooks';

export type FormItemProps = {
layout: IInternalLayout;
Expand All @@ -17,9 +17,8 @@ export type FormItemProps = {
};

export const FormItem = ({ layout, id, duplicateComponents }: FormItemProps) => {
const itemTitle = useItemTitle();
const { t } = useTranslation();

const componentTitle = useComponentTitle();
const formItem = getItem(layout, id);

if (!formItem) {
Expand All @@ -43,7 +42,7 @@ export const FormItem = ({ layout, id, duplicateComponents }: FormItemProps) =>
icon={Icon && <Icon />}
emptyMessage={t('ux_editor.container_empty')}
expandable={isContainer(layout, id)}
label={itemTitle(formItem)}
label={componentTitle(formItem)}
labelWrapper={labelWrapper}
nodeId={id}
>
Expand Down
13 changes: 12 additions & 1 deletion frontend/packages/ux-editor/src/data/formItemConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ import { FilterUtils } from './FilterUtils';

export type FormItemConfig<T extends ComponentType | InternalComponentType = ComponentType> = {
name: ComponentType | InternalComponentType;
getDisplayName?: (formItem: ComponentSpecificConfig<ComponentType>) => string;
getDisplayName?: (
formItem: ComponentSpecificConfig<ComponentType>,
) => ComponentType | InternalComponentType;
componentRef?: ComponentType;
itemType: T extends ContainerComponentType ? LayoutItemType.Container : LayoutItemType.Component;
defaultProperties: ComponentSpecificConfig;
Expand Down Expand Up @@ -153,6 +155,15 @@ export const formItemConfigs: FormItemConfigs = {
[ComponentType.CustomButton]: {
name: ComponentType.CustomButton,
itemType: LayoutItemType.Component,
getDisplayName: (
formItem: ComponentSpecificConfig<ComponentType.CustomButton>,
): ComponentType | InternalComponentType => {
return formItem?.actions?.length === 1 &&
formItem.actions[0].id === 'closeSubform' &&
formItem.actions[0].type === 'ClientAction'
? InternalComponentType.CloseSubformButton
: ComponentType.CustomButton;
},
defaultProperties: {
actions: [],
buttonStyle: 'primary',
Expand Down
2 changes: 1 addition & 1 deletion frontend/packages/ux-editor/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export type { UseText } from './useText';
export { useComponentErrorMessage } from './useComponentErrorMessage';
export { useComponentTypeName } from './useComponentTypeName';
export { useComponentTitle } from './useComponentTitle';
export { useFormLayouts } from './useFormLayouts';
export { useFormLayout } from './useFormLayout';
export { useSelectedFormLayout } from './useSelectedFormLayout';
Expand Down
33 changes: 33 additions & 0 deletions frontend/packages/ux-editor/src/hooks/useComponentTitle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ComponentType, InternalComponentType } from 'app-shared/types/ComponentType';
import { renderHook } from '@testing-library/react';
import { useComponentTitle } from './useComponentTitle';
import { textMock } from '@studio/testing/mocks/i18nMock';
import { componentMocks } from '../testing/componentMocks';

// Test data:
const inputText = textMock(`ux_editor.component_title.${ComponentType.Input}`);
const paragraphText = textMock(`ux_editor.component_title.${ComponentType.Paragraph}`);
const headerText = textMock(`ux_editor.component_title.${ComponentType.Header}`);
const checkboxesText = textMock(`ux_editor.component_title.${ComponentType.Checkboxes}`);
const customButtonText = textMock(`ux_editor.component_title.${ComponentType.CustomButton}`);
const closeSubformButtonText = textMock(
`ux_editor.component_title.${InternalComponentType.CloseSubformButton}`,
);

describe('useComponentTypeName', () => {
const { result } = renderHook(useComponentTitle);

it('Returns the correct text if it exists', () => {
expect(result.current(componentMocks[ComponentType.Input])).toBe(inputText);
expect(result.current(componentMocks[ComponentType.Paragraph])).toBe(paragraphText);
expect(result.current(componentMocks[ComponentType.CustomButton])).toBe(customButtonText);
expect(result.current(componentMocks[InternalComponentType.CloseSubformButton])).toBe(
closeSubformButtonText,
);
});

it('Returns the component type if the text does not exist', () => {
expect(result.current(componentMocks[ComponentType.Header])).toBe(headerText);
expect(result.current(componentMocks[ComponentType.Checkboxes])).toBe(checkboxesText);
});
});
41 changes: 41 additions & 0 deletions frontend/packages/ux-editor/src/hooks/useComponentTitle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useTranslation } from 'react-i18next';
import { useCallback } from 'react';
import type { FormContainer } from '../types/FormContainer';
import type { FormComponent } from '../types/FormComponent';
import { formItemConfigs } from '../data/formItemConfig';
import type { IToolbarElement } from '../types/global';
import { textResourcesByLanguageSelector } from '../selectors/textResourceSelectors';
import { DEFAULT_LANGUAGE } from 'app-shared/constants';
import { useTextResourcesSelector } from './useTextResourcesSelector';
import type { ITextResource } from 'app-shared/types/global';
import { getTextResource, getTitleByComponentType } from '../utils/language';

export function useComponentTitle(): (
formItem: FormComponent | FormContainer | IToolbarElement,
) => string {
const { t } = useTranslation();
const getTitleByTextResource = useTextResourceTitle();

return useCallback(
(formItem: FormComponent | FormContainer | IToolbarElement) => {
if ('textResourceBindings' in formItem && getTitleByTextResource(formItem))
return getTitleByTextResource(formItem);

const getDisplayName = formItemConfigs[formItem.type]?.getDisplayName;
const componentType = getDisplayName ? getDisplayName(formItem) : formItem.type;
return getTitleByComponentType(componentType, t);
},
[t, getTitleByTextResource],
);
}

const useTextResourceTitle = (): ((item: FormComponent | FormContainer) => string) => {
const textResources: ITextResource[] = useTextResourcesSelector<ITextResource[]>(
textResourcesByLanguageSelector(DEFAULT_LANGUAGE),
);
return useCallback(
(item: FormComponent | FormContainer) =>
getTextResource(item.textResourceBindings?.title, textResources),
[textResources],
);
};
Loading

0 comments on commit f81b1fc

Please sign in to comment.