Skip to content

Commit a9dee87

Browse files
johannes-weberJohannes Weber
andauthored
feat: Add interaction state hook with DnD and keyboard support (#3499)
Co-authored-by: Johannes Weber <[email protected]>
1 parent e18b519 commit a9dee87

File tree

7 files changed

+1535
-0
lines changed

7 files changed

+1535
-0
lines changed

build-tools/tasks/package-json.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ function getComponentsExports() {
2828
'./internal/do-not-use/expandable-section': './internal/do-not-use/expandable-section.js',
2929
'./internal/do-not-use/i18n': './internal/do-not-use/i18n.js',
3030
'./internal/do-not-use/tooltip': './internal/do-not-use/tooltip.js',
31+
'./internal/do-not-use/drag-handle': './internal/do-not-use/drag-handle.js',
3132
'./internal/widget-exports': './internal/widget-exports.js',
3233
'./test-utils/dom/internal/tooltip': './test-utils/dom/internal/tooltip.js',
3334
'./test-utils/selectors/internal/tooltip': './test-utils/selectors/internal/tooltip.js',
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import React, { useContext, useRef, useState } from 'react';
4+
import { createPortal } from 'react-dom';
5+
import clsx from 'clsx';
6+
7+
import Box from '~components/box';
8+
import Checkbox from '~components/checkbox';
9+
import Container from '~components/container';
10+
import Header from '~components/header';
11+
import useDragHandleInteractionState, {
12+
UseDragHandleInteractionStateProps,
13+
} from '~components/internal/components/drag-handle/hooks/use-drag-handle-interaction-state';
14+
import SpaceBetween from '~components/space-between';
15+
16+
import AppContext, { AppContextType } from '../app/app-context';
17+
18+
import styles from './styles.scss';
19+
20+
type PageContext = React.Context<
21+
AppContextType<{
22+
renderInPortal: boolean;
23+
}>
24+
>;
25+
26+
const TestBoardItemButton: React.FC = () => {
27+
const { urlParams, setUrlParams } = useContext(AppContext as PageContext);
28+
const [renderInPortal, setRenderInPortal] = useState(false);
29+
30+
interface TestMetadata {
31+
operation: 'drag' | 'resize';
32+
}
33+
34+
const hookProps: UseDragHandleInteractionStateProps<TestMetadata> = {
35+
onDndStartAction: (event, metadata) => {
36+
console.log('onDndStartAction triggered', event.clientX, event.clientY, metadata);
37+
},
38+
onDndActiveAction: event => {
39+
console.log('onDndActiveAction triggered', event.clientX, event.clientY);
40+
},
41+
onDndEndAction: () => {
42+
console.log('onDndEndAction triggered');
43+
},
44+
onUapActionStartAction: metadata => {
45+
console.log('onKeyboardStartAction triggered', metadata);
46+
},
47+
onUapActionEndAction: () => {
48+
console.log('onKeyboardEndAction triggered');
49+
},
50+
};
51+
52+
const hook = useDragHandleInteractionState<TestMetadata>(hookProps);
53+
const buttonRef = useRef<HTMLButtonElement>(null);
54+
55+
const handleGlobalPointerMove = (event: PointerEvent) => {
56+
hook.processPointerMove(event);
57+
};
58+
59+
const handleGlobalPointerUp = (event: PointerEvent) => {
60+
hook.processPointerUp(event);
61+
// Clean up global listeners after interaction ends
62+
window.removeEventListener('pointermove', handleGlobalPointerMove);
63+
window.removeEventListener('pointerup', handleGlobalPointerUp);
64+
};
65+
66+
const handlePointerCancel = () => {
67+
hook.processPointerCancel();
68+
// Clean up global listeners after interaction ends
69+
window.removeEventListener('pointermove', handleGlobalPointerMove);
70+
window.removeEventListener('pointerup', handleGlobalPointerUp);
71+
};
72+
73+
const handleOnPointerDown = (event: PointerEvent) => {
74+
hook.processPointerDown(event, { operation: 'drag' });
75+
if (urlParams.renderInPortal) {
76+
setRenderInPortal(true);
77+
}
78+
79+
window.addEventListener('pointermove', handleGlobalPointerMove);
80+
window.addEventListener('pointerup', handleGlobalPointerUp);
81+
};
82+
83+
// Using a native button for testing because the system's button does not expose all native properties
84+
const buttonComp = (
85+
<button
86+
ref={buttonRef}
87+
onPointerDown={e => handleOnPointerDown(e.nativeEvent)}
88+
onPointerCancel={handlePointerCancel}
89+
onKeyDown={e => hook.processKeyDown(e, { operation: 'resize' })}
90+
onFocus={hook.processFocus}
91+
onBlur={hook.processBlur}
92+
className={clsx(styles['test-button'])}
93+
tabIndex={0}
94+
>
95+
Test button to interact with
96+
</button>
97+
);
98+
99+
return (
100+
<>
101+
<Header
102+
variant="h1"
103+
description="Page to test the board-item behaviour which is rendered in a portal on onPointerDown when adding from the pallete to the board"
104+
>
105+
Drag handle hook
106+
</Header>
107+
<SpaceBetween size="m">
108+
<Checkbox
109+
checked={urlParams.renderInPortal}
110+
onChange={event => {
111+
setUrlParams({ renderInPortal: event.detail.checked });
112+
}}
113+
>
114+
Render Test Button in a portal on click
115+
</Checkbox>
116+
117+
<Container>
118+
<Box variant="pre">
119+
<p>Current State: {String(hook.interaction.value) || 'null'}</p>
120+
<p>Metadata: {JSON.stringify(hook.interaction.metadata)}</p>
121+
<p>
122+
DnD Event Coords:{' '}
123+
{hook.interaction.eventData ? (
124+
<>
125+
({hook.interaction.eventData?.clientX}, {hook.interaction.eventData?.clientY})
126+
</>
127+
) : (
128+
'-'
129+
)}
130+
</p>
131+
</Box>
132+
</Container>
133+
{renderInPortal ? <div>{createPortal(buttonComp, document.body)}</div> : buttonComp}
134+
</SpaceBetween>
135+
</>
136+
);
137+
};
138+
139+
export default TestBoardItemButton;

pages/drag-handle/styles.scss

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
@use '~design-tokens' as awsui;
7+
8+
.test-button {
9+
background: awsui.$color-background-button-normal-default;
10+
padding-block: awsui.$space-scaled-xxl;
11+
padding-inline: awsui.$space-scaled-xxl;
12+
13+
&:hover {
14+
background: awsui.$color-background-button-normal-hover;
15+
}
16+
17+
&:focus {
18+
background: awsui.$color-background-button-normal-active;
19+
}
20+
}

0 commit comments

Comments
 (0)