diff --git a/packages/design/src/Form/index.tsx b/packages/design/src/Form/index.tsx index a821467f3..6efa67114 100644 --- a/packages/design/src/Form/index.tsx +++ b/packages/design/src/Form/index.tsx @@ -20,9 +20,10 @@ export type FormUIContext = { uswdsRoot: `${string}/`; }; -export type ComponentForPattern< - T extends PatternProps = PatternProps, -> = Record>; +export type ComponentForPattern = Record< + string, + PatternComponent +>; export type PatternComponent> = React.ComponentType< diff --git a/packages/design/src/FormManager/FormEdit/components/RadioGroupPatternEdit.tsx b/packages/design/src/FormManager/FormEdit/components/RadioGroupPatternEdit.tsx index ab4b07d33..7be1bcf6d 100644 --- a/packages/design/src/FormManager/FormEdit/components/RadioGroupPatternEdit.tsx +++ b/packages/design/src/FormManager/FormEdit/components/RadioGroupPatternEdit.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { type RadioGroupProps, type PatternId } from '@atj/forms'; @@ -38,6 +38,8 @@ const EditComponent = ({ patternId }: { patternId: PatternId }) => { state => state.form.patterns[patternId] ) as RadioGroupPattern; const methods = usePatternEditFormContext(); + const [options, setOptions] = useState(pattern.data.options); + return (
@@ -53,18 +55,30 @@ const EditComponent = ({ patternId }: { patternId: PatternId }) => { >
- {pattern.data.options.map((option, index) => ( - <> - + {options.map((option, index) => ( +
+ - +
))} +
diff --git a/packages/forms/src/components.ts b/packages/forms/src/components.ts index 9a9a8395b..1454ff5ea 100644 --- a/packages/forms/src/components.ts +++ b/packages/forms/src/components.ts @@ -104,7 +104,7 @@ export const createPrompt = ( config, session.form.patterns[patternId].type ); - return !!elemConfig.parseData; + return !!elemConfig.parseUserInput; }) .map(([patternId, value]) => { return { diff --git a/packages/forms/src/pattern.ts b/packages/forms/src/pattern.ts index 5676b7e9c..8340a5e0f 100644 --- a/packages/forms/src/pattern.ts +++ b/packages/forms/src/pattern.ts @@ -15,8 +15,8 @@ export type PatternValueMap = Record; export type PatternMap = Record; export type GetPattern = (form: Blueprint, id: PatternId) => Pattern; -type ParsePatternData = ( - patternData: PatternConfigData, +type ParsePatternData = ( + pattern: Pattern, obj: unknown ) => Result; @@ -39,7 +39,7 @@ export type PatternConfig< > = { displayName: string; initial: ThisPattern['data']; - parseData?: ParsePatternData; + parseUserInput?: ParsePatternData; parseConfigData: ParsePatternConfigData; getChildren: ( pattern: ThisPattern, @@ -73,13 +73,13 @@ export const validatePattern = ( pattern: Pattern, value: any ): Result => { - if (!patternConfig.parseData) { + if (!patternConfig.parseUserInput) { return { success: true, data: value, }; } - const parseResult = patternConfig.parseData(pattern, value); + const parseResult = patternConfig.parseUserInput(pattern, value); if (!parseResult.success) { return { success: false, diff --git a/packages/forms/src/patterns/address/index.ts b/packages/forms/src/patterns/address/index.ts index 28520afd7..6bb43fb2e 100644 --- a/packages/forms/src/patterns/address/index.ts +++ b/packages/forms/src/patterns/address/index.ts @@ -85,7 +85,7 @@ export const addressConfig: PatternConfig< initial: { patterns: [], }, - parseData: (_, obj) => { + parseUserInput: (_, obj) => { return safeZodParse(AddressSchema, obj); }, parseConfigData: obj => safeZodParse(configSchema, obj), diff --git a/packages/forms/src/patterns/checkbox.ts b/packages/forms/src/patterns/checkbox.ts index f72a70a08..c2303c501 100644 --- a/packages/forms/src/patterns/checkbox.ts +++ b/packages/forms/src/patterns/checkbox.ts @@ -20,7 +20,7 @@ export const checkboxConfig: PatternConfig = { label: 'Checkbox label', defaultChecked: false, }, - parseData: (_, obj) => { + parseUserInput: (_, obj) => { return safeZodParse(PatternOutput, obj); }, parseConfigData: obj => safeZodParse(configSchema, obj), diff --git a/packages/forms/src/patterns/input.ts b/packages/forms/src/patterns/input.ts index 1a1ca98be..0462778d8 100644 --- a/packages/forms/src/patterns/input.ts +++ b/packages/forms/src/patterns/input.ts @@ -26,8 +26,8 @@ export const inputConfig: PatternConfig = { required: true, maxLength: 128, }, - parseData: (patternData, obj) => { - return safeZodParse(createSchema(patternData), obj); + parseUserInput: (pattern, obj) => { + return safeZodParse(createSchema(pattern['data']), obj); }, parseConfigData: obj => safeZodParse(configSchema, obj), getChildren() { diff --git a/packages/forms/src/patterns/radio-group.ts b/packages/forms/src/patterns/radio-group.ts index d8b45a764..8c3e5e084 100644 --- a/packages/forms/src/patterns/radio-group.ts +++ b/packages/forms/src/patterns/radio-group.ts @@ -1,7 +1,9 @@ import * as z from 'zod'; -import { type Pattern, type PatternConfig, validatePattern } from '../pattern'; +import { Result } from '@atj/common'; + import { type RadioGroupProps } from '../components'; +import { type Pattern, type PatternConfig, validatePattern } from '../pattern'; import { getFormSessionValue } from '../session'; import { safeZodParse } from '../util/zod'; @@ -9,47 +11,31 @@ const configSchema = z.object({ label: z.string(), options: z .object({ - id: z.string(), + id: z.string().regex(/^[^\s]+$/), label: z.string(), }) .array(), }); export type RadioGroupPattern = Pattern>; -const PatternOutput = z.boolean(); +const PatternOutput = z.string(); type PatternOutput = z.infer; -const parseJSONString = (obj: any) => { - if (typeof obj === 'string') { - try { - return JSON.parse(obj); - } catch (error) { - return null; - } - } -}; - export const radioGroupConfig: PatternConfig = { displayName: 'Radio group', initial: { label: 'Radio group label', - // TODO: for now, have some default options, so we can visualize what - // radio groups look like. - // replace this with an empty array once we get a proper UI. options: [ - { id: '1', label: 'Option 1' }, - { id: '2', label: 'Option 2' }, + { id: 'option-1', label: 'Option 1' }, + { id: 'option-1', label: 'Option 2' }, ], }, - parseData: (_, obj) => { - return safeZodParse(PatternOutput, obj); + parseUserInput: (pattern, input) => { + return extractOptionId(pattern.id, input); }, parseConfigData: obj => { - const result = safeZodParse(configSchema, { - ...(obj as any), - options: parseJSONString((obj as any).options), - }); + const result = safeZodParse(configSchema, obj); if (!result.success) { console.error(result.error); } @@ -76,15 +62,43 @@ export const radioGroupConfig: PatternConfig = _patternId: pattern.id, type: 'radio-group', legend: pattern.data.label, - options: pattern.data.options.map(option => ({ - id: option.id, - name: option.id, - label: option.label, - defaultChecked: sessionValue === option.id, - })), + options: pattern.data.options.map(option => { + const optionId = createId(pattern.id, option.id); + return { + id: optionId, + name: pattern.id, + label: option.label, + defaultChecked: sessionValue === optionId, + }; + }), ...extraAttributes, } as RadioGroupProps, children: [], }; }, }; + +const createId = (groupId: string, optionId: string) => + `${groupId}-${optionId}`; + +export const extractOptionId = ( + groupId: string, + inputId: unknown +): Result => { + if (typeof inputId !== 'string') { + return { + success: false, + error: 'invalid data', + }; + } + if (!inputId.startsWith(groupId)) { + return { + success: false, + error: `invalid id: ${inputId}`, + }; + } + return { + success: true, + data: inputId.slice(groupId.length + 1), + }; +};