@automattic/jetpack-components 0.70.1 → 0.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -1
- package/build/components/dot-pager/index.d.ts +14 -0
- package/build/components/dot-pager/index.js +52 -0
- package/build/components/number-control/index.js +1 -1
- package/build/components/split-button/index.js +2 -2
- package/build/components/swipeable/index.d.ts +12 -0
- package/build/components/swipeable/index.js +293 -0
- package/build/index.d.ts +2 -3
- package/build/index.js +2 -3
- package/components/dot-pager/README.md +20 -0
- package/components/dot-pager/index.tsx +147 -0
- package/components/dot-pager/style.scss +80 -0
- package/components/number-control/index.jsx +3 -1
- package/components/split-button/index.tsx +14 -12
- package/components/split-button/style.module.scss +5 -0
- package/components/swipeable/README.md +34 -0
- package/components/swipeable/index.tsx +425 -0
- package/components/swipeable/style.scss +34 -0
- package/index.ts +2 -3
- package/package.json +1 -3
- package/build/components/threat-fixer-button/index.d.ts +0 -17
- package/build/components/threat-fixer-button/index.js +0 -56
- package/build/components/threat-severity-badge/index.d.ts +0 -4
- package/build/components/threat-severity-badge/index.js +0 -13
- package/build/components/threats-data-views/constants.d.ts +0 -33
- package/build/components/threats-data-views/constants.js +0 -37
- package/build/components/threats-data-views/index.d.ts +0 -30
- package/build/components/threats-data-views/index.js +0 -413
- package/build/components/threats-data-views/threats-status-toggle-group-control.d.ts +0 -16
- package/build/components/threats-data-views/threats-status-toggle-group-control.js +0 -95
- package/components/threat-fixer-button/index.tsx +0 -101
- package/components/threat-fixer-button/styles.module.scss +0 -16
- package/components/threat-modal/fixer-state-notice.tsx +0 -63
- package/components/threat-modal/index.tsx +0 -109
- package/components/threat-modal/styles.module.scss +0 -81
- package/components/threat-modal/threat-actions.tsx +0 -93
- package/components/threat-modal/threat-fix-confirmation.tsx +0 -54
- package/components/threat-modal/threat-fix-details.tsx +0 -65
- package/components/threat-modal/threat-notice.tsx +0 -84
- package/components/threat-modal/threat-summary.tsx +0 -30
- package/components/threat-modal/threat-technical-details.tsx +0 -67
- package/components/threat-severity-badge/index.tsx +0 -26
- package/components/threat-severity-badge/styles.module.scss +0 -27
- package/components/threats-data-views/constants.ts +0 -49
- package/components/threats-data-views/index.tsx +0 -534
- package/components/threats-data-views/styles.module.scss +0 -40
- package/components/threats-data-views/threats-status-toggle-group-control.tsx +0 -158
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
@import '@automattic/jetpack-base-styles/gutenberg-base-styles';
|
|
2
|
+
@import '@automattic/jetpack-base-styles/root-variables';
|
|
3
|
+
|
|
4
|
+
.dot-pager {
|
|
5
|
+
width: 100%;
|
|
6
|
+
height: 100%;
|
|
7
|
+
position: relative;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.dot-pager__page {
|
|
11
|
+
height: 100%;
|
|
12
|
+
transition: opacity 0.5s ease-in-out, visibility 0.5s ease-in-out;
|
|
13
|
+
@include reduce-motion( "transition" );
|
|
14
|
+
opacity: 1;
|
|
15
|
+
visibility: visible;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.dot-pager__controls {
|
|
19
|
+
margin: 0;
|
|
20
|
+
margin-bottom: 16px;
|
|
21
|
+
margin-top: auto;
|
|
22
|
+
display: flex;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
position: absolute;
|
|
25
|
+
bottom: 0px;
|
|
26
|
+
left: 0;
|
|
27
|
+
right: 0;
|
|
28
|
+
z-index: 1;
|
|
29
|
+
li {
|
|
30
|
+
display: inline-flex;
|
|
31
|
+
margin: 0 4px;
|
|
32
|
+
height: 18px;
|
|
33
|
+
align-items: center;
|
|
34
|
+
|
|
35
|
+
&:first-child {
|
|
36
|
+
margin-left: 0;
|
|
37
|
+
}
|
|
38
|
+
&:last-child {
|
|
39
|
+
margin-right: 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.dot-pager__control-gap {
|
|
44
|
+
margin-left: auto;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.dot-pager__control-choose-page {
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
|
|
51
|
+
&:disabled {
|
|
52
|
+
cursor: default;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&:focus-visible {
|
|
56
|
+
box-shadow: 0 0 0 2px var(--jp-gray-90);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.dot-pager__control-choose-page {
|
|
61
|
+
border-radius: 50%;
|
|
62
|
+
width: 6px;
|
|
63
|
+
height: 6px;
|
|
64
|
+
padding: 0px;
|
|
65
|
+
background-color: var(--jp-white-off);
|
|
66
|
+
|
|
67
|
+
&:hover {
|
|
68
|
+
background-color: var(--jp-gray-20);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
&.dot-pager__control-current {
|
|
72
|
+
background-color: var(--jp-gray-80);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.dot-pager {
|
|
77
|
+
&__button {
|
|
78
|
+
margin-right: 10px;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -14,7 +14,9 @@ import {
|
|
|
14
14
|
const NumberControl =
|
|
15
15
|
ExperimentalNumberControl ||
|
|
16
16
|
function CustomNumberControl( props ) {
|
|
17
|
-
return
|
|
17
|
+
return (
|
|
18
|
+
<TextControl type="number" inputMode="numeric" { ...props } __next40pxDefaultSize={ true } />
|
|
19
|
+
);
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
export default NumberControl;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Button, Flex, DropdownMenu } from '@wordpress/components';
|
|
2
2
|
import styles from './style.module.scss';
|
|
3
3
|
import { SplitButtonProps } from './types.js';
|
|
4
4
|
import type React from 'react';
|
|
@@ -22,17 +22,19 @@ const SplitButton: React.FC< SplitButtonProps > = ( {
|
|
|
22
22
|
...buttonProps
|
|
23
23
|
} ) => {
|
|
24
24
|
return (
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
<Flex className={ styles[ 'split-button' ] }>
|
|
26
|
+
<div role="group" className="components-button-group">
|
|
27
|
+
<Button variant={ variant } { ...buttonProps } className={ styles.button } />
|
|
28
|
+
<DropdownMenu
|
|
29
|
+
toggleProps={ { variant, className: styles.button, ...toggleProps } }
|
|
30
|
+
popoverProps={ { noArrow: false, ...popoverProps } }
|
|
31
|
+
icon={ <DownIcon /> }
|
|
32
|
+
disableOpenOnArrowDown={ true }
|
|
33
|
+
controls={ controls }
|
|
34
|
+
label={ label }
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
</Flex>
|
|
36
38
|
);
|
|
37
39
|
};
|
|
38
40
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Swipeable
|
|
2
|
+
|
|
3
|
+
This component is used to add horizontal swipe controls to a list of elements (pages).
|
|
4
|
+
|
|
5
|
+
## Example Usage
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { Swipeable } from '@automattic/jetpack-components';
|
|
9
|
+
|
|
10
|
+
function Pager() {
|
|
11
|
+
const [ currentPage, setCurrentPage ] = useState( 0 );
|
|
12
|
+
return (
|
|
13
|
+
<Swipeable
|
|
14
|
+
onPageSelect={ ( index ) => {
|
|
15
|
+
setCurrentPage( index );
|
|
16
|
+
} }
|
|
17
|
+
currentPage={ currentPage }
|
|
18
|
+
pageClassName="example-page-component-class"
|
|
19
|
+
>
|
|
20
|
+
<div>Page 1</div>
|
|
21
|
+
<div>Page 2</div>
|
|
22
|
+
<div>Page 3</div>
|
|
23
|
+
</Swipeable>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Props
|
|
29
|
+
|
|
30
|
+
- `onPageSelect` - (function) Function that runs when the page is selected on using a swipe. Useful for updating the current page.
|
|
31
|
+
- `currentPage` - (number) Index of the currently selected/shown page.
|
|
32
|
+
- `hasDynamicHeight` - (bool) Whether height should be changed dynamically.
|
|
33
|
+
- `pageClassName` - _optional_ (string) Optional class attribute of the page component.
|
|
34
|
+
- `containerClassName` - _optional_ (string) Optional class attribute of the page container component.
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import React, { Children, useState, useLayoutEffect, useRef, useCallback, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
import './style.scss';
|
|
5
|
+
|
|
6
|
+
interface PageStyle {
|
|
7
|
+
transitionDuration: string;
|
|
8
|
+
height?: number;
|
|
9
|
+
transform?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const OFFSET_THRESHOLD_PERCENTAGE = 0.35; // Percentage of width to travel before we trigger the slider to move to the desired slide.
|
|
13
|
+
const VELOCITY_THRESHOLD = 0.2; // Speed of drag above, before we trigger the slider to move to the desired slide.
|
|
14
|
+
const VERTICAL_THRESHOLD_ANGLE = 55;
|
|
15
|
+
const TRANSITION_DURATION = '300ms';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Custom hook to observe and handle resize events on a DOM element.
|
|
19
|
+
*
|
|
20
|
+
* @return {[React.Dispatch<React.SetStateAction<HTMLElement | null>>, ResizeObserverEntry | null]} Tuple containing setter and entry
|
|
21
|
+
*/
|
|
22
|
+
function useResizeObserver(): [
|
|
23
|
+
React.Dispatch< React.SetStateAction< HTMLElement | null > >,
|
|
24
|
+
ResizeObserverEntry | null,
|
|
25
|
+
] {
|
|
26
|
+
const [ observerEntry, setObserverEntry ] = useState< ResizeObserverEntry | null >( null );
|
|
27
|
+
const [ node, setNode ] = useState< HTMLElement | null >( null );
|
|
28
|
+
const observer = useRef< ResizeObserver | null >( null );
|
|
29
|
+
|
|
30
|
+
const disconnect = useCallback( () => observer.current?.disconnect(), [] );
|
|
31
|
+
|
|
32
|
+
const observe = useCallback( () => {
|
|
33
|
+
observer.current = new ResizeObserver( ( [ entry ] ) => setObserverEntry( entry ) );
|
|
34
|
+
if ( node ) {
|
|
35
|
+
observer.current.observe( node );
|
|
36
|
+
}
|
|
37
|
+
}, [ node ] );
|
|
38
|
+
|
|
39
|
+
useLayoutEffect( () => {
|
|
40
|
+
observe();
|
|
41
|
+
return () => disconnect();
|
|
42
|
+
}, [ disconnect, observe ] );
|
|
43
|
+
|
|
44
|
+
return [ setNode, observerEntry ];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Gets the drag position and timestamp from a mouse, touch, or pointer event.
|
|
49
|
+
*
|
|
50
|
+
* @param {Event} event - The event object from the drag/touch/pointer interaction
|
|
51
|
+
* @return {object} Object containing x, y coordinates and timestamp of the event
|
|
52
|
+
*/
|
|
53
|
+
function getDragPositionAndTime( event ) {
|
|
54
|
+
const { timeStamp } = event;
|
|
55
|
+
if ( Object.prototype.hasOwnProperty.call( event, 'clientX' ) ) {
|
|
56
|
+
return { x: event.clientX, y: event.clientY, timeStamp };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if ( event.targetTouches[ 0 ] ) {
|
|
60
|
+
return {
|
|
61
|
+
x: event.targetTouches[ 0 ].clientX,
|
|
62
|
+
y: event.targetTouches[ 0 ].clientY,
|
|
63
|
+
timeStamp,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const touch = event.changedTouches[ 0 ];
|
|
68
|
+
return { x: touch.clientX, y: touch.clientY, timeStamp };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Calculates the total width needed for all pages in the swipeable component.
|
|
73
|
+
*
|
|
74
|
+
* @param {number} pageWidth - The width of a single page
|
|
75
|
+
* @param {number} numPages - The total number of pages
|
|
76
|
+
* @return {number|null} The total width of all pages or null if pageWidth is not available
|
|
77
|
+
*/
|
|
78
|
+
function getPagesWidth( pageWidth, numPages ) {
|
|
79
|
+
if ( ! pageWidth ) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return pageWidth * numPages;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const Swipeable = ( {
|
|
86
|
+
hasDynamicHeight = false,
|
|
87
|
+
children,
|
|
88
|
+
currentPage = 0,
|
|
89
|
+
onPageSelect,
|
|
90
|
+
pageClassName,
|
|
91
|
+
containerClassName,
|
|
92
|
+
isClickEnabled,
|
|
93
|
+
...otherProps
|
|
94
|
+
} ) => {
|
|
95
|
+
const prevPageRef = useRef( currentPage );
|
|
96
|
+
const [ swipeableArea, setSwipeableArea ] = useState< DOMRect | null >( null );
|
|
97
|
+
// TODO: Needs to be added RTL support
|
|
98
|
+
const isRtl = false;
|
|
99
|
+
|
|
100
|
+
const [ resizeObserverRef, entry ] = useResizeObserver();
|
|
101
|
+
const [ isTransitioning, setIsTransitioning ] = useState( false );
|
|
102
|
+
|
|
103
|
+
const [ pagesStyle, setPagesStyle ] = useState< PageStyle >( {
|
|
104
|
+
transitionDuration: TRANSITION_DURATION,
|
|
105
|
+
} );
|
|
106
|
+
|
|
107
|
+
const [ dragData, setDragData ] = useState( null );
|
|
108
|
+
|
|
109
|
+
const pagesRef = useRef< HTMLDivElement >( null );
|
|
110
|
+
const numPages = Children.count( children );
|
|
111
|
+
const containerWidth = entry?.contentRect?.width;
|
|
112
|
+
|
|
113
|
+
useEffect( () => {
|
|
114
|
+
let timeoutId: ReturnType< typeof setTimeout >;
|
|
115
|
+
if (
|
|
116
|
+
( currentPage === 0 && prevPageRef.current === numPages ) ||
|
|
117
|
+
( currentPage === numPages - 1 && prevPageRef.current === -1 )
|
|
118
|
+
) {
|
|
119
|
+
// We are in a real slide after being transitioned from a clone
|
|
120
|
+
// we need to set again the transitionDuration to TRANSITION_DURATION
|
|
121
|
+
// But we need to wait a little bit to avoid enabling it before
|
|
122
|
+
// we moved to the real slide
|
|
123
|
+
timeoutId = setTimeout( () => {
|
|
124
|
+
setPagesStyle( prev => ( { ...prev, transitionDuration: TRANSITION_DURATION } ) );
|
|
125
|
+
}, 500 );
|
|
126
|
+
} else if ( currentPage === numPages || currentPage < 0 ) {
|
|
127
|
+
// In a clone slide. Start the transition to the real slide
|
|
128
|
+
setIsTransitioning( true );
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
prevPageRef.current = currentPage;
|
|
132
|
+
return () => {
|
|
133
|
+
if ( timeoutId ) {
|
|
134
|
+
clearTimeout( timeoutId );
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}, [ currentPage, numPages ] );
|
|
138
|
+
|
|
139
|
+
const getOffset = useCallback(
|
|
140
|
+
index => {
|
|
141
|
+
// Adjust offset to account for the cloned element at the beginning
|
|
142
|
+
const adjustedIndex = index + 1;
|
|
143
|
+
const offset = containerWidth * adjustedIndex;
|
|
144
|
+
return isRtl ? offset : -offset;
|
|
145
|
+
},
|
|
146
|
+
[ isRtl, containerWidth ]
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const updateEnabled = hasDynamicHeight && numPages > 1;
|
|
150
|
+
|
|
151
|
+
// Generate a property that denotes the order of the cards, in order to recalculate height whenever the card order changes.
|
|
152
|
+
const childrenOrder = children.reduce( ( acc, child ) => acc + child.key, '' );
|
|
153
|
+
|
|
154
|
+
useLayoutEffect( () => {
|
|
155
|
+
if ( ! updateEnabled ) {
|
|
156
|
+
// This is a fix for a bug when you have >1 pages and it update the component to just one but the height is still
|
|
157
|
+
// Related to https://github.com/Automattic/dotcom-forge/issues/2033
|
|
158
|
+
if ( pagesStyle?.height ) {
|
|
159
|
+
setPagesStyle( { ...pagesStyle, height: undefined } );
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const targetHeight = ( pagesRef.current?.querySelector( '.is-current' ) as HTMLElement )
|
|
164
|
+
?.offsetHeight;
|
|
165
|
+
|
|
166
|
+
if ( targetHeight && pagesStyle?.height !== targetHeight ) {
|
|
167
|
+
setPagesStyle( { ...pagesStyle, height: targetHeight } );
|
|
168
|
+
}
|
|
169
|
+
}, [ pagesRef, currentPage, pagesStyle, updateEnabled, containerWidth, childrenOrder ] );
|
|
170
|
+
|
|
171
|
+
const resetDragData = useCallback( () => {
|
|
172
|
+
setPagesStyle( prev => ( {
|
|
173
|
+
...prev,
|
|
174
|
+
transitionDuration: TRANSITION_DURATION,
|
|
175
|
+
} ) );
|
|
176
|
+
setDragData( null );
|
|
177
|
+
}, [ setPagesStyle, setDragData ] );
|
|
178
|
+
|
|
179
|
+
const handleDragStart = useCallback(
|
|
180
|
+
event => {
|
|
181
|
+
const position = getDragPositionAndTime( event );
|
|
182
|
+
setSwipeableArea( pagesRef.current?.getBoundingClientRect() );
|
|
183
|
+
setDragData( { start: position } );
|
|
184
|
+
setPagesStyle( prev => ( { ...prev, transitionDuration: `0ms` } ) ); // Set transition Duration to 0 for smooth dragging.
|
|
185
|
+
},
|
|
186
|
+
[ setPagesStyle, setDragData ]
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const hasSwipedToNextPage = useCallback( delta => ( isRtl ? delta > 0 : delta < 0 ), [ isRtl ] );
|
|
190
|
+
const hasSwipedToPreviousPage = useCallback(
|
|
191
|
+
delta => ( isRtl ? delta < 0 : delta > 0 ),
|
|
192
|
+
[ isRtl ]
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const handleTransitionEnd = useCallback( () => {
|
|
196
|
+
if ( ! isTransitioning ) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
setIsTransitioning( false );
|
|
201
|
+
|
|
202
|
+
// If we're on the clone slides, jump to the corresponding real slide
|
|
203
|
+
// We set the transitionDuration to 0ms to make invisible the
|
|
204
|
+
// change from the clone to the real slide
|
|
205
|
+
if ( currentPage >= numPages ) {
|
|
206
|
+
setPagesStyle( prev => ( { ...prev, transitionDuration: '0ms' } ) );
|
|
207
|
+
onPageSelect( 0 );
|
|
208
|
+
} else if ( currentPage < 0 ) {
|
|
209
|
+
setPagesStyle( prev => ( { ...prev, transitionDuration: '0ms' } ) );
|
|
210
|
+
onPageSelect( numPages - 1 );
|
|
211
|
+
}
|
|
212
|
+
}, [ currentPage, numPages, onPageSelect, isTransitioning ] );
|
|
213
|
+
|
|
214
|
+
const handleDragEnd = useCallback(
|
|
215
|
+
event => {
|
|
216
|
+
if ( ! dragData ) {
|
|
217
|
+
return; // End early if we are not dragging any more.
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let dragPosition = getDragPositionAndTime( event );
|
|
221
|
+
|
|
222
|
+
if ( dragPosition.x === 0 ) {
|
|
223
|
+
dragPosition = dragData.last;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const delta = dragPosition.x - dragData.start.x;
|
|
227
|
+
const absoluteDelta = Math.abs( delta );
|
|
228
|
+
const velocity = absoluteDelta / ( dragPosition.timeStamp - dragData.start.timeStamp );
|
|
229
|
+
|
|
230
|
+
const verticalAbsoluteDelta = Math.abs( dragPosition.y - dragData.start.y );
|
|
231
|
+
const angle = ( Math.atan2( verticalAbsoluteDelta, absoluteDelta ) * 180 ) / Math.PI;
|
|
232
|
+
|
|
233
|
+
// Is click or tap?
|
|
234
|
+
if ( velocity === 0 && isClickEnabled ) {
|
|
235
|
+
onPageSelect( ( currentPage + 1 ) % numPages );
|
|
236
|
+
resetDragData();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Is vertical scroll detected?
|
|
241
|
+
if ( angle > VERTICAL_THRESHOLD_ANGLE ) {
|
|
242
|
+
resetDragData();
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const hasMetThreshold =
|
|
247
|
+
absoluteDelta > OFFSET_THRESHOLD_PERCENTAGE * containerWidth ||
|
|
248
|
+
velocity > VELOCITY_THRESHOLD;
|
|
249
|
+
|
|
250
|
+
let newIndex = currentPage;
|
|
251
|
+
|
|
252
|
+
if ( hasMetThreshold ) {
|
|
253
|
+
if ( hasSwipedToNextPage( delta ) ) {
|
|
254
|
+
newIndex = currentPage + 1;
|
|
255
|
+
if ( newIndex >= numPages ) {
|
|
256
|
+
setIsTransitioning( true );
|
|
257
|
+
}
|
|
258
|
+
} else if ( hasSwipedToPreviousPage( delta ) ) {
|
|
259
|
+
newIndex = currentPage - 1;
|
|
260
|
+
if ( newIndex < 0 ) {
|
|
261
|
+
setIsTransitioning( true );
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
setPagesStyle( prev => ( {
|
|
267
|
+
...prev,
|
|
268
|
+
transform: `translate3d(${ getOffset( newIndex ) }px, 0px, 0px)`,
|
|
269
|
+
transitionDuration: TRANSITION_DURATION,
|
|
270
|
+
} ) );
|
|
271
|
+
|
|
272
|
+
onPageSelect( newIndex );
|
|
273
|
+
setDragData( null );
|
|
274
|
+
},
|
|
275
|
+
[
|
|
276
|
+
currentPage,
|
|
277
|
+
dragData,
|
|
278
|
+
hasSwipedToNextPage,
|
|
279
|
+
hasSwipedToPreviousPage,
|
|
280
|
+
numPages,
|
|
281
|
+
onPageSelect,
|
|
282
|
+
setPagesStyle,
|
|
283
|
+
containerWidth,
|
|
284
|
+
isClickEnabled,
|
|
285
|
+
resetDragData,
|
|
286
|
+
getOffset,
|
|
287
|
+
]
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const handleDrag = useCallback(
|
|
291
|
+
event => {
|
|
292
|
+
if ( ! dragData ) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const dragPosition = getDragPositionAndTime( event );
|
|
297
|
+
const delta = dragPosition.x - dragData.start.x;
|
|
298
|
+
const absoluteDelta = Math.abs( delta );
|
|
299
|
+
const offset = getOffset( currentPage ) + delta;
|
|
300
|
+
setDragData( { ...dragData, last: dragPosition } );
|
|
301
|
+
// The user needs to swipe horizontally more then 2 px in order for the canvase to be dragging.
|
|
302
|
+
// We do this so that the user can scroll vertically smother.
|
|
303
|
+
if ( absoluteDelta < 3 ) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
setPagesStyle( prev => ( {
|
|
308
|
+
...prev,
|
|
309
|
+
transform: `translate3d(${ offset }px, 0px, 0px)`,
|
|
310
|
+
transitionDuration: '0ms',
|
|
311
|
+
} ) );
|
|
312
|
+
|
|
313
|
+
if ( ! swipeableArea ) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
// Did the user swipe out of the swipeable area?
|
|
317
|
+
if (
|
|
318
|
+
dragPosition.x < swipeableArea.left ||
|
|
319
|
+
dragPosition.x > swipeableArea.right ||
|
|
320
|
+
dragPosition.y > swipeableArea.bottom ||
|
|
321
|
+
dragPosition.y < swipeableArea.top
|
|
322
|
+
) {
|
|
323
|
+
handleDragEnd( event );
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
[ dragData, getOffset, currentPage, swipeableArea, handleDragEnd ]
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
const getTouchEvents = useCallback( () => {
|
|
330
|
+
if ( 'onpointerup' in document ) {
|
|
331
|
+
return {
|
|
332
|
+
onPointerDown: handleDragStart,
|
|
333
|
+
onPointerMove: handleDrag,
|
|
334
|
+
onPointerUp: handleDragEnd,
|
|
335
|
+
onPointerLeave: handleDragEnd,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if ( 'ondragend' in document ) {
|
|
340
|
+
return {
|
|
341
|
+
onDragStart: handleDragStart,
|
|
342
|
+
onDrag: handleDrag,
|
|
343
|
+
onDragEnd: handleDragEnd,
|
|
344
|
+
onDragExit: handleDragEnd,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if ( 'ontouchend' in document ) {
|
|
349
|
+
return {
|
|
350
|
+
onTouchStart: handleDragStart,
|
|
351
|
+
onTouchMove: handleDrag,
|
|
352
|
+
onTouchEnd: handleDragEnd,
|
|
353
|
+
onTouchCancel: handleDragEnd,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return null;
|
|
358
|
+
}, [ handleDragStart, handleDrag, handleDragEnd ] );
|
|
359
|
+
|
|
360
|
+
const offset = getOffset( currentPage );
|
|
361
|
+
|
|
362
|
+
return (
|
|
363
|
+
<>
|
|
364
|
+
<div
|
|
365
|
+
{ ...getTouchEvents() }
|
|
366
|
+
className="swipeable__container"
|
|
367
|
+
ref={ pagesRef }
|
|
368
|
+
{ ...otherProps }
|
|
369
|
+
>
|
|
370
|
+
<div
|
|
371
|
+
className={ clsx( 'swipeable__pages', containerClassName ) }
|
|
372
|
+
style={ {
|
|
373
|
+
...pagesStyle,
|
|
374
|
+
width: getPagesWidth( containerWidth, numPages + 2 ),
|
|
375
|
+
transform: `translate3d(${ offset }px, 0px, 0px)`,
|
|
376
|
+
} }
|
|
377
|
+
onTransitionEnd={ handleTransitionEnd }
|
|
378
|
+
>
|
|
379
|
+
{ /* Clone of the last element */ }
|
|
380
|
+
<div
|
|
381
|
+
style={ { width: `${ containerWidth }px` } }
|
|
382
|
+
className={ clsx( 'swipeable__page', pageClassName, {
|
|
383
|
+
'is-clone': true,
|
|
384
|
+
'is-prev': currentPage === 0,
|
|
385
|
+
} ) }
|
|
386
|
+
key={ `clone-prev-${ numPages - 1 }` }
|
|
387
|
+
>
|
|
388
|
+
{ Children.toArray( children )[ numPages - 1 ] }
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
{ /* Original elements */ }
|
|
392
|
+
{ Children.map( children, ( child, index ) => (
|
|
393
|
+
<div
|
|
394
|
+
style={ { width: `${ containerWidth }px` } }
|
|
395
|
+
className={ clsx( 'swipeable__page', pageClassName, {
|
|
396
|
+
'is-current': index === currentPage,
|
|
397
|
+
'is-prev': index < currentPage,
|
|
398
|
+
'is-next': index > currentPage,
|
|
399
|
+
} ) }
|
|
400
|
+
key={ `page-${ index }` }
|
|
401
|
+
data-testid={ `swipeable-page-${ index + 1 }` }
|
|
402
|
+
>
|
|
403
|
+
{ child }
|
|
404
|
+
</div>
|
|
405
|
+
) ) }
|
|
406
|
+
|
|
407
|
+
{ /* Clone of the first element */ }
|
|
408
|
+
<div
|
|
409
|
+
style={ { width: `${ containerWidth }px` } }
|
|
410
|
+
className={ clsx( 'swipeable__page', pageClassName, {
|
|
411
|
+
'is-clone': true,
|
|
412
|
+
'is-next': currentPage === numPages - 1,
|
|
413
|
+
} ) }
|
|
414
|
+
key={ `clone-next-0` }
|
|
415
|
+
>
|
|
416
|
+
{ Children.toArray( children )[ 0 ] }
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
</div>
|
|
420
|
+
<div ref={ resizeObserverRef } className="swipeable__resize-observer"></div>
|
|
421
|
+
</>
|
|
422
|
+
);
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
export default Swipeable;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
@import '@automattic/jetpack-base-styles/gutenberg-base-styles';
|
|
2
|
+
|
|
3
|
+
.swipeable__container {
|
|
4
|
+
width: 100%;
|
|
5
|
+
height: 100%;
|
|
6
|
+
position: relative;
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
list-style: none;
|
|
9
|
+
padding: 0;
|
|
10
|
+
touch-action: pan-y;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.swipeable__pages {
|
|
14
|
+
position: relative;
|
|
15
|
+
width: 100%;
|
|
16
|
+
height: 100%;
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: flex-start;
|
|
19
|
+
transition: all 0.5s ease-in-out;
|
|
20
|
+
transition-property: transform, height;
|
|
21
|
+
|
|
22
|
+
@include reduce-motion( "transition" );
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.swipeable__page {
|
|
26
|
+
width: 100%;
|
|
27
|
+
flex-shrink: 0;
|
|
28
|
+
margin-left: 0;
|
|
29
|
+
|
|
30
|
+
&.is-clone {
|
|
31
|
+
position: relative;
|
|
32
|
+
pointer-events: none; // Prevent interactions with cloned elements
|
|
33
|
+
}
|
|
34
|
+
}
|
package/index.ts
CHANGED
|
@@ -44,9 +44,6 @@ export { default as CopyToClipboard } from './components/copy-to-clipboard/index
|
|
|
44
44
|
export * from './components/icons/index.js';
|
|
45
45
|
export { default as SplitButton } from './components/split-button/index.js';
|
|
46
46
|
export { default as ThemeProvider } from './components/theme-provider/index.js';
|
|
47
|
-
export { default as ThreatFixerButton } from './components/threat-fixer-button/index.js';
|
|
48
|
-
export { default as ThreatSeverityBadge } from './components/threat-severity-badge/index.js';
|
|
49
|
-
export { default as ThreatsDataViews } from './components/threats-data-views/index.js';
|
|
50
47
|
export { default as Text, H2, H3, Title } from './components/text/index.js';
|
|
51
48
|
export { default as ToggleControl } from './components/toggle-control/index.js';
|
|
52
49
|
export { default as NumberControl } from './components/number-control/index.js';
|
|
@@ -56,6 +53,7 @@ export { default as Button } from './components/button/index.js';
|
|
|
56
53
|
export type { ButtonProps } from './components/button/types.js';
|
|
57
54
|
export { default as LoadingPlaceholder } from './components/loading-placeholder/index.js';
|
|
58
55
|
export { default as TermsOfService } from './components/terms-of-service/index.js';
|
|
56
|
+
export { default as Badge } from './components/badge/index.js';
|
|
59
57
|
export { default as Chip } from './components/chip/index.js';
|
|
60
58
|
export {
|
|
61
59
|
default as PricingTable,
|
|
@@ -68,6 +66,7 @@ export { default as BoostScoreGraph } from './components/boost-score-graph/index
|
|
|
68
66
|
export { default as ProductPrice } from './components/product-price/index.js';
|
|
69
67
|
export { default as ProductOffer, IconsCard } from './components/product-offer/index.js';
|
|
70
68
|
export { default as Dialog } from './components/dialog/index.js';
|
|
69
|
+
export { default as DotPager } from './components/dot-pager/index.js';
|
|
71
70
|
export { default as DonutMeter } from './components/donut-meter/index.js';
|
|
72
71
|
export { default as RecordMeterBar } from './components/record-meter-bar/index.js';
|
|
73
72
|
export { default as ContextualUpgradeTrigger } from './components/contextual-upgrade-trigger/index.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/jetpack-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.72.0",
|
|
4
4
|
"description": "Jetpack Components Package",
|
|
5
5
|
"author": "Automattic",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -17,14 +17,12 @@
|
|
|
17
17
|
"@automattic/format-currency": "1.0.1",
|
|
18
18
|
"@automattic/jetpack-boost-score-api": "^0.1.60",
|
|
19
19
|
"@automattic/jetpack-api": "^0.20.0",
|
|
20
|
-
"@automattic/jetpack-scan": "^0.5.9",
|
|
21
20
|
"@automattic/jetpack-script-data": "^0.3.0",
|
|
22
21
|
"@babel/runtime": "^7",
|
|
23
22
|
"@wordpress/browserslist-config": "6.19.0",
|
|
24
23
|
"@wordpress/components": "29.5.0",
|
|
25
24
|
"@wordpress/compose": "7.19.0",
|
|
26
25
|
"@wordpress/data": "10.19.0",
|
|
27
|
-
"@wordpress/dataviews": "4.15.0",
|
|
28
26
|
"@wordpress/date": "5.19.0",
|
|
29
27
|
"@wordpress/element": "6.19.0",
|
|
30
28
|
"@wordpress/i18n": "5.19.0",
|