Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ca657b7
feat: add ManualPivot component and integrate into DrawerEdit
Mar 12, 2026
240e25e
style: format SCSS and TypeScript code for improved readability
Mar 12, 2026
eb4d476
test: update DrawerEdit.spec.tsx to expect three action buttons
Mar 12, 2026
854cc72
feat: enhance rearrange functionality with modal descriptions in tran…
Mar 12, 2026
958acb3
feat: add labels to ManualPivot component for improved accessibility
Mar 12, 2026
dcf99f7
feat: add focus-visible styles and tabIndex to list items in ManualPi…
Mar 16, 2026
674481b
feat: implement manual pivot functionality in ManualPivot component a…
Mar 17, 2026
d2a2c0d
refactor: improve formatting and readability in ManualPivot component…
Mar 17, 2026
97892e2
Merge branch 'main' into feature/PXWEB2-987-manual-pivot
Mar 17, 2026
c36a6c8
Merge branch 'main' into feature/PXWEB2-987-manual-pivot
Mar 23, 2026
79ac0d0
feat: enhance manual pivot with drop preview functionality and improv…
Mar 23, 2026
75ab9da
refactor: improve code readability and formatting in ManualPivot comp…
Mar 23, 2026
d5ab239
feat: implement keyboard navigation and drag-and-drop functionality i…
Mar 23, 2026
a96537f
refactor: improve code formatting and readability in ManualPivot comp…
Mar 23, 2026
bfb842f
feat: enhance Modal and ManualPivot components with improved keyboard…
Mar 23, 2026
36ad278
refactor: streamline import statements in Modal component
Mar 23, 2026
1878fb0
feat: enhance ManualPivot component with improved label capitalizatio…
Mar 23, 2026
318fbf2
refactor: improve code formatting in ManualPivot component for better…
Mar 23, 2026
df6a4f4
refactor: consolidate import statements in ManualPivot component for …
Mar 24, 2026
9005c2c
feat: enhance keyboard handling in ManualPivot component with improve…
Mar 24, 2026
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 packages/pxweb2-ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export * from './lib/components/Link/Link';
export * from './lib/components/LinkCard/LinkCard';
export * from './lib/components/List';
export * from './lib/components/LocalAlert/LocalAlert';
export * from './lib/components/Modal/Modal';
export * from './lib/components/Notes/MandatoryNotes';
export * from './lib/components/Notes/MandatoryTableNotes';
export * from './lib/components/Notes/MandatoryVariableNotes';
Expand Down
16 changes: 14 additions & 2 deletions packages/pxweb2-ui/src/lib/components/Modal/Modal.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ describe('Modal', () => {
expect(screen.getByText('Test Heading')).toBeDefined();
});

it('should handle Enter key press on buttons', () => {
it('should not close on button keyup Enter alone', () => {
const onCloseMock = vi.fn();

render(
Expand All @@ -157,7 +157,19 @@ describe('Modal', () => {

fireEvent.keyUp(confirmButton, { key: 'Enter' });

expect(onCloseMock).toHaveBeenCalledWith(true, 'Enter');
expect(onCloseMock).not.toHaveBeenCalled();
});

it('should focus modal body when opened', () => {
const { container } = render(
<Modal isOpen={true}>
<span>test</span>
</Modal>,
);

const body = container.querySelector('div[class*="body"]');

expect(body).toBe(document.activeElement);
});

it('should update body overflow style when opening and closing', () => {
Expand Down
52 changes: 11 additions & 41 deletions packages/pxweb2-ui/src/lib/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import cl from 'clsx';
import { useTranslation } from 'react-i18next';
import {
useCallback,
useEffect,
useRef,
useState,
KeyboardEvent as ReactKeyboardEvent,
MouseEvent,
} from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';

import classes from './Modal.module.scss';
import Label from '../Typography/Label/Label';
Expand Down Expand Up @@ -39,6 +32,7 @@ export function Modal({
const cssClasses = className.length > 0 ? ' ' + className : '';
const [isModalOpen, setIsModalOpen] = useState(isOpen);
const modalRef = useRef<HTMLDialogElement | null>(null);
const bodyRef = useRef<HTMLDivElement | null>(null);
let cancelLabelValue = cancelLabel;
let confirmLabelValue = confirmLabel;

Expand All @@ -59,6 +53,7 @@ export function Modal({
if (isModalOpen) {
modalElement.showModal();
setWindowScroll(false);
bodyRef.current?.focus();
} else {
modalElement.close();
setWindowScroll(true);
Expand All @@ -74,33 +69,15 @@ export function Modal({
};

const handleCloseModal = useCallback(
(updated: boolean, event?: ReactKeyboardEvent | MouseEvent) => {
const handleKeyboardEvent = (
updated: boolean,
event: ReactKeyboardEvent,
) => {
const keyPress = event.key;
const isValidKeyPress =
keyPress === 'Enter' || keyPress === ' ' || keyPress === 'Escape';

if (onClose && isValidKeyPress) {
setWindowScroll(true);
(updated: boolean, keyPress?: ' ' | 'Enter' | 'Escape') => {
if (onClose) {
setWindowScroll(true);
if (keyPress) {
onClose(updated, keyPress);
setIsModalOpen(false);
}
};

const handleMouseEvent = (updated: boolean) => {
if (onClose) {
setWindowScroll(true);
} else {
onClose(updated);
setIsModalOpen(false);
}
};
if (event) {
handleKeyboardEvent(updated, event as ReactKeyboardEvent);
} else {
handleMouseEvent(updated);
setIsModalOpen(false);
}
},
[onClose],
Expand All @@ -110,11 +87,7 @@ export function Modal({
// Handle the Escape key to close the modal
const handleKeyDownInModal = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
// 'as unknown as ReactKeyboardEvent' is a hack to avoid the type error when passing the event to the function
handleCloseModal(false, event as unknown as ReactKeyboardEvent);
}
if (event.key === 'Enter') {
event.preventDefault(); // Prevent the default behavior of the Enter key on buttons (turns it into a mouse click event)
handleCloseModal(false, 'Escape');
}
};

Expand Down Expand Up @@ -149,14 +122,13 @@ export function Modal({
icon="XMark"
type="button"
onClick={() => handleCloseModal(false)}
onKeyUp={(event) => handleCloseModal(false, event)}
aria-label={cancelLabelValue}
></Button>
</div>
</div>
</div>
{/* tabIndex to fix the div being focusable for some reason */}
<div className={cl(classes.body)} tabIndex={-1}>
<div ref={bodyRef} className={cl(classes.body)} tabIndex={-1}>
{children}
</div>
<div className={cl(classes.footer)}>
Expand All @@ -166,7 +138,6 @@ export function Modal({
size="medium"
type="button"
onClick={() => handleCloseModal(true)}
onKeyUp={(event) => handleCloseModal(true, event)}
aria-label={confirmLabelValue}
>
{confirmLabelValue}
Expand All @@ -176,7 +147,6 @@ export function Modal({
size="medium"
type="button"
onClick={() => handleCloseModal(false)}
onKeyUp={(event) => handleCloseModal(false, event)}
aria-label={cancelLabelValue}
>
{cancelLabelValue}
Expand Down
10 changes: 9 additions & 1 deletion packages/pxweb2/public/locales/ar/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,15 @@
},
"rearrange": {
"title": "إعادة ترتيب الجدول",
"description": "نص الوصف..."
"description": "نص الوصف...",
"rearrange_modal": {
"cancel_button": "يلغي",
"confirm_button": "يحفظ",
"description": "اسحب وأفلت المتغيرات لإعادة ترتيب تخطيط الجدول",
"title": "إعادة ترتيب الجدول",
"stub_variable_header": "متغيرات الستوب",
"heading_variable_header": "متغيرات العنوان"
}
},
"change_order": {
"title": "تغيير الترتيب",
Expand Down
10 changes: 9 additions & 1 deletion packages/pxweb2/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,15 @@
},
"rearrange": {
"title": "Rearrange table",
"description": "Description text..."
"description": "Description text...",
"rearrange_modal": {
"cancel_button": "Cancel",
"confirm_button": "Confirm",
"description": "Drag and drop the variables to rearrange the table layout",
"title": "Rearrange table",
"stub_variable_header": "Stub variables",
"heading_variable_header": "Heading variables"
}
},
"change_order": {
"title": "Change order",
Expand Down
10 changes: 9 additions & 1 deletion packages/pxweb2/public/locales/no/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,15 @@
},
"rearrange": {
"title": "Omorganiser tabell",
"description": "Beskrivelsestekst..."
"description": "Beskrivelsestekst...",
"rearrange_modal": {
"cancel_button": "Avbryt",
"confirm_button": "Bekreft",
"description": "Dra og slipp variablene for å endre tabelloppsettet",
"title": "Tilpass tabell",
"stub_variable_header": "Rader",
"heading_variable_header": "Kolonner"
}
},
"change_order": {
"title": "Endre rekkefølge",
Expand Down
10 changes: 9 additions & 1 deletion packages/pxweb2/public/locales/sv/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,15 @@
},
"rearrange": {
"title": "Anpassa tabell",
"description": "Beskrivande text..."
"description": "Beskrivande text...",
"rearrange_modal": {
"cancel_button": "Avbryt",
"confirm_button": "Bekräfta",
"description": "Dra och släpp variablerna för att ändra tabellens layout",
"title": "Anpassa tabell",
"stub_variable_header": "Rader",
"heading_variable_header": "Kolumner"
}
},
"change_order": {
"title": "Ändra ordning",
Expand Down
8 changes: 8 additions & 0 deletions packages/pxweb2/src/@types/resources.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,14 @@ interface Resources {
rearrange: {
description: 'Description text...';
title: 'Rearrange table';
rearrange_modal: {
cancel_button: 'Cancel';
confirm_button: 'Confirm';
description: 'Drag and drop the variables to rearrange the table layout';
title: 'Rearrange table';
stub_variable_header: 'Stub variables';
heading_variable_header: 'Header variables';
};
};
title: 'Customise';
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
.wrapper {
display: flex;
flex-direction: row;
align-items: stretch;
width: 100%;
gap: 12px;
}

.groupColumn {
flex: 1 1 0;
min-width: 0;
display: flex;
flex-direction: column;
}

.groupZone {
position: relative;
width: 100%;
border: 1px solid var(--px-color-border-subtle);
border-radius: var(--px-border-radius-medium);
padding: 8px;
min-height: 220px;
}

.list {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 6px;
position: relative;
z-index: 1;
}

.dropPlaceholder {
position: relative;
z-index: 2;
width: 100%;
height: var(--drop-preview-height, 40px);
border: 1px dashed var(--px-color-border-focus-outline);
border-radius: var(--px-border-radius-small);
padding: 8px 10px;
display: flex;
align-items: center;
background: color-mix(
in srgb,
var(--px-color-background-subtle) 88%,
transparent
);
pointer-events: none;
}

.dropPlaceholderLabel {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

.listItem {
position: relative;
z-index: 1;
border: 1px solid var(--px-color-border-subtle);
border-radius: var(--px-border-radius-small);
background: var(--px-color-surface-default);
padding: 8px 10px;
cursor: grab;
-webkit-user-select: none;
user-select: none;
touch-action: none;
transition: box-shadow 120ms ease;
}

.listItem:active {
cursor: grabbing;
}

.listItem:focus-visible {
outline: 2px solid var(--px-color-border-focus-outline);
outline-offset: 1px;
}

.visuallyHidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
Loading
Loading