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

Added the ability to move question #274

Merged
merged 22 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
803b498
Part 1 of moving question to another page
natashapl Jul 13, 2024
aae11f6
Part 2 of moving question to another page
natashapl Jul 13, 2024
396256f
Merge branch 'main' into move-question-to-another-page
natashapl Jul 13, 2024
b8a7269
Added the ability to move question to another page
natashapl Jul 24, 2024
aab0886
Added the ability to move question to another page
natashapl Jul 24, 2024
1aeedbd
Merge branch 'main' into move-question-to-another-page
natashapl Jul 24, 2024
f343011
Merge branch 'main' into move-question-to-another-page
natashapl Jul 24, 2024
56fdff7
fixing get by role selector to remove ambiguity
ethangardner Jul 24, 2024
faf1b36
removed logging
ethangardner Jul 24, 2024
bca169d
Removed some console logs
natashapl Jul 24, 2024
02dfdef
Fixed some bugs and accessibility issues
natashapl Jul 26, 2024
594ec55
Fixed some bugs and accessibility issues part 2
natashapl Jul 26, 2024
12b016c
Addressed some bugs and feedback reviews
natashapl Jul 31, 2024
5066c38
Addressed some bugs and feedback reviews Part 2
natashapl Jul 31, 2024
acee9ad
Updated the builder test to account for source page
natashapl Jul 31, 2024
5f60cfe
Merge branch 'main' into move-question-to-another-page
natashapl Jul 31, 2024
964d36c
Made updates per the reviews in my PR
natashapl Aug 6, 2024
6e1766b
Made updates per the reviews in my PR Part 2
natashapl Aug 6, 2024
bcba4ba
Ensured that page types can't be moved
natashapl Aug 6, 2024
14dc6d0
A minor update to the store.ts
natashapl Aug 6, 2024
0cbf279
A minor update to the store.ts Part 2
natashapl Aug 6, 2024
f149ef6
Merge branch 'main' into move-question-to-another-page
natashapl Aug 17, 2024
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 apps/spotlight/src/env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
2 changes: 1 addition & 1 deletion e2e/src/create.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const createNewForm = async (page: Page) => {
}

const addQuestions = async (page: Page) => {
const menuButton = page.getByRole('button', { name: 'Question' });
const menuButton = page.getByRole('button', { name: 'Question', exact: true });
await menuButton.click();
await page.getByRole('button', { name: 'Short Answer' }).click();
await menuButton.click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,12 +282,9 @@ const AddPatternDropdownContent = ({
className={`${styles.dropdownMenu} usa-list usa-list--unstyled position-absolute bg-white z-100 shadow-3 text-left`}
>
{availablePatterns.map(([patternType, pattern], index) => (
<li
key={index}
className={`${styles.dropdownItem} padding-1 cursor-pointer margin-left-1`}
>
<li key={index} className={`${styles.dropdownItem} margin-left-1`}>
<button
className="bg-transparent padding-0 border-0"
className="bg-transparent padding-1 text-left width-full cursor-pointer border-0"
onClick={() => {
patternSelected(patternType);
}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import React, { useState, useRef, useEffect } from 'react';
import { useFormManagerStore } from '../../../store';
import styles from '../../formEditStyles.module.css';

interface MovePatternDropdownProps {
isFieldset: boolean;
}

// Define the extended type for pages
interface PageWithLabel {
id: string;
type: string;
data: {
title: string;
patterns: string[];
};
specialLabel?: string;
}

const MovePatternDropdown: React.FC<MovePatternDropdownProps> = ({
isFieldset,
}) => {
const context = useFormManagerStore(state => state.context);
const [dropdownOpen, setDropdownOpen] = useState(false);
const [targetPage, setTargetPage] = useState('');
const [moveToPosition, setMoveToPosition] = useState('');
const dropdownRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const pages = useFormManagerStore(state =>
Object.values(state.session.form.patterns).filter(p => p.type === 'page')
);
const movePatternToPage = useFormManagerStore(state => state.movePattern);
const focusPatternId = useFormManagerStore(state => state.focus?.pattern.id);
const useAvailablePages = () => {
const currentPageIndex = pages.findIndex(page =>
page.data.patterns.includes(focusPatternId || '')
);
const page1Count = pages.reduce(
(count, page) => count + (page.data.title === 'Page 1' ? 1 : 0),
0
);
const availablePages: PageWithLabel[] =
page1Count > 1
? pages.slice(1).map((page, index) => {
if (index + 1 === currentPageIndex) {
return { ...page, specialLabel: 'Current page' };
}
return page;
})
: pages.map((page, index) => {
if (index === currentPageIndex) {
return { ...page, specialLabel: 'Current page' };
}
return page;
});

return availablePages;
};
const availablePages = useAvailablePages();
const currentPageIndex = pages.findIndex(page =>
page.data.patterns.includes(focusPatternId || '')
);
const sourcePage = pages[currentPageIndex]?.id;
const handleMovePattern = () => {
if (focusPatternId && targetPage) {
movePatternToPage(sourcePage, targetPage, focusPatternId, moveToPosition);
}
setDropdownOpen(false);
};
const toggleDropdown = () => {
setDropdownOpen(!dropdownOpen);
};
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setDropdownOpen(false);
}
};
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setDropdownOpen(false);
buttonRef.current?.focus();
}
};

useEffect(() => {
if (dropdownOpen) {
document.addEventListener('mousedown', handleClickOutside);
dropdownRef.current?.addEventListener('keydown', handleKeyDown);
} else {
document.removeEventListener('mousedown', handleClickOutside);
dropdownRef.current?.removeEventListener('keydown', handleKeyDown);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
dropdownRef.current?.removeEventListener('keydown', handleKeyDown);
};
}, [dropdownOpen]);

return (
<div
className={`${styles.moveToPageWrapper} display-inline-block text-ttop position-relative`}
ref={dropdownRef}
>
<p
className={`${styles.movePatternButton} margin-top-1 display-inline-block text-ttop cursor-pointer`}
>
<button
className="usa-button--outline usa-button--unstyled margin-right-0 padding-top-1 padding-left-05 padding-bottom-05"
type="button"
ref={buttonRef}
aria-haspopup="true"
aria-expanded={dropdownOpen ? 'true' : 'false'}
onClick={event => {
event.preventDefault();
toggleDropdown();
}}
>
<span className="display-inline-block text-ttop">
{isFieldset ? 'Move fieldset' : 'Move question'}
</span>
<svg
className="usa-icon display-inline-block text-ttop"
aria-hidden="true"
focusable="false"
role="img"
>
<use
xlinkHref={`${context.uswdsRoot}img/sprite.svg#expand_more`}
></use>
</svg>
</button>
</p>
{dropdownOpen && (
<div className={`${styles.dropDown} padding-2`} tabIndex={-1}>
<div className={`${styles.moveToPagePosition} margin-bottom-1`}>
<label
className="usa-label display-inline-block text-ttop margin-right-1"
htmlFor="pagenumbers"
>
Page:
</label>
<select
className="usa-select display-inline-block text-ttop"
name="pagenumbers"
id="pagenumbers"
value={targetPage}
onChange={e => setTargetPage(e.target.value)}
>
<option value="" disabled>
Select page
</option>
{availablePages.map((page, index) => (
<option key={page.id} value={page.id}>
{page.specialLabel || page.data.title || `Page ${index + 2}`}
</option>
))}
</select>
</div>
<div className={`${styles.moveToPagePosition} margin-bottom-1`}>
<label
className="usa-label margin-right-1 display-inline-block text-ttop"
htmlFor="elementPosition"
>
Position:
</label>
<select
className="usa-select display-inline-block text-ttop"
name="elementPosition"
id="elementPosition"
value={moveToPosition}
onChange={e =>
setMoveToPosition(e.target.value as 'top' | 'bottom')
}
>
<option value="" disabled>
Select position
</option>
<option value="top">Top of page</option>
<option value="bottom">Bottom of page</option>
</select>
</div>
<p>
<button
type="button"
aria-label={
isFieldset
? 'Move fieldset to another page'
: 'Move question to another page'
}
title={
isFieldset
? 'Move fieldset to another page'
: 'Move question to another page'
}
className="usa-button margin-right-0"
onClick={handleMovePattern}
>
{isFieldset ? 'Move fieldset' : 'Move question'}
</button>
</p>
</div>
)}
</div>
);
};

export default MovePatternDropdown;
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React, { PropsWithChildren, ReactElement } from 'react';
import React, { PropsWithChildren, ReactElement, useMemo } from 'react';
import classNames from 'classnames';

import { useFormManagerStore } from '../../../store';
import MovePatternDropdown from './MovePatternDropdown';
import styles from '../../formEditStyles.module.css';

type PatternEditActionsProps = PropsWithChildren<{
children?: ReactElement;
Expand All @@ -13,16 +15,42 @@ export const PatternEditActions = ({ children }: PatternEditActionsProps) => {
const { deleteSelectedPattern } = useFormManagerStore(state => ({
deleteSelectedPattern: state.deleteSelectedPattern,
}));
const focusPatternType = useFormManagerStore(
state => state.focus?.pattern.type
);
const patterns = useFormManagerStore(state =>
Object.values(state.session.form.patterns)
);
const focusPatternId = useFormManagerStore(state => state.focus?.pattern.id);
const isPatternInFieldset = useMemo(() => {
if (!focusPatternId) return false;
return patterns.some(
p => p.type === 'fieldset' && p.data.patterns.includes(focusPatternId)
);
}, [focusPatternId, patterns]);

const isFieldset = focusPatternType === 'fieldset';
const isPagePattern = focusPatternType === 'page';

return (
<>
<div className="border-top-1px border-base-lighter margin-top-2 margin-bottom-2 padding-top-1 width-full text-right pattern-edit-panel base-dark">
<span
className={classNames('display-inline-block', {
<div
className={`${styles.patternActionWrapper} margin-top-2 margin-bottom-1 padding-top-1 width-full pattern-edit-panel base-dark text-right`}
>
<div
className={classNames(
'border-top-1px border-bottom-1px border-base-lighter ',
{
'border-base-lighter': children,
'padding-right-1': children,
'margin-right-1': children,
})}
}
)}
>
{!isPatternInFieldset && !isPagePattern && (
<MovePatternDropdown isFieldset={isFieldset} />
)}
<span
className={`${styles.patternActionButtons} margin-top-1 margin-bottom-1 display-inline-block text-ttop`}
>
<button
type="button"
Expand Down Expand Up @@ -53,7 +81,12 @@ export const PatternEditActions = ({ children }: PatternEditActionsProps) => {
className="usa-button--outline usa-button--unstyled"
onClick={event => {
event.preventDefault();
deleteSelectedPattern();
const confirmed = window.confirm(
'Are you sure you want to delete this question?'
);
if (confirmed) {
deleteSelectedPattern();
}
}}
>
<svg
Expand All @@ -67,30 +100,21 @@ export const PatternEditActions = ({ children }: PatternEditActionsProps) => {
></use>
</svg>
</button>

<button
type="submit"
aria-label="Save changes to this pattern"
title="Save changes to this pattern"
className="usa-button--outline usa-button--unstyled text-success hover:text-success"
>
<svg
className="usa-icon usa-icon--size-3 margin-1 text-middle"
aria-hidden="true"
focusable="false"
role="img"
>
<use xlinkHref={`${context.uswdsRoot}img/sprite.svg#check`}></use>
</svg>
</button>

{children ? (
<span className="margin-left-1 padding-left-2 border-left-1px border-base-lighter">
{children}
</span>
<span className="padding-left-1 padding-top-2px">{children}</span>
) : null}
</span>
</div>
</>
<div className="padding-top-2">
<button
type="submit"
aria-label="Save and Close"
title="Save and Close"
className="usa-button"
>
Save and Close
</button>
</div>
</div>
);
};
Loading
Loading