@via-profit/ui-kit 0.7.0 → 0.9.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/Autocomplete/AutocompleteContainer.d.ts +13 -0
- package/Autocomplete/AutocompleteContainer.js +46 -7
- package/Badge/BadgeBase.d.ts +10 -1
- package/Badge/BadgeBase.js +9 -2
- package/Badge/BadgeDot.d.ts +4 -0
- package/Badge/BadgeDot.js +20 -0
- package/Badge/BadgeStyled.js +67 -26
- package/Badge/index.d.ts +4 -4
- package/CHANGELOG.md +20 -0
- package/EmptyState/EmptyStateAction.d.ts +4 -0
- package/EmptyState/EmptyStateAction.js +16 -0
- package/EmptyState/EmptyStateContainer.d.ts +8 -0
- package/EmptyState/EmptyStateContainer.js +34 -0
- package/EmptyState/EmptyStateContent.d.ts +4 -0
- package/EmptyState/EmptyStateContent.js +15 -0
- package/EmptyState/EmptyStateIcon.d.ts +4 -0
- package/EmptyState/EmptyStateIcon.js +38 -0
- package/EmptyState/EmptyStateTitle.d.ts +4 -0
- package/EmptyState/EmptyStateTitle.js +13 -0
- package/EmptyState/index.d.ts +82 -0
- package/EmptyState/index.js +61 -0
- package/FileDropzone/FileDropzoneArea.d.ts +24 -0
- package/FileDropzone/FileDropzoneArea.js +81 -0
- package/FileDropzone/index.d.ts +107 -0
- package/FileDropzone/index.js +169 -0
- package/FileDropzone/utils.d.ts +9 -0
- package/FileDropzone/utils.js +42 -0
- package/HintPopover/HintPopoverButton.d.ts +4 -0
- package/HintPopover/HintPopoverButton.js +67 -0
- package/HintPopover/HintPopoverContainer.d.ts +4 -0
- package/HintPopover/HintPopoverContainer.js +52 -0
- package/HintPopover/index.d.ts +67 -0
- package/HintPopover/index.js +71 -0
- package/InfoNote/InfoNoteContainer.d.ts +14 -0
- package/InfoNote/InfoNoteContainer.js +56 -0
- package/InfoNote/InfoNoteContent.d.ts +4 -0
- package/InfoNote/InfoNoteContent.js +16 -0
- package/InfoNote/InfoNoteIcon.d.ts +9 -0
- package/InfoNote/InfoNoteIcon.js +47 -0
- package/InfoNote/InfoNoteTitle.d.ts +4 -0
- package/InfoNote/InfoNoteTitle.js +13 -0
- package/InfoNote/index.d.ts +65 -0
- package/InfoNote/index.js +59 -0
- package/Modal/Drawer/DrawerInner.d.ts +6 -0
- package/Modal/Drawer/DrawerInner.js +91 -2
- package/Modal/Drawer/index.d.ts +8 -0
- package/Modal/Drawer/index.js +3 -3
- package/Modal/Drawer/useSwipeToClose.d.ts +30 -0
- package/Modal/Drawer/useSwipeToClose.js +238 -0
- package/Skeleton/index.d.ts +39 -0
- package/Skeleton/index.js +69 -0
- package/esm/Autocomplete/AutocompleteContainer.js +46 -7
- package/esm/Badge/BadgeBase.js +9 -2
- package/esm/Badge/BadgeDot.js +15 -0
- package/esm/Badge/BadgeStyled.js +67 -26
- package/esm/EmptyState/EmptyStateAction.js +11 -0
- package/esm/EmptyState/EmptyStateContainer.js +29 -0
- package/esm/EmptyState/EmptyStateContent.js +10 -0
- package/esm/EmptyState/EmptyStateIcon.js +33 -0
- package/esm/EmptyState/EmptyStateTitle.js +8 -0
- package/esm/EmptyState/index.js +42 -0
- package/esm/FileDropzone/FileDropzoneArea.js +76 -0
- package/esm/FileDropzone/index.js +148 -0
- package/esm/FileDropzone/utils.js +37 -0
- package/esm/HintPopover/HintPopoverButton.js +62 -0
- package/esm/HintPopover/HintPopoverContainer.js +47 -0
- package/esm/HintPopover/index.js +52 -0
- package/esm/InfoNote/InfoNoteContainer.js +49 -0
- package/esm/InfoNote/InfoNoteContent.js +11 -0
- package/esm/InfoNote/InfoNoteIcon.js +42 -0
- package/esm/InfoNote/InfoNoteTitle.js +8 -0
- package/esm/InfoNote/index.js +40 -0
- package/esm/Modal/Drawer/DrawerInner.js +91 -2
- package/esm/Modal/Drawer/index.js +3 -3
- package/esm/Modal/Drawer/useSwipeToClose.js +233 -0
- package/esm/Skeleton/index.js +64 -0
- package/package.json +126 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import styled from '@emotion/styled';
|
|
14
|
+
import { variantColor } from './InfoNoteContainer';
|
|
15
|
+
const StyledIcon = styled.span `
|
|
16
|
+
flex: none;
|
|
17
|
+
display: inline-flex;
|
|
18
|
+
font-size: 1.15em;
|
|
19
|
+
color: ${({ theme, $variant }) => variantColor(theme, $variant).toString()};
|
|
20
|
+
`;
|
|
21
|
+
const Path = styled.path `
|
|
22
|
+
fill: currentColor;
|
|
23
|
+
`;
|
|
24
|
+
/**
|
|
25
|
+
* The paths of the default icons, 24×24: the circle with «i», the check, the triangle with «!»
|
|
26
|
+
* and the circle with «!»
|
|
27
|
+
*/
|
|
28
|
+
const PATHS = {
|
|
29
|
+
info: 'M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z',
|
|
30
|
+
success: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm4.59-12.42L10 14.17l-2.59-2.58L6 13l4 4 8-8z',
|
|
31
|
+
warning: 'M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z',
|
|
32
|
+
error: 'M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z',
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The icon of the variant. `children` replaces the default icon
|
|
36
|
+
*/
|
|
37
|
+
const InfoNoteIcon = (props, ref) => {
|
|
38
|
+
const { variant, children } = props, nativeProps = __rest(props, ["variant", "children"]);
|
|
39
|
+
return (React.createElement(StyledIcon, Object.assign({}, nativeProps, { "$variant": variant, "aria-hidden": true, ref: ref }), children !== null && children !== void 0 ? children : (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1em", height: "1em", viewBox: "0 0 24 24" },
|
|
40
|
+
React.createElement(Path, { d: PATHS[variant] })))));
|
|
41
|
+
};
|
|
42
|
+
export default React.forwardRef(InfoNoteIcon);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from '@emotion/styled';
|
|
3
|
+
const StyledTitle = styled.div `
|
|
4
|
+
font-weight: 600;
|
|
5
|
+
margin-bottom: 0.15em;
|
|
6
|
+
`;
|
|
7
|
+
const InfoNoteTitle = (props, ref) => React.createElement(StyledTitle, Object.assign({}, props, { ref: ref }));
|
|
8
|
+
export default React.forwardRef(InfoNoteTitle);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import Container from './InfoNoteContainer';
|
|
14
|
+
import Icon from './InfoNoteIcon';
|
|
15
|
+
import Title from './InfoNoteTitle';
|
|
16
|
+
import Content from './InfoNoteContent';
|
|
17
|
+
/**
|
|
18
|
+
* The short text in the form or the card which must be seen right here, without a click:
|
|
19
|
+
* «only the completed orders get into the revision». The longer explanation which is needed
|
|
20
|
+
* sometimes goes into HintPopover
|
|
21
|
+
*/
|
|
22
|
+
const InfoNote = (props, ref) => {
|
|
23
|
+
const { variant = 'info', title, icon, children, overrides } = props, nativeProps = __rest(props, ["variant", "title", "icon", "children", "overrides"]);
|
|
24
|
+
const overridesMap = React.useMemo(() => ({
|
|
25
|
+
Container: (overrides === null || overrides === void 0 ? void 0 : overrides.Container) || Container,
|
|
26
|
+
Icon: (overrides === null || overrides === void 0 ? void 0 : overrides.Icon) || Icon,
|
|
27
|
+
Title: (overrides === null || overrides === void 0 ? void 0 : overrides.Title) || Title,
|
|
28
|
+
Content: (overrides === null || overrides === void 0 ? void 0 : overrides.Content) || Content,
|
|
29
|
+
}), [overrides]);
|
|
30
|
+
return (React.createElement(overridesMap.Container, Object.assign({ role: "note" }, nativeProps, { variant: variant, ref: ref }),
|
|
31
|
+
icon !== false && React.createElement(overridesMap.Icon, { variant: variant }, icon),
|
|
32
|
+
React.createElement(overridesMap.Content, null,
|
|
33
|
+
title && React.createElement(overridesMap.Title, null, title),
|
|
34
|
+
children)));
|
|
35
|
+
};
|
|
36
|
+
export * from './InfoNoteContainer';
|
|
37
|
+
export * from './InfoNoteIcon';
|
|
38
|
+
export * from './InfoNoteTitle';
|
|
39
|
+
export * from './InfoNoteContent';
|
|
40
|
+
export default React.forwardRef(InfoNote);
|
|
@@ -13,6 +13,84 @@ import React from 'react';
|
|
|
13
13
|
import styled from '@emotion/styled';
|
|
14
14
|
import { css } from '@emotion/react';
|
|
15
15
|
import ModalInner from '../BaseModal/ModalInner';
|
|
16
|
+
import useSwipeToClose from './useSwipeToClose';
|
|
17
|
+
/**
|
|
18
|
+
* The grip on the side facing the page: shows that the drawer can be swiped
|
|
19
|
+
* and lets a mouse drag it (elsewhere the mouse selects the text)
|
|
20
|
+
*/
|
|
21
|
+
const DrawerHandle = styled.div `
|
|
22
|
+
position: absolute;
|
|
23
|
+
z-index: 1;
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
justify-content: center;
|
|
27
|
+
cursor: grab;
|
|
28
|
+
touch-action: none;
|
|
29
|
+
|
|
30
|
+
&:active {
|
|
31
|
+
cursor: grabbing;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&::before {
|
|
35
|
+
content: '';
|
|
36
|
+
border-radius: 1em;
|
|
37
|
+
background-color: ${({ theme }) => theme.color.textPrimary.alpha(0.24).toString()};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
${({ $anchor }) => {
|
|
41
|
+
switch ($anchor) {
|
|
42
|
+
case 'top':
|
|
43
|
+
return css `
|
|
44
|
+
bottom: 0;
|
|
45
|
+
left: 50%;
|
|
46
|
+
transform: translateX(-50%);
|
|
47
|
+
width: 4em;
|
|
48
|
+
height: 1.25em;
|
|
49
|
+
&::before {
|
|
50
|
+
width: 2.5em;
|
|
51
|
+
height: 0.25em;
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
case 'left':
|
|
55
|
+
return css `
|
|
56
|
+
right: 0;
|
|
57
|
+
top: 50%;
|
|
58
|
+
transform: translateY(-50%);
|
|
59
|
+
width: 1.25em;
|
|
60
|
+
height: 4em;
|
|
61
|
+
&::before {
|
|
62
|
+
width: 0.25em;
|
|
63
|
+
height: 2.5em;
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
case 'right':
|
|
67
|
+
return css `
|
|
68
|
+
left: 0;
|
|
69
|
+
top: 50%;
|
|
70
|
+
transform: translateY(-50%);
|
|
71
|
+
width: 1.25em;
|
|
72
|
+
height: 4em;
|
|
73
|
+
&::before {
|
|
74
|
+
width: 0.25em;
|
|
75
|
+
height: 2.5em;
|
|
76
|
+
}
|
|
77
|
+
`;
|
|
78
|
+
case 'bottom':
|
|
79
|
+
default:
|
|
80
|
+
return css `
|
|
81
|
+
top: 0;
|
|
82
|
+
left: 50%;
|
|
83
|
+
transform: translateX(-50%);
|
|
84
|
+
width: 4em;
|
|
85
|
+
height: 1.25em;
|
|
86
|
+
&::before {
|
|
87
|
+
width: 2.5em;
|
|
88
|
+
height: 0.25em;
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
}}
|
|
93
|
+
`;
|
|
16
94
|
const StyledDrawerBottomInner = styled(ModalInner) `
|
|
17
95
|
position: fixed;
|
|
18
96
|
margin: 0;
|
|
@@ -126,7 +204,16 @@ const StyledDrawerLeftInner = styled(ModalInner) `
|
|
|
126
204
|
`}
|
|
127
205
|
`;
|
|
128
206
|
const DrawerInner = (props, ref) => {
|
|
129
|
-
const { children, isOpen, anchor, onRequestClose, dialogID } = props, nativeProps = __rest(props, ["children", "isOpen", "anchor", "onRequestClose", "dialogID"]);
|
|
207
|
+
const { children, isOpen, anchor, onRequestClose, dialogID, swipeToClose } = props, nativeProps = __rest(props, ["children", "isOpen", "anchor", "onRequestClose", "dialogID", "swipeToClose"]);
|
|
208
|
+
const elementRef = React.useRef(null);
|
|
209
|
+
React.useImperativeHandle(ref, () => elementRef.current);
|
|
210
|
+
const swipeHandlers = useSwipeToClose({
|
|
211
|
+
elementRef,
|
|
212
|
+
enabled: Boolean(swipeToClose),
|
|
213
|
+
isOpen,
|
|
214
|
+
anchor,
|
|
215
|
+
onClose: onRequestClose,
|
|
216
|
+
});
|
|
130
217
|
const Element = React.useMemo(() => {
|
|
131
218
|
switch (anchor) {
|
|
132
219
|
case 'top':
|
|
@@ -140,6 +227,8 @@ const DrawerInner = (props, ref) => {
|
|
|
140
227
|
return StyledDrawerBottomInner;
|
|
141
228
|
}
|
|
142
229
|
}, [anchor]);
|
|
143
|
-
return (React.createElement(Element, Object.assign({ role: "dialog", "aria-modal": "true", isOpen: isOpen, id: `${dialogID}-description`, onRequestClose: onRequestClose }, nativeProps, { ref:
|
|
230
|
+
return (React.createElement(Element, Object.assign({ role: "dialog", "aria-modal": "true", isOpen: isOpen, id: `${dialogID}-description`, onRequestClose: onRequestClose }, nativeProps, swipeHandlers, { ref: elementRef }),
|
|
231
|
+
swipeToClose && React.createElement(DrawerHandle, { "$anchor": anchor, "data-drawer-handle": true, "aria-hidden": "true" }),
|
|
232
|
+
children));
|
|
144
233
|
};
|
|
145
234
|
export default React.forwardRef(DrawerInner);
|
|
@@ -19,7 +19,7 @@ import Footer from './DrawerFooter';
|
|
|
19
19
|
import Overlay from '../BaseModal/ModalOverlay';
|
|
20
20
|
import useDialogID from '../BaseModal/useDialogID';
|
|
21
21
|
const Drawer = props => {
|
|
22
|
-
const { children, onRequestClose, showCloseButton, header, closeButtonLabel, anchor, toolbar, footer, overrides } = props, otherProps = __rest(props, ["children", "onRequestClose", "showCloseButton", "header", "closeButtonLabel", "anchor", "toolbar", "footer", "overrides"]);
|
|
22
|
+
const { children, onRequestClose, showCloseButton, header, closeButtonLabel, anchor, toolbar, footer, overrides, swipeToClose } = props, otherProps = __rest(props, ["children", "onRequestClose", "showCloseButton", "header", "closeButtonLabel", "anchor", "toolbar", "footer", "overrides", "swipeToClose"]);
|
|
23
23
|
const hasFooter = React.useMemo(() => typeof footer !== 'undefined' && footer !== null, [footer]);
|
|
24
24
|
const hasHeader = React.useMemo(() => typeof header !== 'undefined' ||
|
|
25
25
|
typeof toolbar !== 'undefined' ||
|
|
@@ -29,8 +29,8 @@ const Drawer = props => {
|
|
|
29
29
|
// Created once: a component created during the render is a new type on every render,
|
|
30
30
|
// so React would remount the whole drawer content (inputs lose the focus and the state)
|
|
31
31
|
const Inner = React.useMemo(() => React.forwardRef(function Inner(innerProps, ref) {
|
|
32
|
-
return (React.createElement(DrawerInner, Object.assign({ anchor: anchor, dialogID: dialogID, "aria-labelledby": hasTitle ? `${dialogID}-title` : undefined, ref: ref }, innerProps)));
|
|
33
|
-
}), [anchor, dialogID, hasTitle]);
|
|
32
|
+
return (React.createElement(DrawerInner, Object.assign({ anchor: anchor, dialogID: dialogID, swipeToClose: swipeToClose, "aria-labelledby": hasTitle ? `${dialogID}-title` : undefined, ref: ref }, innerProps)));
|
|
33
|
+
}), [anchor, dialogID, hasTitle, swipeToClose]);
|
|
34
34
|
const overridesMap = React.useMemo(() => ({
|
|
35
35
|
Container: (overrides === null || overrides === void 0 ? void 0 : overrides.Container) || Container,
|
|
36
36
|
Header: (overrides === null || overrides === void 0 ? void 0 : overrides.Header) || Header,
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
// The finger moves this far before the gesture is recognized as a drag or a scroll
|
|
3
|
+
const SLOP = 6;
|
|
4
|
+
// The drawer is closed when dragged by this part of its size or thrown faster than this
|
|
5
|
+
const CLOSE_RATIO = 0.25;
|
|
6
|
+
const CLOSE_VELOCITY = 0.5; // px per ms
|
|
7
|
+
const RETURN_TRANSITION = 'transform 240ms ease-out';
|
|
8
|
+
const initialState = {
|
|
9
|
+
mode: 'idle',
|
|
10
|
+
pointerId: -1,
|
|
11
|
+
startX: 0,
|
|
12
|
+
startY: 0,
|
|
13
|
+
offset: 0,
|
|
14
|
+
lastOffset: 0,
|
|
15
|
+
lastTime: 0,
|
|
16
|
+
velocity: 0,
|
|
17
|
+
};
|
|
18
|
+
const isVertical = (anchor) => anchor === 'bottom' || anchor === 'top';
|
|
19
|
+
/**
|
|
20
|
+
* The movement of the finger towards the edge of the drawer: positive closes it
|
|
21
|
+
*/
|
|
22
|
+
const getClosingDelta = (anchor, dx, dy) => {
|
|
23
|
+
switch (anchor) {
|
|
24
|
+
case 'top':
|
|
25
|
+
return -dy;
|
|
26
|
+
case 'left':
|
|
27
|
+
return -dx;
|
|
28
|
+
case 'right':
|
|
29
|
+
return dx;
|
|
30
|
+
case 'bottom':
|
|
31
|
+
default:
|
|
32
|
+
return dy;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const getTransform = (anchor, offset) => {
|
|
36
|
+
switch (anchor) {
|
|
37
|
+
case 'top':
|
|
38
|
+
return `translateY(${-offset}px)`;
|
|
39
|
+
case 'left':
|
|
40
|
+
return `translateX(${-offset}px)`;
|
|
41
|
+
case 'right':
|
|
42
|
+
return `translateX(${offset}px)`;
|
|
43
|
+
case 'bottom':
|
|
44
|
+
default:
|
|
45
|
+
return `translateY(${offset}px)`;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Whether an element between the target and the drawer can scroll the way the finger moves.
|
|
50
|
+
* Then the finger scrolls the content: the drawer is dragged only when the content is at its edge
|
|
51
|
+
*/
|
|
52
|
+
const canScrollWithGesture = (target, root, anchor) => {
|
|
53
|
+
let element = target instanceof Element ? target : null;
|
|
54
|
+
while (element && element !== root) {
|
|
55
|
+
if (element instanceof HTMLElement) {
|
|
56
|
+
const style = window.getComputedStyle(element);
|
|
57
|
+
const overflow = isVertical(anchor) ? style.overflowY : style.overflowX;
|
|
58
|
+
const scrollable = overflow === 'auto' || overflow === 'scroll';
|
|
59
|
+
if (scrollable) {
|
|
60
|
+
const { scrollTop, scrollLeft, scrollHeight, scrollWidth, clientHeight, clientWidth } = element;
|
|
61
|
+
switch (anchor) {
|
|
62
|
+
case 'bottom':
|
|
63
|
+
if (scrollTop > 0) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
case 'top':
|
|
68
|
+
if (scrollTop + clientHeight < scrollHeight - 1) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
case 'left':
|
|
73
|
+
if (scrollLeft + clientWidth < scrollWidth - 1) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
case 'right':
|
|
78
|
+
if (scrollLeft > 0) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
default:
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
element = element.parentElement;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Closes the drawer by a swipe towards its edge.
|
|
93
|
+
*
|
|
94
|
+
* The pointer events move the drawer. A touch would be taken by the browser for the scroll
|
|
95
|
+
* (and the pointer cancelled), so the native `touchmove` is prevented while the drawer is dragged
|
|
96
|
+
*/
|
|
97
|
+
const useSwipeToClose = (options) => {
|
|
98
|
+
const { elementRef, enabled, isOpen, anchor, onClose } = options;
|
|
99
|
+
const stateRef = React.useRef(Object.assign({}, initialState));
|
|
100
|
+
const setOffset = React.useCallback((offset, transition) => {
|
|
101
|
+
const element = elementRef.current;
|
|
102
|
+
if (element) {
|
|
103
|
+
element.style.transition = transition;
|
|
104
|
+
element.style.transform = offset > 0 ? getTransform(anchor, offset) : '';
|
|
105
|
+
}
|
|
106
|
+
}, [anchor, elementRef]);
|
|
107
|
+
const restore = React.useCallback(() => {
|
|
108
|
+
const element = elementRef.current;
|
|
109
|
+
if (element) {
|
|
110
|
+
element.style.transition = '';
|
|
111
|
+
element.style.transform = '';
|
|
112
|
+
}
|
|
113
|
+
}, [elementRef]);
|
|
114
|
+
// A drawer closed by a swipe keeps its offset while it slides out: reset it for the next opening
|
|
115
|
+
React.useEffect(() => {
|
|
116
|
+
if (isOpen) {
|
|
117
|
+
stateRef.current = Object.assign({}, initialState);
|
|
118
|
+
restore();
|
|
119
|
+
}
|
|
120
|
+
}, [isOpen, restore]);
|
|
121
|
+
/**
|
|
122
|
+
* Decides on the first noticeable movement whether it drags the drawer or scrolls the content
|
|
123
|
+
*/
|
|
124
|
+
const update = React.useCallback((clientX, clientY, target) => {
|
|
125
|
+
const state = stateRef.current;
|
|
126
|
+
const element = elementRef.current;
|
|
127
|
+
if (!element || (state.mode !== 'pending' && state.mode !== 'drag')) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const dx = clientX - state.startX;
|
|
131
|
+
const dy = clientY - state.startY;
|
|
132
|
+
const closing = getClosingDelta(anchor, dx, dy);
|
|
133
|
+
if (state.mode === 'pending') {
|
|
134
|
+
const main = isVertical(anchor) ? Math.abs(dy) : Math.abs(dx);
|
|
135
|
+
const cross = isVertical(anchor) ? Math.abs(dx) : Math.abs(dy);
|
|
136
|
+
if (main < SLOP && cross < SLOP) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const isDrag = closing > 0 && main >= cross && !canScrollWithGesture(target, element, anchor);
|
|
140
|
+
state.mode = isDrag ? 'drag' : 'scroll';
|
|
141
|
+
if (!isDrag) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// The drag starts from here, without a jump by the slop
|
|
145
|
+
state.startX = clientX;
|
|
146
|
+
state.startY = clientY;
|
|
147
|
+
state.lastTime = performance.now();
|
|
148
|
+
}
|
|
149
|
+
const offset = Math.max(0, getClosingDelta(anchor, clientX - state.startX, clientY - state.startY));
|
|
150
|
+
const now = performance.now();
|
|
151
|
+
const elapsed = now - state.lastTime;
|
|
152
|
+
if (elapsed > 0) {
|
|
153
|
+
state.velocity = (offset - state.lastOffset) / elapsed;
|
|
154
|
+
}
|
|
155
|
+
state.lastOffset = offset;
|
|
156
|
+
state.lastTime = now;
|
|
157
|
+
state.offset = offset;
|
|
158
|
+
setOffset(offset, 'none');
|
|
159
|
+
}, [anchor, elementRef, setOffset]);
|
|
160
|
+
// A non-passive listener: only it may stop the browser from scrolling under the drag
|
|
161
|
+
React.useEffect(() => {
|
|
162
|
+
const element = elementRef.current;
|
|
163
|
+
if (!enabled || !element) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
const handleTouchMove = (event) => {
|
|
167
|
+
const touch = event.touches[0];
|
|
168
|
+
if (touch) {
|
|
169
|
+
update(touch.clientX, touch.clientY, event.target);
|
|
170
|
+
}
|
|
171
|
+
if (stateRef.current.mode === 'drag' && event.cancelable) {
|
|
172
|
+
event.preventDefault();
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
element.addEventListener('touchmove', handleTouchMove, { passive: false });
|
|
176
|
+
return () => {
|
|
177
|
+
element.removeEventListener('touchmove', handleTouchMove);
|
|
178
|
+
};
|
|
179
|
+
}, [enabled, elementRef, update]);
|
|
180
|
+
const onPointerDown = React.useCallback((event) => {
|
|
181
|
+
if (!event.isPrimary || event.button !== 0) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// A mouse drags only by the handle: everywhere else it selects the text
|
|
185
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
186
|
+
if (event.pointerType === 'mouse') {
|
|
187
|
+
if (!(target === null || target === void 0 ? void 0 : target.closest('[data-drawer-handle]'))) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
192
|
+
}
|
|
193
|
+
stateRef.current = Object.assign(Object.assign({}, initialState), { mode: 'pending', pointerId: event.pointerId, startX: event.clientX, startY: event.clientY, lastTime: performance.now() });
|
|
194
|
+
}, []);
|
|
195
|
+
const onPointerMove = React.useCallback((event) => {
|
|
196
|
+
if (event.pointerId === stateRef.current.pointerId) {
|
|
197
|
+
update(event.clientX, event.clientY, event.target);
|
|
198
|
+
}
|
|
199
|
+
}, [update]);
|
|
200
|
+
const finish = React.useCallback((event, cancelled) => {
|
|
201
|
+
const state = stateRef.current;
|
|
202
|
+
if (event.pointerId !== state.pointerId) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const wasDragged = state.mode === 'drag';
|
|
206
|
+
const element = elementRef.current;
|
|
207
|
+
const size = element ? (isVertical(anchor) ? element.offsetHeight : element.offsetWidth) : 0;
|
|
208
|
+
const shouldClose = wasDragged &&
|
|
209
|
+
!cancelled &&
|
|
210
|
+
(state.offset > size * CLOSE_RATIO || state.velocity > CLOSE_VELOCITY);
|
|
211
|
+
stateRef.current = Object.assign({}, initialState);
|
|
212
|
+
if (!wasDragged) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (shouldClose) {
|
|
216
|
+
// The offset is kept: the drawer slides out from where the finger left it.
|
|
217
|
+
// The own transitions of the drawer are back, the drag turned them off
|
|
218
|
+
if (element) {
|
|
219
|
+
element.style.transition = '';
|
|
220
|
+
}
|
|
221
|
+
onClose(event);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
setOffset(0, RETURN_TRANSITION);
|
|
225
|
+
}, [anchor, elementRef, onClose, setOffset]);
|
|
226
|
+
const onPointerUp = React.useCallback((event) => finish(event, false), [finish]);
|
|
227
|
+
const onPointerCancel = React.useCallback((event) => finish(event, true), [finish]);
|
|
228
|
+
if (!enabled) {
|
|
229
|
+
return {};
|
|
230
|
+
}
|
|
231
|
+
return { onPointerDown, onPointerMove, onPointerUp, onPointerCancel };
|
|
232
|
+
};
|
|
233
|
+
export default useSwipeToClose;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import styled from '@emotion/styled';
|
|
14
|
+
import { css, keyframes } from '@emotion/react';
|
|
15
|
+
const size = (value) => (typeof value === 'number' ? `${value}em` : value);
|
|
16
|
+
const DEFAULT_HEIGHT = {
|
|
17
|
+
text: '0.8em',
|
|
18
|
+
rect: '3em',
|
|
19
|
+
circle: '2.5em',
|
|
20
|
+
pill: '1.6em',
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The solid shape fades in and out: calmer than the moving gradient and does not depend on the width
|
|
24
|
+
*/
|
|
25
|
+
const pulse = keyframes `
|
|
26
|
+
0%,
|
|
27
|
+
100% {
|
|
28
|
+
opacity: 1;
|
|
29
|
+
}
|
|
30
|
+
50% {
|
|
31
|
+
opacity: 0.4;
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
const StyledSkeleton = styled.span `
|
|
35
|
+
display: block;
|
|
36
|
+
flex-shrink: 0;
|
|
37
|
+
width: ${({ $width }) => $width};
|
|
38
|
+
max-width: 100%;
|
|
39
|
+
height: ${({ $height }) => $height};
|
|
40
|
+
border-radius: ${({ theme, $variant }) => $variant === 'circle' || $variant === 'pill'
|
|
41
|
+
? '999em'
|
|
42
|
+
: `${theme.shape.radiusFactor * ($variant === 'rect' ? 1.5 : 0.75)}em`};
|
|
43
|
+
background-color: ${({ theme }) => theme.color.textPrimary.alpha(0.08).toString()};
|
|
44
|
+
animation: ${pulse} 1.6s ease-in-out infinite;
|
|
45
|
+
|
|
46
|
+
${({ $variant, $height }) => $variant === 'circle' &&
|
|
47
|
+
css `
|
|
48
|
+
aspect-ratio: 1;
|
|
49
|
+
width: ${$height};
|
|
50
|
+
`}
|
|
51
|
+
|
|
52
|
+
@media (prefers-reduced-motion: reduce) {
|
|
53
|
+
animation: none;
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
/**
|
|
57
|
+
* The placeholder of the content which is loaded: the gray shape which fades in and out.
|
|
58
|
+
* Shows the structure of the content instead of the spinner
|
|
59
|
+
*/
|
|
60
|
+
const Skeleton = (props, ref) => {
|
|
61
|
+
const { variant = 'text', width = '100%', height } = props, nativeProps = __rest(props, ["variant", "width", "height"]);
|
|
62
|
+
return (React.createElement(StyledSkeleton, Object.assign({ "aria-hidden": true }, nativeProps, { ref: ref, "$variant": variant, "$width": size(width), "$height": height !== undefined ? size(height) : DEFAULT_HEIGHT[variant] })));
|
|
63
|
+
};
|
|
64
|
+
export default React.forwardRef(Skeleton);
|