Skip to content

Commit d5170c0

Browse files
authored
Merge pull request #1307 from revisit-studies/codex/issue-1252-next-button-alignment
Add configurable next button alignment
2 parents 5397d0a + db0b41a commit d5170c0

6 files changed

Lines changed: 226 additions & 3 deletions

File tree

src/components/NextButton.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ import {
1616
getAutoAdvanceWarning,
1717
} from './nextButtonTimeout';
1818

19+
const nextButtonJustify = {
20+
left: 'flex-start',
21+
center: 'center',
22+
right: 'flex-end',
23+
} as const;
24+
1925
type Props = {
2026
label?: string;
2127
disabled?: boolean;
@@ -125,10 +131,11 @@ export function NextButton({
125131

126132
const nextButtonDisabled = disabled || isNextDisabled || !buttonTimerSatisfied;
127133
const previousButtonText = config?.previousButtonText ?? studyConfig.uiConfig.previousButtonText ?? 'Previous';
134+
const nextButtonAlignment = config?.nextButtonAlignment ?? studyConfig.uiConfig.nextButtonAlignment ?? 'right';
128135

129136
return (
130137
<>
131-
<Group justify="right" gap="xs" mt="sm">
138+
<Group justify={nextButtonJustify[nextButtonAlignment]} gap="xs" mt="sm" wrap="wrap">
132139
{config?.previousButton && (
133140
<PreviousButton
134141
label={previousButtonText}

src/components/tests/NextButton.spec.tsx

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ let mockStudyConfig: {
1919
uiConfig: {
2020
nextButtonDisableTime: number | undefined;
2121
nextButtonEnableTime: number | undefined;
22+
nextButtonAlignment?: 'left' | 'center' | 'right';
2223
nextOnEnter: boolean;
2324
previousButtonText: string;
2425
timeoutReject: boolean;
@@ -74,7 +75,9 @@ vi.mock('@mantine/core', () => ({
7475
{children}
7576
</button>
7677
),
77-
Group: ({ children }: { children: ReactNode }) => <div>{children}</div>,
78+
Group: ({ children, justify }: { children: ReactNode; justify?: string }) => (
79+
<div data-justify={justify}>{children}</div>
80+
),
7881
}));
7982

8083
vi.mock('@tabler/icons-react', () => ({
@@ -155,6 +158,64 @@ describe('NextButton', () => {
155158
expect(html).toContain('Check Answer');
156159
});
157160

161+
test('right-aligns the action group by default', () => {
162+
const html = renderToStaticMarkup(<NextButton checkAnswer={null} onNext={vi.fn()} />);
163+
expect(html).toContain('data-justify="flex-end"');
164+
});
165+
166+
test.each([
167+
['left', 'flex-start'],
168+
['center', 'center'],
169+
['right', 'flex-end'],
170+
] as const)('uses the global %s alignment', (alignment, justify) => {
171+
mockStudyConfig = {
172+
uiConfig: {
173+
...mockStudyConfig.uiConfig,
174+
nextButtonAlignment: alignment,
175+
},
176+
};
177+
178+
const html = renderToStaticMarkup(<NextButton checkAnswer={null} onNext={vi.fn()} />);
179+
expect(html).toContain(`data-justify="${justify}"`);
180+
});
181+
182+
test('component alignment overrides the global alignment', () => {
183+
mockStudyConfig = {
184+
uiConfig: {
185+
...mockStudyConfig.uiConfig,
186+
nextButtonAlignment: 'left',
187+
},
188+
};
189+
190+
const html = renderToStaticMarkup(
191+
<NextButton
192+
config={{
193+
type: 'questionnaire', response: [], nextButtonAlignment: 'center',
194+
}}
195+
checkAnswer={null}
196+
onNext={vi.fn()}
197+
/>,
198+
);
199+
expect(html).toContain('data-justify="center"');
200+
});
201+
202+
test.each(['sidebar', 'aboveStimulus', 'belowStimulus'] as const)(
203+
'keeps Previous, Check Answer, and Next in order at %s',
204+
(location) => {
205+
const html = renderToStaticMarkup(
206+
<NextButton
207+
config={{ type: 'questionnaire', response: [], previousButton: true }}
208+
location={location}
209+
checkAnswer={<button type="button">Check Answer</button>}
210+
onNext={vi.fn()}
211+
/>,
212+
);
213+
214+
expect(html.indexOf('Previous')).toBeLessThan(html.indexOf('Check Answer'));
215+
expect(html.indexOf('Check Answer')).toBeLessThan(html.indexOf('Next'));
216+
},
217+
);
218+
158219
test('shows "Please wait" alert after render when nextButtonEnableTime is set', async () => {
159220
mockStudyConfig = {
160221
uiConfig: {

src/parser/LibraryConfigSchema.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@
8080
"description": "The meta data for the component. This is used to identify and provide additional information for the component in the admin panel.",
8181
"type": "object"
8282
},
83+
"nextButtonAlignment": {
84+
"$ref": "#/definitions/NextButtonAlignment",
85+
"description": "The horizontal alignment of the navigation action group. If present, will override the alignment setting in the uiConfig."
86+
},
8387
"nextButtonAutoAdvanceTime": {
8488
"description": "The time in milliseconds after which the participant is automatically advanced to the next component without saving answers from the current component.",
8589
"type": "number"
@@ -956,6 +960,10 @@
956960
"description": "The meta data for the component. This is used to identify and provide additional information for the component in the admin panel.",
957961
"type": "object"
958962
},
963+
"nextButtonAlignment": {
964+
"$ref": "#/definitions/NextButtonAlignment",
965+
"description": "The horizontal alignment of the navigation action group. If present, will override the alignment setting in the uiConfig."
966+
},
959967
"nextButtonAutoAdvanceTime": {
960968
"description": "The time in milliseconds after which the participant is automatically advanced to the next component without saving answers from the current component.",
961969
"type": "number"
@@ -1231,6 +1239,10 @@
12311239
"description": "The meta data for the component. This is used to identify and provide additional information for the component in the admin panel.",
12321240
"type": "object"
12331241
},
1242+
"nextButtonAlignment": {
1243+
"$ref": "#/definitions/NextButtonAlignment",
1244+
"description": "The horizontal alignment of the navigation action group. If present, will override the alignment setting in the uiConfig."
1245+
},
12341246
"nextButtonAutoAdvanceTime": {
12351247
"description": "The time in milliseconds after which the participant is automatically advanced to the next component without saving answers from the current component.",
12361248
"type": "number"
@@ -1761,6 +1773,10 @@
17611773
"description": "The meta data for the component. This is used to identify and provide additional information for the component in the admin panel.",
17621774
"type": "object"
17631775
},
1776+
"nextButtonAlignment": {
1777+
"$ref": "#/definitions/NextButtonAlignment",
1778+
"description": "The horizontal alignment of the navigation action group. If present, will override the alignment setting in the uiConfig."
1779+
},
17641780
"nextButtonAutoAdvanceTime": {
17651781
"description": "The time in milliseconds after which the participant is automatically advanced to the next component without saving answers from the current component.",
17661782
"type": "number"
@@ -2207,6 +2223,14 @@
22072223
}
22082224
]
22092225
},
2226+
"NextButtonAlignment": {
2227+
"enum": [
2228+
"left",
2229+
"center",
2230+
"right"
2231+
],
2232+
"type": "string"
2233+
},
22102234
"NumberOption": {
22112235
"additionalProperties": false,
22122236
"description": "The NumberOption interface is used to define the options for a slider response. The label is the text that is displayed to the user, and the value is the value that is stored in the data file.",
@@ -2361,6 +2385,10 @@
23612385
"description": "The meta data for the component. This is used to identify and provide additional information for the component in the admin panel.",
23622386
"type": "object"
23632387
},
2388+
"nextButtonAlignment": {
2389+
"$ref": "#/definitions/NextButtonAlignment",
2390+
"description": "The horizontal alignment of the navigation action group. If present, will override the alignment setting in the uiConfig."
2391+
},
23642392
"nextButtonAutoAdvanceTime": {
23652393
"description": "The time in milliseconds after which the participant is automatically advanced to the next component without saving answers from the current component.",
23662394
"type": "number"
@@ -2783,6 +2811,10 @@
27832811
"description": "The meta data for the component. This is used to identify and provide additional information for the component in the admin panel.",
27842812
"type": "object"
27852813
},
2814+
"nextButtonAlignment": {
2815+
"$ref": "#/definitions/NextButtonAlignment",
2816+
"description": "The horizontal alignment of the navigation action group. If present, will override the alignment setting in the uiConfig."
2817+
},
27862818
"nextButtonAutoAdvanceTime": {
27872819
"description": "The time in milliseconds after which the participant is automatically advanced to the next component without saving answers from the current component.",
27882820
"type": "number"
@@ -3601,6 +3633,10 @@
36013633
"description": "The meta data for the component. This is used to identify and provide additional information for the component in the admin panel.",
36023634
"type": "object"
36033635
},
3636+
"nextButtonAlignment": {
3637+
"$ref": "#/definitions/NextButtonAlignment",
3638+
"description": "The horizontal alignment of the navigation action group. If present, will override the alignment setting in the uiConfig."
3639+
},
36043640
"nextButtonAutoAdvanceTime": {
36053641
"description": "The time in milliseconds after which the participant is automatically advanced to the next component without saving answers from the current component.",
36063642
"type": "number"
@@ -3768,6 +3804,10 @@
37683804
"description": "The meta data for the component. This is used to identify and provide additional information for the component in the admin panel.",
37693805
"type": "object"
37703806
},
3807+
"nextButtonAlignment": {
3808+
"$ref": "#/definitions/NextButtonAlignment",
3809+
"description": "The horizontal alignment of the navigation action group. If present, will override the alignment setting in the uiConfig."
3810+
},
37713811
"nextButtonAutoAdvanceTime": {
37723812
"description": "The time in milliseconds after which the participant is automatically advanced to the next component without saving answers from the current component.",
37733813
"type": "number"
@@ -3943,6 +3983,10 @@
39433983
"description": "The meta data for the component. This is used to identify and provide additional information for the component in the admin panel.",
39443984
"type": "object"
39453985
},
3986+
"nextButtonAlignment": {
3987+
"$ref": "#/definitions/NextButtonAlignment",
3988+
"description": "The horizontal alignment of the navigation action group. If present, will override the alignment setting in the uiConfig."
3989+
},
39463990
"nextButtonAutoAdvanceTime": {
39473991
"description": "The time in milliseconds after which the participant is automatically advanced to the next component without saving answers from the current component.",
39483992
"type": "number"
@@ -4114,6 +4158,10 @@
41144158
"description": "The meta data for the component. This is used to identify and provide additional information for the component in the admin panel.",
41154159
"type": "object"
41164160
},
4161+
"nextButtonAlignment": {
4162+
"$ref": "#/definitions/NextButtonAlignment",
4163+
"description": "The horizontal alignment of the navigation action group. If present, will override the alignment setting in the uiConfig."
4164+
},
41174165
"nextButtonAutoAdvanceTime": {
41184166
"description": "The time in milliseconds after which the participant is automatically advanced to the next component without saving answers from the current component.",
41194167
"type": "number"

0 commit comments

Comments
 (0)