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

Repeater pattern component #336

Merged
merged 65 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
5326816
copy fieldset pattern to use as the basis for the repeater pattern (m…
ethangardner Sep 11, 2024
332f48f
add a clone/delete item control for the repeater field to duplicate o…
ethangardner Sep 26, 2024
8e5513d
formatting
ethangardner Sep 26, 2024
a9fcb81
add presentational component for edit view
ethangardner Sep 27, 2024
da6c2cd
prevent duplicate ids for input fields. Will need to map canonical id…
ethangardner Sep 27, 2024
a54fb6f
use local storage for storing repeater options on the client
ethangardner Sep 30, 2024
1f72200
add function to mutate ids for cloned elements. need to make it work …
ethangardner Sep 30, 2024
c3d39bb
formatting
ethangardner Sep 30, 2024
74ef760
render update radio group components id in repeater
ethangardner Oct 1, 2024
b21a44f
remove empty test language from user-facing component
ethangardner Oct 1, 2024
27ed9b9
update ids have an optional suffix to ensure unique ids in the repeat…
ethangardner Oct 2, 2024
0cf02bd
sensible default for local storage
ethangardner Oct 3, 2024
cbce28d
add function to get id for pattern
ethangardner Oct 4, 2024
af4aaea
update id modifier string
ethangardner Oct 4, 2024
0e5ad3b
clean up pattern logic for dropdown
ethangardner Oct 4, 2024
25e77e9
refactor to use react hook form useFieldsArray
ethangardner Oct 8, 2024
47e9565
work in progress on repeater validation and structure
ethangardner Oct 8, 2024
ea4d983
ignore .idea dir
ethangardner Oct 8, 2024
adaa90a
dry out add pattern dropdown functions
ethangardner Oct 9, 2024
cc3df6a
refactor dropdown buttons and consolidate prop types
ethangardner Oct 9, 2024
649889b
update validation to accommodate an array of objects
ethangardner Oct 9, 2024
85652d2
turn off results summary table for now
ethangardner Oct 9, 2024
41f2e89
remove debugging and console statements
ethangardner Oct 10, 2024
5549ee3
remove function from repeater pattern. validation occurs on individua…
ethangardner Oct 10, 2024
b73cf69
turn off localstorage on the repeater for now
ethangardner Oct 11, 2024
720cec7
unified add pattern methods to fieldset and repeaters into a single m…
ethangardner Oct 11, 2024
086f3bc
resolve ts issue
ethangardner Oct 11, 2024
c386e64
prevent effect hook from running until decision is made about behavior
ethangardner Oct 11, 2024
ea9d980
rename var for clarity
ethangardner Oct 11, 2024
825e21c
cleanup from copy/paste
ethangardner Oct 11, 2024
7b6031a
remove unneeded code
ethangardner Oct 11, 2024
fcb6850
remove the move control if the question is in a repeater or fieldset
ethangardner Oct 14, 2024
e6bc138
handle field copy
ethangardner Oct 14, 2024
0e3592b
rename test
ethangardner Oct 14, 2024
017b370
add better tests for repeater component
ethangardner Oct 16, 2024
5d1f770
default to empty state for repeater
ethangardner Oct 16, 2024
d2ac7ba
update spacing
ethangardner Oct 16, 2024
bff041f
convert add/delete buttons to submit so they can be caught on backend
ethangardner Oct 16, 2024
e5ba38f
remove useform hook in repeater component
ethangardner Oct 21, 2024
ab54c5d
table the submit event name and value for now
ethangardner Oct 21, 2024
9589372
fix unfound import issue
ethangardner Dec 5, 2024
f0315c2
re-add useform hook. needed for pattern validation
ethangardner Oct 23, 2024
754cb47
wip on repeater field
ethangardner Oct 24, 2024
b838d47
add todo comments to help provide guidance for handoff
ethangardner Dec 6, 2024
6dc766a
refactor: update single field component for better value and error ha…
kalasgarov Jan 6, 2025
984b6b7
refactor: update single field component config files tckt-310
kalasgarov Jan 6, 2025
705d6b6
test: update tests for single field component config files tckt-310
kalasgarov Jan 6, 2025
efa2b80
fix: repeater duplicate children id issue tckt-310
kalasgarov Jan 6, 2025
2214109
feat: implement isFormBuilder flag tckt-310
kalasgarov Jan 6, 2025
3bf244a
feat: implement parse user input and submit actions for repeater tckt…
kalasgarov Jan 6, 2025
16072a2
storybook: update stories for repeater and edit repeater form tckt-310
kalasgarov Jan 6, 2025
d2a2581
refactor: clean up old files tckt-310
kalasgarov Jan 8, 2025
1955cb7
chore: add db file to gitignore tchkt-310
kalasgarov Jan 10, 2025
b32846a
feat: update phone component type to tel for better user experience t…
kalasgarov Jan 13, 2025
74c3300
Merge remote-tracking branch 'origin/main' into feature/310-repeater-…
danielnaab Jan 13, 2025
798b881
Fix child rendering in the repeater component
danielnaab Jan 14, 2025
17e68c4
Don't emit output file on typecheck step, to avoid cluttering up the …
danielnaab Jan 14, 2025
1ba0558
Use childComponents in PromptComponent to get the patternId
danielnaab Jan 14, 2025
6746d7a
ensure terraform is installed in apply workflow (#439)
ethangardner Jan 14, 2025
4f41bd2
chore: clean up ';' in FormContents TCKT-310
kalasgarov Jan 14, 2025
4409672
chore: remove commented code block in repeater TCKT-310
kalasgarov Jan 14, 2025
f552dcb
chore: update pnpm lock yaml TCKT-310
kalasgarov Jan 14, 2025
d9ad8f3
Merge remote-tracking branch 'origin/main' into feature/310-repeater-…
danielnaab Jan 15, 2025
dbbfee7
feat: add removeChildPattern to repaeter config and clean up types TC…
kalasgarov Jan 15, 2025
ae95dba
storybook: add tests for repeater pattern TCKT-310
kalasgarov Jan 15, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
_site
.turbo/
.vscode/
.idea/
coverage/
html/
node_modules/
Expand Down
5 changes: 5 additions & 0 deletions packages/common/src/locales/en/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,10 @@ export const en = {
preferNotToAnswerTextLabel:
'Prefer not to share my gender identity checkbox label',
},
repeater: {
...defaults,
displayName: 'Repeatable Group',
errorTextMustContainChar: 'String must contain at least 1 character(s)',
},
},
};
170 changes: 90 additions & 80 deletions packages/design/src/Form/components/DateOfBirth/DateOfBirth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,94 +35,104 @@ export const DateOfBirthPattern: PatternComponent<DateOfBirthProps> = ({
hint,
required,
error,
value,
}) => {
const { register } = useFormContext();
const errorId = `input-error-message-${monthId}`;
const hintId = `hint-${monthId}`;

return (
<fieldset className="usa-fieldset">
<legend className="usa-legend">
{label}
{required && <span className="required-indicator">*</span>}
</legend>
{hint && (
<span className="usa-hint" id={hintId}>
{hint}
</span>
)}
{error && (
<div className="usa-error-message" id={errorId} role="alert">
{error.message}
</div>
)}
<div className="usa-memorable-date">
<div className="usa-form-group usa-form-group--month usa-form-group--select">
<label className="usa-label" htmlFor={monthId}>
Month
</label>
<select
className={classNames('usa-input', {
'usa-input--error': !!error,
})}
id={monthId}
{...register(monthId)}
aria-describedby={
getAriaDescribedBy(
error ? errorId : null,
hint ? hintId : null
) || undefined
}
>
<option key="default" value="">
- Select -
</option>
{months.map((option, index) => (
<option key={index} value={option.value}>
{option.label}
<div className={classNames('usa-form-group margin-top-2')}>
<legend
className={classNames('usa-legend', {
'usa-legend--error': error,
})}
>
{label}
{required && <span className="required-indicator">*</span>}
</legend>
{hint && (
<div className="usa-hint" id={hintId}>
{hint}
</div>
)}
{error && (
<div className="usa-error-message" id={errorId} role="alert">
{error.message}
</div>
)}
<div className="usa-memorable-date">
<div className="usa-form-group usa-form-group--month usa-form-group--select">
<label className="usa-label" htmlFor={monthId}>
Month
</label>
<select
className={classNames('usa-input', {
'usa-input--error': !!error,
})}
id={monthId}
{...register(monthId)}
defaultValue={value?.month}
aria-describedby={
getAriaDescribedBy(
error ? errorId : null,
hint ? hintId : null
) || undefined
}
>
<option key="default" value="">
- Select -
</option>
))}
</select>
</div>
<div className="usa-form-group usa-form-group--day">
<label className="usa-label" htmlFor={dayId}>
Day
</label>
<input
className={classNames('usa-input', {
'usa-input--error': !!error,
})}
id={dayId}
{...register(dayId, { required })}
minLength={2}
maxLength={2}
pattern="[0-9]*"
inputMode="numeric"
aria-describedby={getAriaDescribedBy(
error ? `input-error-message-${dayId}` : null,
hint ? hintId : null
)}
/>
</div>
<div className="usa-form-group usa-form-group--year">
<label className="usa-label" htmlFor={yearId}>
Year
</label>
<input
className={classNames('usa-input', {
'usa-input--error': !!error,
})}
id={yearId}
{...register(yearId, { required })}
minLength={4}
maxLength={4}
pattern="[0-9]*"
inputMode="numeric"
aria-describedby={getAriaDescribedBy(
error ? `input-error-message-${yearId}` : null,
hint ? hintId : null
)}
/>
{months.map((option, index) => (
<option key={index} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
<div className="usa-form-group usa-form-group--day">
<label className="usa-label" htmlFor={dayId}>
Day
</label>
<input
className={classNames('usa-input', {
'usa-input--error': !!error,
})}
id={dayId}
{...register(dayId, { required })}
minLength={2}
maxLength={2}
pattern="[0-9]*"
inputMode="numeric"
aria-describedby={getAriaDescribedBy(
error ? `input-error-message-${dayId}` : null,
hint ? hintId : null
)}
defaultValue={value?.day}
/>
</div>
<div className="usa-form-group usa-form-group--year">
<label className="usa-label" htmlFor={yearId}>
Year
</label>
<input
className={classNames('usa-input', {
'usa-input--error': !!error,
})}
id={yearId}
{...register(yearId, { required })}
minLength={4}
maxLength={4}
pattern="[0-9]*"
inputMode="numeric"
aria-describedby={getAriaDescribedBy(
error ? `input-error-message-${yearId}` : null,
hint ? hintId : null
)}
defaultValue={value?.year}
/>
</div>
</div>
</div>
</fieldset>
Expand Down
13 changes: 10 additions & 3 deletions packages/design/src/Form/components/EmailInput/EmailInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@ export const EmailInputPattern: PatternComponent<EmailInputProps> = ({
label,
required,
error,
value,
}) => {
const { register } = useFormContext();
const errorId = `input-error-message-${emailId}`;

return (
<fieldset className="usa-fieldset">
<div className="usa-form-group">
<label className="usa-label" htmlFor={emailId}>
<div className={classNames('usa-form-group margin-top-2')}>
<label
className={classNames('usa-label', {
'usa-label--error': error,
})}
htmlFor={emailId}
>
{label}
{required && <span className="required-indicator">*</span>}
</label>
Expand All @@ -26,14 +32,15 @@ export const EmailInputPattern: PatternComponent<EmailInputProps> = ({
</div>
)}
<input
className={classNames('usa-input margin-bottom-1', {
className={classNames('usa-input usa-input--xl', {
'usa-input--error': error,
})}
id={emailId}
type="email"
autoCapitalize="off"
autoCorrect="off"
{...register(emailId, { required })}
defaultValue={value?.email || ''}
aria-describedby={error ? errorId : undefined}
/>
</div>
Expand Down
4 changes: 2 additions & 2 deletions packages/design/src/Form/components/GenderId/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const GenderIdPattern: PatternComponent<GenderIdProps> = ({
label,
required,
error,
value = '',
value,
preferNotToAnswerText,
preferNotToAnswerChecked: initialPreferNotToAnswerChecked = false,
}) => {
Expand All @@ -22,7 +22,7 @@ const GenderIdPattern: PatternComponent<GenderIdProps> = ({
const errorId = `input-error-message-${genderId}`;
const hintId = `hint-${genderId}`;
const preferNotToAnswerId = `${genderId}.preferNotToAnswer`;
const inputId = `${genderId}.input`;
const inputId = `${genderId}.gender`;

const watchedValue = useWatch({ name: inputId, defaultValue: value });

Expand Down
21 changes: 18 additions & 3 deletions packages/design/src/Form/components/PhoneNumber/PhoneNumber.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ import { useFormContext } from 'react-hook-form';
import { type PhoneNumberProps } from '@atj/forms';
import { type PatternComponent } from '../../index.js';

const formatPhoneNumber = (value: string) => {
const rawValue = value.replace(/[^\d]/g, ''); // Remove non-digit characters

if (rawValue.length <= 3) return rawValue;
if (rawValue.length <= 6)
return `${rawValue.slice(0, 3)}-${rawValue.slice(3)}`;
return `${rawValue.slice(0, 3)}-${rawValue.slice(3, 6)}-${rawValue.slice(6, 10)}`;
};

export const PhoneNumberPattern: PatternComponent<PhoneNumberProps> = ({
phoneId,
hint,
Expand All @@ -12,10 +21,15 @@ export const PhoneNumberPattern: PatternComponent<PhoneNumberProps> = ({
error,
value,
}) => {
const { register } = useFormContext();
const { register, setValue } = useFormContext();
const errorId = `input-error-message-${phoneId}`;
const hintId = `hint-${phoneId}`;

const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const formattedPhone = formatPhoneNumber(e.target.value);
setValue(phoneId, formattedPhone, { shouldValidate: true });
};

return (
<fieldset className="usa-fieldset">
<div className={classNames('usa-form-group margin-top-2')}>
Expand All @@ -39,13 +53,14 @@ export const PhoneNumberPattern: PatternComponent<PhoneNumberProps> = ({
</div>
)}
<input
className={classNames('usa-input', {
className={classNames('usa-input usa-input--xl', {
'usa-input--error': error,
})}
id={phoneId}
type="tel"
type="text"
defaultValue={value}
{...register(phoneId, { required })}
onChange={handlePhoneChange}
aria-describedby={
`${hint ? `${hintId}` : ''}${error ? ` ${errorId}` : ''}`.trim() ||
undefined
Expand Down
7 changes: 4 additions & 3 deletions packages/design/src/Form/components/RadioGroup/RadioGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@ export const RadioGroupPattern: PatternComponent<RadioGroupProps> = props => {
{props.legend}
</legend>
{props.options.map((option, index) => {
const id = option.id;
return (
<div key={index} className="usa-radio">
<input
className="usa-radio__input"
type="radio"
id={option.id}
{...register(props.groupId)}
id={`input-${id}`}
{...register(`${props.groupId}`)}
value={option.id}
defaultChecked={option.defaultChecked}
/>
<label htmlFor={option.id} className="usa-radio__label">
<label htmlFor={`input-${id}`} className="usa-radio__label">
{option.label}
</label>
</div>
Expand Down
Loading
Loading