Skip to content

Commit

Permalink
Radio group "add new" option button added; rename pattern parseData t…
Browse files Browse the repository at this point in the history
…o parseUserInput.
  • Loading branch information
danielnaab committed May 1, 2024
1 parent 54469aa commit a6f0879
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 52 deletions.
7 changes: 4 additions & 3 deletions packages/design/src/Form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ export type FormUIContext = {
uswdsRoot: `${string}/`;
};

export type ComponentForPattern<
T extends PatternProps = PatternProps<unknown>,
> = Record<string, PatternComponent<T>>;
export type ComponentForPattern<T extends PatternProps = PatternProps> = Record<
string,
PatternComponent<T>
>;

export type PatternComponent<T extends PatternProps = PatternProps<unknown>> =
React.ComponentType<
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';

import { type RadioGroupProps, type PatternId } from '@atj/forms';

Expand Down Expand Up @@ -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 (
<div className="grid-row grid-gap">
<div className="tablet:grid-col-6 mobile-lg:grid-col-12">
Expand All @@ -53,18 +55,30 @@ const EditComponent = ({ patternId }: { patternId: PatternId }) => {
></input>
</div>
<div className="tablet:grid-col-6 mobile-lg:grid-col-12">
{pattern.data.options.map((option, index) => (
<>
<label className="usa-label" htmlFor={`${pattern.id}.data.label`}>
Option {index}
</label>
{options.map((option, index) => (
<div key={index} className="display-flex">
<input
className="usa-input"
id={`${pattern.id}.data.options.${index}.id`}
{...methods.register(`${pattern.id}.data.options.${index}.id`)}
/>
<input
className="usa-input"
id={`${pattern.id}.data.options.${index}`}
{...methods.register(`${pattern.id}.data.options.${index}`)}
id={`${pattern.id}.data.options.${index}.label`}
{...methods.register(`${pattern.id}.data.options.${index}.label`)}
/>
</>
</div>
))}
<button
className="usa-button usa-button--outline"
onClick={event => {
event.preventDefault();
const optionId = `${options.length + 1}`;
setOptions(options.concat({ id: optionId, label: optionId }));
}}
>
Add new
</button>
</div>
<div className="grid-col-12">
<PatternEditActions>
Expand Down
2 changes: 1 addition & 1 deletion packages/forms/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export const createPrompt = (
config,
session.form.patterns[patternId].type
);
return !!elemConfig.parseData;
return !!elemConfig.parseUserInput;
})
.map(([patternId, value]) => {
return {
Expand Down
10 changes: 5 additions & 5 deletions packages/forms/src/pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export type PatternValueMap = Record<PatternId, PatternValue>;
export type PatternMap = Record<PatternId, Pattern>;
export type GetPattern = (form: Blueprint, id: PatternId) => Pattern;

type ParsePatternData<PatternConfigData, PatternOutput> = (
patternData: PatternConfigData,
type ParsePatternData<Pattern, PatternOutput> = (
pattern: Pattern,
obj: unknown
) => Result<PatternOutput>;

Expand All @@ -39,7 +39,7 @@ export type PatternConfig<
> = {
displayName: string;
initial: ThisPattern['data'];
parseData?: ParsePatternData<ThisPattern['data'], PatternOutput>;
parseUserInput?: ParsePatternData<ThisPattern, PatternOutput>;
parseConfigData: ParsePatternConfigData<ThisPattern['data']>;
getChildren: (
pattern: ThisPattern,
Expand Down Expand Up @@ -73,13 +73,13 @@ export const validatePattern = (
pattern: Pattern,
value: any
): Result<Pattern['data']> => {
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,
Expand Down
2 changes: 1 addition & 1 deletion packages/forms/src/patterns/address/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const addressConfig: PatternConfig<
initial: {
patterns: [],
},
parseData: (_, obj) => {
parseUserInput: (_, obj) => {
return safeZodParse(AddressSchema, obj);
},
parseConfigData: obj => safeZodParse(configSchema, obj),
Expand Down
2 changes: 1 addition & 1 deletion packages/forms/src/patterns/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const checkboxConfig: PatternConfig<InputPattern, PatternOutput> = {
label: 'Checkbox label',
defaultChecked: false,
},
parseData: (_, obj) => {
parseUserInput: (_, obj) => {
return safeZodParse(PatternOutput, obj);
},
parseConfigData: obj => safeZodParse(configSchema, obj),
Expand Down
4 changes: 2 additions & 2 deletions packages/forms/src/patterns/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ export const inputConfig: PatternConfig<InputPattern, InputPatternOutput> = {
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() {
Expand Down
74 changes: 44 additions & 30 deletions packages/forms/src/patterns/radio-group.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,41 @@
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';

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<z.infer<typeof configSchema>>;

const PatternOutput = z.boolean();
const PatternOutput = z.string();
type PatternOutput = z.infer<typeof PatternOutput>;

const parseJSONString = (obj: any) => {
if (typeof obj === 'string') {
try {
return JSON.parse(obj);
} catch (error) {
return null;
}
}
};

export const radioGroupConfig: PatternConfig<RadioGroupPattern, PatternOutput> =
{
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);
}
Expand All @@ -76,15 +62,43 @@ export const radioGroupConfig: PatternConfig<RadioGroupPattern, PatternOutput> =
_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<string> => {
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),
};
};

0 comments on commit a6f0879

Please sign in to comment.