Skip to content

Commit acad98e

Browse files
authored
feat: create accordion row pattern TCKT-523 (#592)
* feat: create accordion row pattern TCKT-523 * feat: create accordion row edit pattern TCKT-523 * feat: add types and default values for accordion row pattern TCKT-523 * feat: create accordion row pattern config files TCKT-523 * test: add config tests for accordion row pattern TCKT-523 * test(storybook): add tests for accordion row pattern TCKT-523 * test(storybook): add tests for accordion row edit pattern TCKT-523 * refactor: update default label texts for multiple edit patterns TCKT-523 * refactor: remove unnecessary comments and update variable name in story file TCKT-523
1 parent b2eeb85 commit acad98e

17 files changed

Lines changed: 420 additions & 5 deletions

File tree

packages/common/src/locales/en/app.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ const defaults = {
77

88
export const en = {
99
patterns: {
10+
accordionRow: {
11+
displayName: 'Accordion row',
12+
fieldLabel: 'Information box title',
13+
textLabel: 'Information box text',
14+
errorTextMustContainChar: 'String must contain at least 1 character(s)',
15+
},
1016
address: {
1117
...defaults,
1218
displayName: 'Address',
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import { type Meta, type StoryObj } from '@storybook/react';
3+
4+
import AccordionRow from './index.js';
5+
6+
const meta: Meta<typeof AccordionRow> = {
7+
title: 'patterns/AccordionRow',
8+
component: AccordionRow,
9+
decorators: [
10+
Story => {
11+
return (
12+
<div className="padding-left-2">
13+
<Story />
14+
</div>
15+
);
16+
},
17+
],
18+
tags: ['autodocs'],
19+
};
20+
21+
export default meta;
22+
23+
export const Default: StoryObj<typeof AccordionRow> = {
24+
args: {
25+
inputId: 'accordion-1',
26+
title: 'Accordion Row 1 (Closed)',
27+
text: 'This is the content of the first accordion row.',
28+
isOpen: false,
29+
},
30+
};
31+
32+
export const Open: StoryObj<typeof AccordionRow> = {
33+
args: {
34+
inputId: 'accordion-2',
35+
title: 'Accordion Row 2 (Open)',
36+
text: 'This is the content of the second accordion row, and it is open by default.',
37+
isOpen: true,
38+
},
39+
};
40+
41+
export const WithLongContent: StoryObj<typeof AccordionRow> = {
42+
args: {
43+
inputId: 'accordion-3',
44+
title: 'Accordion Row with Long Content',
45+
text: 'This is a longer content for the accordion row. It demonstrates how the component handles larger amounts of text. The content can span multiple lines and should still be displayed correctly within the accordion.',
46+
isOpen: true, // Starts open
47+
},
48+
};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React, { useState } from 'react';
2+
import classNames from 'classnames';
3+
4+
import { type AccordionRowProps } from '@gsa-tts/forms-core';
5+
import { type PatternComponent } from '../../index.js';
6+
7+
const AccordionRow: PatternComponent<AccordionRowProps> = ({
8+
inputId,
9+
title,
10+
text,
11+
isOpen = false,
12+
}) => {
13+
const [isExpanded, setIsExpanded] = useState(isOpen);
14+
const contentId = `${inputId}.content`;
15+
16+
const toggleAccordion = () => {
17+
setIsExpanded((prev: boolean) => !prev);
18+
};
19+
20+
return (
21+
<div
22+
className="usa-accordion__row maxw-tablet padding-top-1 padding-bottom-1"
23+
id={inputId}
24+
>
25+
<h4 className="usa-accordion__heading">
26+
<button
27+
type="button"
28+
className={classNames('usa-accordion__button', {
29+
'usa-accordion__button--expanded': isExpanded,
30+
})}
31+
aria-expanded={isExpanded}
32+
aria-controls={contentId}
33+
onClick={toggleAccordion}
34+
>
35+
{title}
36+
</button>
37+
</h4>
38+
{isExpanded && (
39+
<div id={contentId} className="usa-accordion__content usa-prose">
40+
<p>{text}</p>
41+
</div>
42+
)}
43+
</div>
44+
);
45+
};
46+
47+
export default AccordionRow;

packages/design/src/Form/components/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { PatternComponent, type ComponentForPattern } from '../index.js';
22

3+
import AccordionRow from './AccordionRow/index.js';
34
import Attachment from './Attachment/index.js';
45
import Address from './Address/index.js';
56
import Checkbox from './Checkbox/index.js';
@@ -26,6 +27,7 @@ import TextArea from './TextArea/index.js';
2627
import Name from './Name/index.js';
2728

2829
export const defaultPatternComponents: ComponentForPattern = {
30+
'accordion-row': AccordionRow as PatternComponent,
2931
attachment: Attachment as PatternComponent,
3032
address: Address as PatternComponent,
3133
checkbox: Checkbox as PatternComponent,

packages/design/src/FormManager/FormEdit/AddPatternDropdown.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ const sidebarPatterns: DropdownPattern[] = [
138138
['rich-text', defaultFormConfig.patterns['rich-text'], 'Other'],
139139
['attachment', defaultFormConfig.patterns['attachment'], 'Other'],
140140
['package-download', defaultFormConfig.patterns['package-download'], 'Other'],
141+
['accordion-row', defaultFormConfig.patterns['accordion-row'], 'Other'], // TODO: remove this from the sidebar menu once accordion-row is added to fieldset and repeater
141142
] as const;
142143

143144
export const compoundFieldChildPatterns: DropdownPattern[] =
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
3+
import { type AccordionRowPattern } from '@gsa-tts/forms-core';
4+
5+
import { createPatternEditStoryMeta } from '../common/story-helper.js';
6+
import FormEdit from '../../index.js';
7+
import { enLocale as message } from '@gsa-tts/forms-common';
8+
import { expect, userEvent } from '@storybook/test';
9+
import { within } from '@testing-library/react';
10+
11+
const pattern: AccordionRowPattern = {
12+
id: 'accordion-row-pattern-1',
13+
type: 'accordion-row',
14+
data: {
15+
title: 'Information box title',
16+
text: 'Helper text that adds supplementary information or instructions for the question',
17+
isOpen: false,
18+
},
19+
};
20+
21+
const storyConfig: Meta = {
22+
title: 'Edit components/AccordionRowPattern',
23+
...createPatternEditStoryMeta({
24+
pattern,
25+
}),
26+
} as Meta<typeof FormEdit>;
27+
export default storyConfig;
28+
29+
export const Basic: StoryObj<typeof FormEdit> = {
30+
play: async ({ canvasElement }) => {
31+
const canvas = within(canvasElement);
32+
const updatedLabel = 'Accordion Row update';
33+
const updatedText = 'Helper text update';
34+
35+
await userEvent.click(
36+
canvas.getByText(message.patterns.accordionRow.fieldLabel)
37+
);
38+
39+
const titleInput = canvas.getByLabelText(
40+
message.patterns.accordionRow.fieldLabel
41+
);
42+
await userEvent.clear(titleInput);
43+
await userEvent.type(titleInput, updatedLabel);
44+
45+
const helperText = canvas.getByLabelText(
46+
message.patterns.accordionRow.textLabel
47+
);
48+
await userEvent.clear(helperText);
49+
await userEvent.type(helperText, updatedText);
50+
51+
const form = titleInput?.closest('form');
52+
form?.requestSubmit();
53+
54+
await expect(await canvas.findByText(updatedLabel)).toBeInTheDocument();
55+
},
56+
};
57+
58+
export const Error: StoryObj<typeof FormEdit> = {
59+
play: async ({ canvasElement }) => {
60+
const canvas = within(canvasElement);
61+
62+
await userEvent.click(
63+
canvas.getByText(message.patterns.accordionRow.fieldLabel)
64+
);
65+
66+
const titleInput = canvas.getByLabelText(
67+
message.patterns.accordionRow.fieldLabel
68+
);
69+
await userEvent.clear(titleInput);
70+
71+
const textInput = canvas.getByLabelText(
72+
message.patterns.accordionRow.textLabel
73+
);
74+
await userEvent.clear(textInput);
75+
76+
const form = textInput?.closest('form');
77+
form?.requestSubmit();
78+
79+
await expect(
80+
await canvas.findByText('Text is required')
81+
).toBeInTheDocument();
82+
},
83+
};
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import React from 'react';
2+
import classNames from 'classnames';
3+
4+
import type {
5+
AccordionRowProps,
6+
AccordionRowPattern,
7+
} from '@gsa-tts/forms-core';
8+
9+
import AccordionRow from '../../../../Form/components/AccordionRow/index.js';
10+
import { PatternEditComponent } from '../../types.js';
11+
12+
import { PatternEditActions } from '../common/PatternEditActions.js';
13+
import { PatternEditForm } from '../common/PatternEditForm.js';
14+
import { usePatternEditFormContext } from '../common/hooks.js';
15+
import { enLocale as message } from '@gsa-tts/forms-common';
16+
import styles from '../../formEditStyles.module.css';
17+
18+
const AccordionRowPatternEdit: PatternEditComponent<AccordionRowProps> = ({
19+
context,
20+
focus,
21+
previewProps,
22+
}) => {
23+
return (
24+
<>
25+
{focus ? (
26+
<PatternEditForm
27+
pattern={focus.pattern}
28+
editComponent={<EditComponent pattern={focus.pattern} />}
29+
/>
30+
) : (
31+
<div className={`padding-left-3 padding-bottom-3 padding-right-3`}>
32+
<AccordionRow context={context} {...previewProps} />
33+
</div>
34+
)}
35+
</>
36+
);
37+
};
38+
39+
const EditComponent = ({ pattern }: { pattern: AccordionRowPattern }) => {
40+
const { fieldId, register, getFieldState } =
41+
usePatternEditFormContext<AccordionRowPattern>(pattern.id);
42+
const title = getFieldState('title');
43+
const text = getFieldState('text');
44+
45+
return (
46+
<div className="grid-row grid-gap">
47+
<div className="grid-col-12 margin-bottom-2">
48+
<label
49+
className={classNames(
50+
'usa-label',
51+
{
52+
'usa-label--error': title.error,
53+
},
54+
`${styles.patternChoiceFieldWrapper}`
55+
)}
56+
>
57+
{message.patterns.accordionRow.fieldLabel}
58+
{title.error ? (
59+
<span className="usa-error-message" role="alert">
60+
{title.error.message}
61+
</span>
62+
) : null}
63+
<input
64+
className={classNames(
65+
'usa-input bg-primary-lighter',
66+
{
67+
'usa-input--error': title.error,
68+
},
69+
`${styles.patternChoiceFieldWrapper}`
70+
)}
71+
id={fieldId('title')}
72+
defaultValue={pattern.data.title}
73+
{...register('title')}
74+
type="text"
75+
autoFocus
76+
/>
77+
</label>
78+
</div>
79+
<div className="grid-col-12 margin-bottom-2">
80+
<label
81+
className={classNames(
82+
'usa-label',
83+
{
84+
'usa-label--error': text.error,
85+
},
86+
`${styles.patternChoiceFieldWrapper}`
87+
)}
88+
>
89+
{message.patterns.accordionRow.textLabel}
90+
{text.error ? (
91+
<span className="usa-error-message" role="alert">
92+
{text.error.message}
93+
</span>
94+
) : null}
95+
<textarea
96+
className={classNames(
97+
'usa-input bg-primary-lighter',
98+
{
99+
'usa-input--error': text.error,
100+
},
101+
`${styles.patternChoiceFieldWrapper}`
102+
)}
103+
id={fieldId('text')}
104+
defaultValue={pattern.data.text}
105+
{...register('text')}
106+
style={{ resize: 'none', overflow: 'auto', height: '100px' }}
107+
/>
108+
</label>
109+
</div>
110+
<div className="grid-col-12">
111+
<PatternEditActions></PatternEditActions>
112+
</div>
113+
</div>
114+
);
115+
};
116+
117+
export default AccordionRowPatternEdit;

packages/design/src/FormManager/FormEdit/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
type EditComponentForPattern,
44
} from '../types.js';
55

6+
import AccordionRowPatternEdit from './AccordionRowPatternEdit/index.js';
67
import AddressPatternEdit from './AddressPatternEdit/index.js';
78
import AttachmentPatternEdit from './AttachmentPatternEdit/index.js';
89
import CheckboxPatternEdit from './CheckboxPatternEdit/index.js';
@@ -28,6 +29,7 @@ import SubmissionConfirmationEdit from './SubmissionConfirmationEdit.js';
2829
import TextAreaPatternEdit from './TextAreaPatternEdit/index.js';
2930
import SexPatternEdit from './SexPatternEdit/index.js';
3031
export const defaultPatternEditComponents: EditComponentForPattern = {
32+
'accordion-row': AccordionRowPatternEdit as PatternEditComponent,
3133
address: AddressPatternEdit as PatternEditComponent,
3234
attachment: AttachmentPatternEdit as PatternEditComponent,
3335
checkbox: CheckboxPatternEdit as PatternEditComponent,

packages/forms/src/components.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ export type TextAreaProps = PatternProps<{
3636
hint?: string;
3737
}>;
3838

39+
export type AccordionRowProps = PatternProps<{
40+
type: 'accordion-row';
41+
inputId: string;
42+
title: string;
43+
text: string;
44+
isOpen?: boolean;
45+
}>;
46+
3947
export type AddressFieldProps = {
4048
type: 'input' | 'select';
4149
inputId: string;

0 commit comments

Comments
 (0)