Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions pages/shared/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ export const boardItemI18nStrings: BoardItemProps.I18nStrings = {
resizeHandleAriaLabel: "Resize handle",
resizeHandleAriaDescription:
"Use Space or Enter to activate resize, arrow keys to move, Space or Enter to submit, or Escape to discard. Be sure to temporarily disable any screen reader navigation feature that may interfere with the functionality of the arrow keys.",
dragHandleTooltipText: "Drag or select to move",
resizeHandleTooltipText: "Drag or select to resize",
};

export const itemsPaletteI18nStrings: ItemsPaletteProps.I18nStrings<ItemData> = {
Expand Down
14 changes: 13 additions & 1 deletion src/__tests__/__snapshots__/documenter.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,9 @@ ARIA labels:
* \`dragHandleAriaLabel\` (string) - the ARIA label for the drag handle.
* \`dragHandleAriaDescription\` (string, optional) - the ARIA description for the drag handle.
* \`resizeHandleAriaLabel\` (string) - the ARIA label for the resize handle.
* \`resizeHandleAriaDescription\` (string, optional) - the ARIA description for the resize handle.",
* \`resizeHandleAriaDescription\` (string, optional) - the ARIA description for the resize handle.
* \`dragHandleTooltipText\` (string, optional) - the ARIA description for the resize handle.
* \`resizeHandleTooltipText\` (string, optional) - the Text for the resize handle Tooltip.",
"inlineType": {
"name": "BoardItemProps.I18nStrings",
"properties": [
Expand All @@ -215,6 +217,11 @@ ARIA labels:
"optional": false,
"type": "string",
},
{
"name": "dragHandleTooltipText",
"optional": true,
"type": "string",
},
{
"name": "resizeHandleAriaDescription",
"optional": true,
Expand All @@ -225,6 +232,11 @@ ARIA labels:
"optional": false,
"type": "string",
},
{
"name": "resizeHandleTooltipText",
"optional": true,
"type": "string",
},
],
"type": "object",
},
Expand Down
7 changes: 5 additions & 2 deletions src/board-item/__tests__/board-item-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@ export function ItemContextWrapper({ children }: { children: ReactNode }) {
<ItemContext.Provider
value={{
isActive: false,
isHidden: false,
dragHandle: {
ref: { current: null },
onPointerDown: () => {},
onKeyDown: () => {},
isActive: false,
activeState: null,
onDirectionClick: () => {},
},
resizeHandle: {
onPointerDown: () => {},
onKeyDown: () => {},
isActive: false,
activeState: null,
onDirectionClick: () => {},
},
}}
>
Expand Down
74 changes: 73 additions & 1 deletion src/board-item/__tests__/board-item.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
// SPDX-License-Identifier: Apache-2.0

import { ReactElement } from "react";
import { cleanup, render as libRender } from "@testing-library/react";
import { cleanup, fireEvent, render as libRender } from "@testing-library/react";
import { afterEach, describe, expect, test } from "vitest";

import Button from "@cloudscape-design/components/button";
import Container from "@cloudscape-design/components/container";
import ExpandableSection from "@cloudscape-design/components/expandable-section";
import Header from "@cloudscape-design/components/header";
import DragHandleWrapper from "@cloudscape-design/components/test-utils/dom/internal/drag-handle";
import TooltipWrapper from "@cloudscape-design/components/test-utils/dom/internal/tooltip";

import "@cloudscape-design/components/test-utils/dom";
import BoardItem from "../../../lib/components/board-item";
Expand All @@ -18,6 +20,8 @@ import { ItemContextWrapper } from "./board-item-wrapper";
const i18nStrings = {
dragHandleAriaLabel: "Drag handle",
resizeHandleAriaLabel: "Resize handle",
dragHandleTooltipText: "Drag or select to move",
resizeHandleTooltipText: "Drag or select to resize",
};

function render(jsx: ReactElement) {
Expand Down Expand Up @@ -61,4 +65,72 @@ describe("WidgetContainer", () => {
expect(getByLabelText("Drag handle")).toBeDefined();
expect(getByLabelText("Resize handle")).toBeDefined();
});

test("renders drag handle tooltip text if provided", () => {
render(<BoardItem i18nStrings={i18nStrings} />);
const wrapper = createWrapper();
const dragHandleEl = wrapper.findBoardItem()!.findDragHandle().getElement();

fireEvent(dragHandleEl, new MouseEvent("pointerover", { bubbles: true }));
const tooltipEl = wrapper.findByClassName(TooltipWrapper.rootSelector)!.getElement();
expect(tooltipEl.textContent).toBe("Drag or select to move");
});

test("does not render drag handle tooltip text if not provided", () => {
render(<BoardItem i18nStrings={{ ...i18nStrings, dragHandleTooltipText: undefined }} />);
const wrapper = createWrapper();
const dragHandleEl = wrapper.findBoardItem()!.findDragHandle().getElement();

fireEvent(dragHandleEl, new MouseEvent("pointerover", { bubbles: true }));
expect(wrapper.findByClassName(TooltipWrapper.rootSelector)).toBeNull();
});

test("renders drag handle UAP actions on handle click", () => {
render(<BoardItem i18nStrings={i18nStrings} />);
const dragHandleEl = createWrapper().findBoardItem()!.findDragHandle()!.getElement();

fireEvent(dragHandleEl, new MouseEvent("pointerdown", { bubbles: true }));
fireEvent(dragHandleEl, new MouseEvent("pointerup", { bubbles: true }));

const dragHandleWrapper = new DragHandleWrapper(document.body);
expect(dragHandleWrapper.findAllVisibleDirectionButtons()).toHaveLength(4);
expect(dragHandleWrapper.findVisibleDirectionButtonBlockStart()).toBeDefined();
expect(dragHandleWrapper.findVisibleDirectionButtonBlockEnd()).toBeDefined();
expect(dragHandleWrapper.findVisibleDirectionButtonInlineStart()).toBeDefined();
expect(dragHandleWrapper.findVisibleDirectionButtonInlineEnd()).toBeDefined();
});

test("renders resize handle tooltip text", () => {
render(<BoardItem i18nStrings={i18nStrings} />);
const wrapper = createWrapper();
const resizeHandleEl = wrapper.findBoardItem()!.findResizeHandle()!.getElement();

fireEvent(resizeHandleEl, new MouseEvent("pointerover", { bubbles: true }));
const tooltipEl = wrapper.findByClassName(TooltipWrapper.rootSelector)!.getElement();
expect(tooltipEl.textContent).toBe("Drag or select to resize");
});

test("does not render resize handle tooltip text if not provided", () => {
render(<BoardItem i18nStrings={{ ...i18nStrings, resizeHandleTooltipText: undefined }} />);
const wrapper = createWrapper();
const resizeHandleEl = wrapper.findBoardItem()!.findResizeHandle()!.getElement();

fireEvent(resizeHandleEl, new MouseEvent("pointerover", { bubbles: true }));
expect(wrapper.findByClassName(TooltipWrapper.rootSelector)).toBeNull();
});

test("renders resize handle UAP actions on handle click", () => {
render(<BoardItem i18nStrings={i18nStrings} />);
const resizeHandleEl = createWrapper().findBoardItem()!.findResizeHandle()!.getElement();

fireEvent(resizeHandleEl, new MouseEvent("pointerdown", { bubbles: true }));
fireEvent(resizeHandleEl, new MouseEvent("pointerup", { bubbles: true }));

const dragHandleWrapper = new DragHandleWrapper(document.body);
expect(dragHandleWrapper.findAllVisibleDirectionButtons()).toHaveLength(4);
expect(dragHandleWrapper.findVisibleDirectionButtonBlockStart()).toBeDefined();
expect(dragHandleWrapper.findVisibleDirectionButtonBlockEnd()).toBeDefined();
expect(dragHandleWrapper.findVisibleDirectionButtonInlineStart()).toBeDefined();
expect(dragHandleWrapper.findVisibleDirectionButtonInlineEnd()).toBeDefined();
});
});
4 changes: 4 additions & 0 deletions src/board-item/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export interface BoardItemProps {
* * `dragHandleAriaDescription` (string, optional) - the ARIA description for the drag handle.
* * `resizeHandleAriaLabel` (string) - the ARIA label for the resize handle.
* * `resizeHandleAriaDescription` (string, optional) - the ARIA description for the resize handle.
* * `dragHandleTooltipText` (string, optional) - the ARIA description for the resize handle.
* * `resizeHandleTooltipText` (string, optional) - the Text for the resize handle Tooltip.
*/
i18nStrings: BoardItemProps.I18nStrings;
}
Expand All @@ -47,5 +49,7 @@ export namespace BoardItemProps {
dragHandleAriaDescription?: string;
resizeHandleAriaLabel: string;
resizeHandleAriaDescription?: string;
dragHandleTooltipText?: string;
resizeHandleTooltipText?: string;
}
}
31 changes: 28 additions & 3 deletions src/board-item/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import clsx from "clsx";

import Container from "@cloudscape-design/components/container";
import { InternalDragHandleProps } from "@cloudscape-design/components/internal/do-not-use/drag-handle";

import { getDataAttributes } from "../internal/base-component/get-data-attributes";
import { InternalBaseComponentProps } from "../internal/base-component/use-base-component";
import DragHandle from "../internal/drag-handle";
import { Direction } from "../internal/interfaces";
import { useItemContext } from "../internal/item-container";
import ResizeHandle from "../internal/resize-handle";
import ScreenreaderOnly from "../internal/screenreader-only";
Expand All @@ -16,6 +18,16 @@

import styles from "./styles.css.js";

const mapToKeyboardDirection = (direction: InternalDragHandleProps.Direction) => {
const directionMap: Record<InternalDragHandleProps.Direction, Direction> = {
"inline-start": "left",
"inline-end": "right",
"block-start": "up",
"block-end": "down",
};
return directionMap[direction];
};

export function InternalBoardItem({
children,
header,
Expand All @@ -26,14 +38,20 @@
__internalRootRef,
...rest
}: BoardItemProps & InternalBaseComponentProps) {
const { dragHandle, resizeHandle, isActive } = useItemContext();
const { dragHandle, resizeHandle, isActive, isHidden } = useItemContext();

const dragHandleAriaLabelledBy = useId();
const dragHandleAriaDescribedBy = useId();

const resizeHandleAriaLabelledBy = useId();
const resizeHandleAriaDescribedBy = useId();

// A board item is hidden while moving a board item from the palette to the board via keyboard or UAP.
// The wrapping container is set to invisible, so we don't need to render anything.
if (isHidden) {
return null;
}

return (
<div ref={__internalRootRef} className={styles.root} {...getDataAttributes(rest)}>
<Container
Expand All @@ -48,7 +66,10 @@
ariaDescribedBy={dragHandleAriaDescribedBy}
onPointerDown={dragHandle.onPointerDown}
onKeyDown={dragHandle.onKeyDown}
isActive={dragHandle.isActive}
activeState={dragHandle.activeState}
initialShowButtons={dragHandle.initialShowButtons}
onDirectionClick={(direction) => dragHandle.onDirectionClick(mapToKeyboardDirection(direction), "drag")}
dragHandleTooltipText={i18nStrings.dragHandleTooltipText}
/>
}
settings={settings}
Expand All @@ -58,7 +79,7 @@
}
footer={footer}
disableContentPaddings={disableContentPaddings}
className={clsx(styles["container-override"], isActive && styles.active)}

Check warning on line 82 in src/board-item/internal.tsx

View workflow job for this annotation

GitHub Actions / build / build

Prop "className" is forbidden on Components

Check warning on line 82 in src/board-item/internal.tsx

View workflow job for this annotation

GitHub Actions / dry-run / Build board components

Prop "className" is forbidden on Components
>
{children}
</Container>
Expand All @@ -69,13 +90,17 @@
ariaDescribedBy={resizeHandleAriaDescribedBy}
onPointerDown={resizeHandle.onPointerDown}
onKeyDown={resizeHandle.onKeyDown}
isActive={resizeHandle.isActive}
activeState={resizeHandle.activeState}
onDirectionClick={(direction) => {
resizeHandle.onDirectionClick(mapToKeyboardDirection(direction), "resize");
}}
resizeHandleTooltipText={i18nStrings.resizeHandleTooltipText}
/>
</div>
)}

<ScreenreaderOnly id={dragHandleAriaLabelledBy}>{i18nStrings.dragHandleAriaLabel}</ScreenreaderOnly>

Check warning on line 102 in src/board-item/internal.tsx

View workflow job for this annotation

GitHub Actions / build / build

Prop "id" is forbidden on Components

Check warning on line 102 in src/board-item/internal.tsx

View workflow job for this annotation

GitHub Actions / dry-run / Build board components

Prop "id" is forbidden on Components
<ScreenreaderOnly id={dragHandleAriaDescribedBy}>{i18nStrings.dragHandleAriaDescription}</ScreenreaderOnly>

Check warning on line 103 in src/board-item/internal.tsx

View workflow job for this annotation

GitHub Actions / build / build

Prop "id" is forbidden on Components

Check warning on line 103 in src/board-item/internal.tsx

View workflow job for this annotation

GitHub Actions / dry-run / Build board components

Prop "id" is forbidden on Components

<ScreenreaderOnly id={resizeHandleAriaLabelledBy}>{i18nStrings.resizeHandleAriaLabel}</ScreenreaderOnly>
<ScreenreaderOnly id={resizeHandleAriaDescribedBy}>{i18nStrings.resizeHandleAriaDescription}</ScreenreaderOnly>
Expand Down
4 changes: 1 addition & 3 deletions src/board-item/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
.container-override.active {
box-shadow: cs.$shadow-container-active;

:global([data-awsui-focus-visible]) & {
@include shared.focus-highlight(0px, cs.$border-radius-container);
}
@include shared.focus-highlight(0px, cs.$border-radius-container);
}

.header {
Expand Down
Loading
Loading