Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option component #2884

Merged
merged 19 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/app-components/Date/DisplayDate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export const DisplayDate = ({ value, format, iconUrl, iconAltText, labelId }: Da
const parsedValue = parseISO(value);
let displayData = parsedValue.toDateString();
if (!isValid(parsedValue)) {
displayData = 'Ugyldig format';
window.logErrorOnce(`Ugyldig datoformat gitt til Date-komponent: "${value}"`);
displayData = '';
} else if (format) {
displayData = formatDate(parsedValue, format);
}
Expand All @@ -30,7 +31,8 @@ export const DisplayDate = ({ value, format, iconUrl, iconAltText, labelId }: Da
alt={iconAltText}
/>
)}
<span aria-labelledby={labelId}>{displayData}</span>
{labelId && <span aria-labelledby={labelId}>{displayData}</span>}
{!labelId && <span>{displayData}</span>}
</>
);
};
3 changes: 2 additions & 1 deletion src/app-components/Number/DisplayNumber.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export const DisplayNumber = ({
alt={iconAltText}
/>
)}
<span aria-labelledby={labelId}>{displayData}</span>
{labelId && <span aria-labelledby={labelId}>{displayData}</span>}
{!labelId && <span>{displayData}</span>}
</>
);
};
3 changes: 2 additions & 1 deletion src/app-components/Text/DisplayText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const DisplayText = ({ value, iconUrl, iconAltText, labelId }: TextProps)
alt={iconAltText}
/>
)}
<span aria-labelledby={labelId}>{value}</span>
{labelId && <span aria-labelledby={labelId}>{value}</span>}
{!labelId && <span>{value}</span>}
</>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "Display value of Option component",
"expression": ["displayValue", "valg"],
"context": {
"component": "valg",
"currentLayout": "Page"
},
"expects": "Pål",
"layouts": {
"Page": {
"$schema": "https://altinncdn.no/schemas/json/layout/layout.schema.v1.json",
"data": {
"layout": [
{
"id": "valg",
"type": "Option",
"value": ["dataModel", "Skjema.Valg"],
"options": [
{
"value": "1",
"label": "Per",
"description": "Hello world",
"helpText": "This is a help text"
},
{
"value": "2",
"label": "Pål",
"description": "Hello world",
"helpText": "This is a help text"
}
]
}
]
}
}
},
"dataModel": {
"Skjema": {
"Valg": "2"
}
}
}
14 changes: 9 additions & 5 deletions src/features/options/OptionsPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface Config<SupportsPreselection extends boolean> {
componentType: CompTypes;
expectedFromExternal: SupportsPreselection extends true ? ISelectionComponentFull : ISelectionComponent;
settings: {
allowsEffects?: boolean;
supportsPreselection: SupportsPreselection;
type: OptionsValueType;
};
Expand All @@ -22,6 +23,7 @@ interface Config<SupportsPreselection extends boolean> {
}

interface ExternalConfig {
allowsEffects?: boolean;
supportsPreselection: boolean;
type: OptionsValueType;
}
Expand Down Expand Up @@ -49,10 +51,6 @@ export class OptionsPlugin<E extends ExternalConfig> extends NodeDefPlugin<ToInt
}

addToComponent(component: ComponentConfig): void {
if (!component.isFormLike()) {
throw new Error('OptionsPlugin can only be used with container or form components');
}

component.inner.extends(
this.settings!.supportsPreselection ? CG.common('ISelectionComponentFull') : CG.common('ISelectionComponent'),
);
Expand All @@ -65,6 +63,12 @@ export class OptionsPlugin<E extends ExternalConfig> extends NodeDefPlugin<ToInt
from: 'src/features/options/StoreOptionsInNode',
});

return `<${StoreOptionsInNode} valueType={'${this.settings!.type}'} />`;
const allowsEffects = this.settings!.allowsEffects ?? true;

return `
<${StoreOptionsInNode}
valueType={'${this.settings!.type}'}
allowEffects={${allowsEffects ? 'true' : 'false'}}
/>`.trim();
}
}
5 changes: 3 additions & 2 deletions src/features/options/StoreOptionsInNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type { LayoutNode } from 'src/utils/layout/LayoutNode';

interface GeneratorOptionProps {
valueType: OptionsValueType;
allowEffects: boolean;
}

export function StoreOptionsInNode(props: GeneratorOptionProps) {
Expand All @@ -32,7 +33,7 @@ export function StoreOptionsInNode(props: GeneratorOptionProps) {
);
}

function StoreOptionsInNodeWorker({ valueType }: GeneratorOptionProps) {
function StoreOptionsInNodeWorker({ valueType, allowEffects }: GeneratorOptionProps) {
const item = GeneratorInternal.useIntermediateItem() as CompIntermediate<CompWithBehavior<'canHaveOptions'>>;
const node = GeneratorInternal.useParent() as LayoutNode<CompWithBehavior<'canHaveOptions'>>;
const dataModelBindings = item.dataModelBindings as IDataModelBindingsOptionsSimple | undefined;
Expand All @@ -55,7 +56,7 @@ function StoreOptionsInNodeWorker({ valueType }: GeneratorOptionProps) {
NodesStateQueue.useSetNodeProp({ node, prop: 'options', value: options }, !hasBeenSet && !isFetching);
NodesStateQueue.useSetNodeProp({ node, prop: 'isFetchingOptions', value: isFetching }, !hasBeenSet);

if (isFetching || !hasBeenSet) {
if (isFetching || !hasBeenSet || !allowEffects) {
// No need to run effects while fetching or if the data has not been set yet
return false;
}
Expand Down
10 changes: 8 additions & 2 deletions src/layout/Date/DateComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ import cn from 'classnames';
import classes from 'src/app-components/Date/Date.module.css';
import { DisplayDate } from 'src/app-components/Date/DisplayDate';
import { getLabelId } from 'src/components/label/Label';
import { useLanguage } from 'src/features/language/useLanguage';
import { ComponentStructureWrapper } from 'src/layout/ComponentStructureWrapper';
import { useNodeItem } from 'src/utils/layout/useNodeItem';
import type { PropsFromGenericComponent } from 'src/layout';

export const DateComponent = ({ node }: PropsFromGenericComponent<'Date'>) => {
const { textResourceBindings, value, icon, direction, format } = useNodeItem(node);
const textResourceBindings = useNodeItem(node, (i) => i.textResourceBindings);
const direction = useNodeItem(node, (i) => i.direction) ?? 'horizontal';
const value = useNodeItem(node, (i) => i.value);
const icon = useNodeItem(node, (i) => i.icon);
const format = useNodeItem(node, (i) => i.format);
const { langAsString } = useLanguage(node);

if (!textResourceBindings?.title) {
return (
Expand All @@ -33,7 +39,7 @@ export const DateComponent = ({ node }: PropsFromGenericComponent<'Date'>) => {
<DisplayDate
value={value}
iconUrl={icon}
iconAltText={textResourceBindings.title}
iconAltText={langAsString(textResourceBindings.title)}
labelId={getLabelId(node.id)}
format={format}
/>
Expand Down
26 changes: 9 additions & 17 deletions src/layout/Group/GroupSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,6 @@ function getHeadingLevel(hierarchyLevel: number): HeadingLevel {
}
}

const ChildComponents = ({ componentNode, hierarchyLevel, summaryOverride }: GroupComponentSummaryProps) => {
const childComponents = useNodeItem(componentNode, (i) => i.childComponents);
return childComponents.map((childId) => (
<ChildComponent
key={childId}
id={childId}
hierarchyLevel={hierarchyLevel}
summaryOverride={summaryOverride}
/>
));
};

function ChildComponent({
id,
hierarchyLevel,
Expand Down Expand Up @@ -84,6 +72,7 @@ export const GroupSummary = ({ componentNode, hierarchyLevel = 0, summaryOverrid
const isNestedGroup = hierarchyLevel > 0;

const dataTestId = hierarchyLevel > 0 ? `summary-group-component-${hierarchyLevel}` : 'summary-group-component';
const childComponents = useNodeItem(componentNode, (i) => i.childComponents);

return (
<section
Expand All @@ -106,11 +95,14 @@ export const GroupSummary = ({ componentNode, hierarchyLevel = 0, summaryOverrid
spacing={6}
alignItems='flex-start'
>
<ChildComponents
componentNode={componentNode}
hierarchyLevel={hierarchyLevel}
summaryOverride={summaryOverride}
/>
{childComponents.map((childId) => (
<ChildComponent
key={childId}
id={childId}
hierarchyLevel={hierarchyLevel}
summaryOverride={summaryOverride}
/>
))}
</Flex>
</section>
);
Expand Down
11 changes: 9 additions & 2 deletions src/layout/Number/NumberComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ import { DisplayNumber } from 'src/app-components/Number/DisplayNumber';
import classes from 'src/app-components/Number/Number.module.css';
import { getLabelId } from 'src/components/label/Label';
import { useCurrentLanguage } from 'src/features/language/LanguageProvider';
import { useLanguage } from 'src/features/language/useLanguage';
import { ComponentStructureWrapper } from 'src/layout/ComponentStructureWrapper';
import { useNodeItem } from 'src/utils/layout/useNodeItem';
import type { PropsFromGenericComponent } from 'src/layout';

export const NumberComponent = ({ node }: PropsFromGenericComponent<'Number'>) => {
const { textResourceBindings, value, icon, direction, formatting } = useNodeItem(node);
const textResourceBindings = useNodeItem(node, (i) => i.textResourceBindings);
const value = useNodeItem(node, (i) => i.value);
const icon = useNodeItem(node, (i) => i.icon);
const direction = useNodeItem(node, (i) => i.direction) ?? 'horizontal';
const formatting = useNodeItem(node, (i) => i.formatting);
const { langAsString } = useLanguage(node);
const currentLanguage = useCurrentLanguage();

if (isNaN(value)) {
return null;
}
Expand Down Expand Up @@ -40,7 +47,7 @@ export const NumberComponent = ({ node }: PropsFromGenericComponent<'Number'>) =
value={value}
currentLanguage={currentLanguage}
iconUrl={icon}
iconAltText={textResourceBindings.title}
iconAltText={langAsString(textResourceBindings.title)}
labelId={getLabelId(node.id)}
formatting={formatting}
/>
Expand Down
29 changes: 29 additions & 0 deletions src/layout/Option/Option.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.icon {
width: 24px;
margin-right: 12px;
}

.optionComponent {
display: flex;
justify-content: space-between;
margin: 0;
width: 100%;
}

.horizontal {
flex-direction: row;
}
.vertical {
flex-direction: column;
}

.optionLabelContainer {
display: flex;
column-gap: 8px;
flex-wrap: wrap;
align-items: center;
}

.optionDescription {
flex: 0 0 100%;
}
89 changes: 89 additions & 0 deletions src/layout/Option/OptionComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react';

import { HelpText } from '@digdir/designsystemet-react';
import cn from 'classnames';

import { getLabelId } from 'src/components/label/Label';
import { Lang } from 'src/features/language/Lang';
import { useLanguage } from 'src/features/language/useLanguage';
import { useGetOptions } from 'src/features/options/useGetOptions';
import { ComponentStructureWrapper } from 'src/layout/ComponentStructureWrapper';
import classes from 'src/layout/Option/Option.module.css';
import { useNodeItem } from 'src/utils/layout/useNodeItem';
import type { PropsFromGenericComponent } from 'src/layout';
import type { LayoutNode } from 'src/utils/layout/LayoutNode';

export const OptionComponent = ({ node }: PropsFromGenericComponent<'Option'>) => {
const textResourceBindings = useNodeItem(node, (i) => i.textResourceBindings);
const direction = useNodeItem(node, (i) => i.direction);

if (!textResourceBindings?.title) {
return (
<Text
node={node}
usingLabel={false}
/>
);
}

return (
<ComponentStructureWrapper
node={node}
label={{
node,
renderLabelAs: 'span',
className: cn(classes.optionComponent, direction === 'vertical' ? classes.vertical : classes.horizontal),
}}
>
<Text
node={node}
usingLabel={true}
/>
</ComponentStructureWrapper>
);
};

interface TextProps {
node: LayoutNode<'Option'>;
usingLabel: boolean;
}

function Text({ node, usingLabel }: TextProps) {
const textResourceBindings = useNodeItem(node, (i) => i.textResourceBindings);
const icon = useNodeItem(node, (i) => i.icon);
const value = useNodeItem(node, (i) => i.value);
const { options, isFetching } = useGetOptions(node, 'single');
const { langAsString } = useLanguage(node);
const selectedOption = options.find((option) => option.value === value);
if (isFetching) {
return null;
}

return (
<>
{icon && textResourceBindings?.title && (
<img
src={icon}
className={classes.icon}
alt={langAsString(textResourceBindings.title)}
/>
)}
<span
{...(usingLabel ? { 'aria-labelledby': getLabelId(node.id) } : {})}
className={classes.optionLabelContainer}
>
<Lang id={selectedOption?.label} />
{selectedOption?.helpText && (
<HelpText title={langAsString(selectedOption.helpText)}>
<Lang id={selectedOption.helpText} />
</HelpText>
)}
{selectedOption?.description && (
<span className={classes.optionDescription}>
<Lang id={selectedOption?.description} />
</span>
)}
</span>
</>
);
}
Loading