From 956db62e2c62fc224bc4137af79e1d3d82217386 Mon Sep 17 00:00:00 2001 From: Jackie Jou Date: Fri, 26 Jun 2026 12:41:26 -0700 Subject: [PATCH] feat(activity-feed-v2): scaffold accessible task modal v2 shell --- i18n/en-US.properties | 10 ++++ .../task-modal-v2/TaskModalV2.scss | 5 ++ .../task-modal-v2/TaskModalV2.tsx | 58 +++++++++++++++++++ .../__tests__/TaskModalV2.test.tsx | 54 +++++++++++++++++ .../activity-feed-v2/task-modal-v2/index.ts | 2 + .../task-modal-v2/messages.ts | 31 ++++++++++ 6 files changed, 160 insertions(+) create mode 100644 src/elements/content-sidebar/activity-feed-v2/task-modal-v2/TaskModalV2.scss create mode 100644 src/elements/content-sidebar/activity-feed-v2/task-modal-v2/TaskModalV2.tsx create mode 100644 src/elements/content-sidebar/activity-feed-v2/task-modal-v2/__tests__/TaskModalV2.test.tsx create mode 100644 src/elements/content-sidebar/activity-feed-v2/task-modal-v2/index.ts create mode 100644 src/elements/content-sidebar/activity-feed-v2/task-modal-v2/messages.ts diff --git a/i18n/en-US.properties b/i18n/en-US.properties index f6a0cfac0e..3681645d7a 100644 --- a/i18n/en-US.properties +++ b/i18n/en-US.properties @@ -872,6 +872,16 @@ be.sort = Sort be.statusSkill = Status # Generic success label. be.success = Success +# aria-label for the task modal close button +be.taskModalV2.close = Close +# Title of the modal for creating an approval task +be.taskModalV2.createApprovalTask = Create Approval Task +# Title of the modal for creating a general task +be.taskModalV2.createGeneralTask = Create General Task +# Title of the modal for editing an existing approval task +be.taskModalV2.editApprovalTask = Modify Approval Task +# Title of the modal for editing an existing general task +be.taskModalV2.editGeneralTask = Modify General Task # Shown instead of todays date. be.today = today # Label for keywords/topics skill section in the preview sidebar diff --git a/src/elements/content-sidebar/activity-feed-v2/task-modal-v2/TaskModalV2.scss b/src/elements/content-sidebar/activity-feed-v2/task-modal-v2/TaskModalV2.scss new file mode 100644 index 0000000000..4ab1ca010f --- /dev/null +++ b/src/elements/content-sidebar/activity-feed-v2/task-modal-v2/TaskModalV2.scss @@ -0,0 +1,5 @@ +.bcs-NewTaskModal { + display: flex; + flex-direction: column; + gap: var(--bp-space-040); +} diff --git a/src/elements/content-sidebar/activity-feed-v2/task-modal-v2/TaskModalV2.tsx b/src/elements/content-sidebar/activity-feed-v2/task-modal-v2/TaskModalV2.tsx new file mode 100644 index 0000000000..b34b249eb4 --- /dev/null +++ b/src/elements/content-sidebar/activity-feed-v2/task-modal-v2/TaskModalV2.tsx @@ -0,0 +1,58 @@ +import * as React from 'react'; +import { useIntl } from 'react-intl'; +import type { MessageDescriptor } from 'react-intl'; + +import { Modal } from '@box/blueprint-web'; + +import { TASK_EDIT_MODE_CREATE, TASK_TYPE_APPROVAL, TASK_TYPE_GENERAL } from '../../../../constants'; + +import type { TaskEditMode, TaskType } from '../../../../common/types/tasks'; + +import messages from './messages'; + +import './TaskModalV2.scss'; + +export type TaskModalV2Props = { + editMode?: TaskEditMode; + isOpen: boolean; + onClose: () => void; + taskType: TaskType; +}; + +const getTitleMessage = (taskType: TaskType, editMode: TaskEditMode): MessageDescriptor => { + const isCreate = editMode === TASK_EDIT_MODE_CREATE; + if (taskType === TASK_TYPE_GENERAL) { + return isCreate ? messages.createGeneralTaskTitle : messages.editGeneralTaskTitle; + } + return isCreate ? messages.createApprovalTaskTitle : messages.editApprovalTaskTitle; +}; + +const TaskModalV2 = ({ + editMode = TASK_EDIT_MODE_CREATE, + isOpen, + onClose, + taskType = TASK_TYPE_APPROVAL, +}: TaskModalV2Props) => { + const { formatMessage } = useIntl(); + const titleMessage = getTitleMessage(taskType, editMode); + + const handleOpenChange = (open: boolean) => { + if (!open) { + onClose(); + } + }; + + return ( + + + {formatMessage(titleMessage)} + +
Form goes here
+
+ +
+
+ ); +}; + +export default TaskModalV2; diff --git a/src/elements/content-sidebar/activity-feed-v2/task-modal-v2/__tests__/TaskModalV2.test.tsx b/src/elements/content-sidebar/activity-feed-v2/task-modal-v2/__tests__/TaskModalV2.test.tsx new file mode 100644 index 0000000000..89a9dea0b2 --- /dev/null +++ b/src/elements/content-sidebar/activity-feed-v2/task-modal-v2/__tests__/TaskModalV2.test.tsx @@ -0,0 +1,54 @@ +import * as React from 'react'; + +import { render, screen, userEvent } from '../../../../../test-utils/testing-library'; +import { + TASK_EDIT_MODE_CREATE, + TASK_EDIT_MODE_EDIT, + TASK_TYPE_APPROVAL, + TASK_TYPE_GENERAL, +} from '../../../../../constants'; +import TaskModalV2 from '../TaskModalV2'; + +import type { TaskModalV2Props } from '../TaskModalV2'; + +describe('elements/content-sidebar/task-modal-v2/TaskModalV2', () => { + const renderModal = (props: Partial = {}) => + render(); + + test('renders nothing when isOpen is false', () => { + renderModal({ isOpen: false }); + expect(screen.queryByTestId('task-modal-v2')).not.toBeInTheDocument(); + }); + + test('renders the modal with a placeholder form region when isOpen is true', () => { + renderModal(); + expect(screen.getByTestId('task-modal-v2')).toBeVisible(); + expect(screen.getByText('Form goes here')).toBeVisible(); + }); + + test.each([ + [TASK_TYPE_APPROVAL, TASK_EDIT_MODE_CREATE, 'Create Approval Task'], + [TASK_TYPE_APPROVAL, TASK_EDIT_MODE_EDIT, 'Modify Approval Task'], + [TASK_TYPE_GENERAL, TASK_EDIT_MODE_CREATE, 'Create General Task'], + [TASK_TYPE_GENERAL, TASK_EDIT_MODE_EDIT, 'Modify General Task'], + ])('renders the correct title for taskType=%s editMode=%s', (taskType, editMode, expectedTitle) => { + renderModal({ taskType, editMode }); + expect(screen.getByRole('heading', { name: expectedTitle })).toBeVisible(); + }); + + test('calls onClose when the close button is clicked', async () => { + const user = userEvent(); + const onClose = jest.fn(); + renderModal({ onClose }); + await user.click(screen.getByRole('button', { name: 'Close' })); + expect(onClose).toHaveBeenCalledTimes(1); + }); + + test('calls onClose when Escape is pressed', async () => { + const user = userEvent(); + const onClose = jest.fn(); + renderModal({ onClose }); + await user.keyboard('{Escape}'); + expect(onClose).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/elements/content-sidebar/activity-feed-v2/task-modal-v2/index.ts b/src/elements/content-sidebar/activity-feed-v2/task-modal-v2/index.ts new file mode 100644 index 0000000000..ea9831cbff --- /dev/null +++ b/src/elements/content-sidebar/activity-feed-v2/task-modal-v2/index.ts @@ -0,0 +1,2 @@ +export { default } from './TaskModalV2'; +export type { TaskModalV2Props } from './TaskModalV2'; diff --git a/src/elements/content-sidebar/activity-feed-v2/task-modal-v2/messages.ts b/src/elements/content-sidebar/activity-feed-v2/task-modal-v2/messages.ts new file mode 100644 index 0000000000..486cb9446d --- /dev/null +++ b/src/elements/content-sidebar/activity-feed-v2/task-modal-v2/messages.ts @@ -0,0 +1,31 @@ +import { defineMessages } from 'react-intl'; + +const messages = defineMessages({ + closeLabel: { + id: 'be.taskModalV2.close', + defaultMessage: 'Close', + description: 'aria-label for the task modal close button', + }, + createApprovalTaskTitle: { + id: 'be.taskModalV2.createApprovalTask', + defaultMessage: 'Create Approval Task', + description: 'Title of the modal for creating an approval task', + }, + createGeneralTaskTitle: { + id: 'be.taskModalV2.createGeneralTask', + defaultMessage: 'Create General Task', + description: 'Title of the modal for creating a general task', + }, + editApprovalTaskTitle: { + id: 'be.taskModalV2.editApprovalTask', + defaultMessage: 'Modify Approval Task', + description: 'Title of the modal for editing an existing approval task', + }, + editGeneralTaskTitle: { + id: 'be.taskModalV2.editGeneralTask', + defaultMessage: 'Modify General Task', + description: 'Title of the modal for editing an existing general task', + }, +}); + +export default messages;