@startlibs/components 1.0.4 → 1.1.1
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/lib/BasicBulletList.mjs +9 -0
- package/lib/BasicBulletList.mjs.map +1 -0
- package/lib/Button.mjs +95 -0
- package/lib/Button.mjs.map +1 -0
- package/lib/ContextMenu.mjs +452 -0
- package/lib/ContextMenu.mjs.map +1 -0
- package/lib/Dialog.mjs +232 -0
- package/lib/Dialog.mjs.map +1 -0
- package/lib/Draggable.mjs +880 -0
- package/lib/Draggable.mjs.map +1 -0
- package/lib/ErrorBoundary.mjs +108 -0
- package/lib/ErrorBoundary.mjs.map +1 -0
- package/lib/FillLastLineResizer.mjs +238 -0
- package/lib/FillLastLineResizer.mjs.map +1 -0
- package/lib/Flip.mjs +604 -0
- package/lib/Flip.mjs.map +1 -0
- package/lib/FlipList.mjs +186 -0
- package/lib/FlipList.mjs.map +1 -0
- package/lib/FormatDate.mjs +25 -0
- package/lib/FormatDate.mjs.map +1 -0
- package/lib/Icon.mjs +50 -0
- package/lib/Icon.mjs.map +1 -0
- package/lib/Image.mjs +41 -0
- package/lib/Image.mjs.map +1 -0
- package/lib/Link.mjs +116 -0
- package/lib/Link.mjs.map +1 -0
- package/lib/Loading.mjs +10 -0
- package/lib/Loading.mjs.map +1 -0
- package/lib/LoadingDiv.mjs +83 -0
- package/lib/LoadingDiv.mjs.map +1 -0
- package/lib/Notifications.mjs +63 -0
- package/lib/Notifications.mjs.map +1 -0
- package/lib/PageLoader.mjs +42 -0
- package/lib/PageLoader.mjs.map +1 -0
- package/lib/PersistentNotifications.mjs +130 -0
- package/lib/PersistentNotifications.mjs.map +1 -0
- package/lib/Popup.mjs +49 -0
- package/lib/Popup.mjs.map +1 -0
- package/lib/SplitColumnsContainer.mjs +43 -0
- package/lib/SplitColumnsContainer.mjs.map +1 -0
- package/lib/SwitchDiv.mjs +61 -0
- package/lib/SwitchDiv.mjs.map +1 -0
- package/lib/TextButton.mjs +41 -0
- package/lib/TextButton.mjs.map +1 -0
- package/lib/ToastNotifications.mjs +215 -0
- package/lib/ToastNotifications.mjs.map +1 -0
- package/lib/Tooltip.mjs +160 -0
- package/lib/Tooltip.mjs.map +1 -0
- package/lib/TransitionDiv.mjs +56 -0
- package/lib/TransitionDiv.mjs.map +1 -0
- package/lib/WarningDialog.mjs +84 -0
- package/lib/WarningDialog.mjs.map +1 -0
- package/lib/alt/buttons/OnClickLoadingButton.mjs +36 -0
- package/lib/alt/buttons/OnClickLoadingButton.mjs.map +1 -0
- package/lib/alt/dialogs/DialogWithImageHeader.mjs +14 -0
- package/lib/alt/dialogs/DialogWithImageHeader.mjs.map +1 -0
- package/lib/alt/dialogs/DialogWithSidebar.mjs +106 -0
- package/lib/alt/dialogs/DialogWithSidebar.mjs.map +1 -0
- package/lib/alt/dialogs/MobileBubbleDialog.mjs +49 -0
- package/lib/alt/dialogs/MobileBubbleDialog.mjs.map +1 -0
- package/lib/containers/DialogContainer.mjs +413 -0
- package/lib/containers/DialogContainer.mjs.map +1 -0
- package/lib/containers/Reposition.mjs +35 -0
- package/lib/containers/Reposition.mjs.map +1 -0
- package/lib/containers/usePositioning.mjs +294 -0
- package/lib/containers/usePositioning.mjs.map +1 -0
- package/lib/index.cjs +5000 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.mjs +37 -0
- package/lib/index.mjs.map +1 -0
- package/lib/useDialogWithToggle.mjs +18 -0
- package/lib/useDialogWithToggle.mjs.map +1 -0
- package/lib/willUseIsSticky.mjs +123 -0
- package/lib/willUseIsSticky.mjs.map +1 -0
- package/package.json +47 -30
- package/src/BasicBulletList.jsx +21 -0
- package/src/Button.jsx +288 -0
- package/src/ContextMenu.jsx +621 -0
- package/src/Dialog.jsx +413 -0
- package/src/Draggable.jsx +801 -0
- package/src/ErrorBoundary.jsx +75 -0
- package/src/FillLastLineResizer.jsx +226 -0
- package/src/Flip.jsx +560 -0
- package/src/FlipList.jsx +136 -0
- package/src/FormatDate.jsx +9 -0
- package/src/Icon.jsx +49 -0
- package/src/Image.jsx +23 -0
- package/src/Link.jsx +68 -0
- package/src/Loading.jsx +29 -0
- package/src/LoadingDiv.jsx +75 -0
- package/src/Notifications.jsx +89 -0
- package/src/PageLoader.jsx +48 -0
- package/src/PersistentNotifications.jsx +182 -0
- package/src/Popup.jsx +49 -0
- package/src/SplitColumnsContainer.jsx +63 -0
- package/src/SwitchDiv.jsx +30 -0
- package/src/TextButton.jsx +34 -0
- package/src/ToastNotifications.jsx +298 -0
- package/src/Tooltip.jsx +158 -0
- package/src/TransitionDiv.jsx +34 -0
- package/src/WarningDialog.jsx +70 -0
- package/src/alt/buttons/OnClickLoadingButton.jsx +16 -0
- package/src/alt/dialogs/DialogWithImageHeader.jsx +42 -0
- package/src/alt/dialogs/DialogWithSidebar.jsx +135 -0
- package/src/alt/dialogs/MobileBubbleDialog.jsx +56 -0
- package/src/containers/DialogContainer.jsx +423 -0
- package/src/containers/Reposition.jsx +24 -0
- package/src/containers/usePositioning.jsx +305 -0
- package/src/index.js +35 -0
- package/src/useDialogWithToggle.jsx +12 -0
- package/src/willUseIsSticky.jsx +113 -0
- package/.babelrc +0 -27
- package/lib/index.js +0 -5429
- package/rollup.config.js +0 -52
- package/yarn-error.log +0 -3724
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React, {useRef} from 'react'
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
import {oneWayAnimation} from '@startlibs/utils'
|
|
4
|
+
import {willUseFlipSwitch} from './Flip'
|
|
5
|
+
import {useLazyConstant} from '@startlibs/core'
|
|
6
|
+
import {callIfFunction, twoWayAnimation} from '@startlibs/utils'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const BlockContainer = styled.div`
|
|
10
|
+
position:relative;
|
|
11
|
+
`
|
|
12
|
+
|
|
13
|
+
const [fadeIn, relativeFadeout] = twoWayAnimation('opacity: 0;','opacity: 0;', 'opacity: 1;')
|
|
14
|
+
const absoluteOnLeave = oneWayAnimation('position: absolute;', 'position: absolute;')
|
|
15
|
+
const fadeOut = [absoluteOnLeave,relativeFadeout]
|
|
16
|
+
|
|
17
|
+
export const SwitchDiv = styled(({children, className,enterAnimation = fadeIn, effect, leaveAnimation = fadeOut, skipChromeFix, on,...rest}) => {
|
|
18
|
+
const switchAnimation = useLazyConstant(willUseFlipSwitch)
|
|
19
|
+
|
|
20
|
+
return <SwitchContainer enterAnimation={enterAnimation} effect={effect} className={className} leaveAnimation={leaveAnimation} key={on + ""} switchAnimation={switchAnimation} children={children} />
|
|
21
|
+
})``
|
|
22
|
+
|
|
23
|
+
const SwitchContainer = ({children,switchAnimation, className, effect ,enterAnimation,leaveAnimation}) => {
|
|
24
|
+
const contentRef = useRef()
|
|
25
|
+
switchAnimation(contentRef,enterAnimation,leaveAnimation, {effect})
|
|
26
|
+
|
|
27
|
+
return <BlockContainer ref={contentRef} className={className}>
|
|
28
|
+
{callIfFunction(children)}
|
|
29
|
+
</BlockContainer>
|
|
30
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import {darken} from 'polished'
|
|
4
|
+
import { getColor } from '@startlibs/utils';
|
|
5
|
+
import {Link} from './Link'
|
|
6
|
+
import {Loading} from './Loading'
|
|
7
|
+
|
|
8
|
+
export const TextButton = styled(({className,isLoading,alert, fontSize, children, innerAs: Wrapper = 'div', ...props}) => <Wrapper className={className} {...props}>
|
|
9
|
+
{children} {isLoading && <Loading size={14} borderWidth={3} />}
|
|
10
|
+
</Wrapper>)`
|
|
11
|
+
display: inline-block;
|
|
12
|
+
font-weight: 600;
|
|
13
|
+
font-size: ${props => props.fontSize ? fontSize : "14"}px;
|
|
14
|
+
color: ${props => props.alert ? getColor('alert') : getColor('main')};
|
|
15
|
+
cursor: pointer;
|
|
16
|
+
text-decoration: none;
|
|
17
|
+
:hover, :active {
|
|
18
|
+
color: ${props => props.alert ? darken(0.1, getColor("alert")(props)) : darken(0.1, getColor("main")(props))};
|
|
19
|
+
}
|
|
20
|
+
${Loading} {
|
|
21
|
+
display: inline-block;
|
|
22
|
+
margin-left: 0.25rem;
|
|
23
|
+
vertical-align: -3px;
|
|
24
|
+
}
|
|
25
|
+
${props => props.nowrap && `
|
|
26
|
+
white-space: nowrap;
|
|
27
|
+
`}
|
|
28
|
+
& ~ & {
|
|
29
|
+
margin-left: 1rem;
|
|
30
|
+
}
|
|
31
|
+
`
|
|
32
|
+
|
|
33
|
+
TextButton.a = (props) => <TextButton {...props} className={props.className} innerAs="a"/>
|
|
34
|
+
TextButton.Link = (props) => <TextButton {...props} className={props.className} innerAs={Link}/>
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import React, {useEffect, useMemo, useRef} from 'react'
|
|
2
|
+
import {oneWayAnimation, constrainEvent} from '@startlibs/utils'
|
|
3
|
+
import _ from 'lodash/fp'
|
|
4
|
+
import styled from 'styled-components'
|
|
5
|
+
import {rgba} from 'polished'
|
|
6
|
+
import {Fill, useToggle, willUseSharedState} from '@startlibs/core'
|
|
7
|
+
import {getUID, ifUndefined, media, callIfFunction, getColor} from '@startlibs/utils'
|
|
8
|
+
import {css} from "styled-components/macro";
|
|
9
|
+
import {FlipList} from './FlipList'
|
|
10
|
+
import {Icon} from './Icon'
|
|
11
|
+
import {Loading} from './Loading'
|
|
12
|
+
import {SwitchDiv} from './SwitchDiv'
|
|
13
|
+
|
|
14
|
+
const NotificationContainer = styled.div `
|
|
15
|
+
position: fixed;
|
|
16
|
+
bottom: 0;
|
|
17
|
+
max-height: 100%;
|
|
18
|
+
z-index: 999999;
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column-reverse;
|
|
21
|
+
align-items: flex-end;
|
|
22
|
+
overflow-y: auto;
|
|
23
|
+
right: 1.5rem;
|
|
24
|
+
${media.desktop`
|
|
25
|
+
padding-bottom: 1.5rem;
|
|
26
|
+
`}
|
|
27
|
+
${media.mobile`
|
|
28
|
+
padding-bottom: 0.75rem;
|
|
29
|
+
`}
|
|
30
|
+
::-webkit-scrollbar {
|
|
31
|
+
width: 21px;
|
|
32
|
+
height: 21px;
|
|
33
|
+
}
|
|
34
|
+
::-webkit-scrollbar-thumb {
|
|
35
|
+
pointer-events: all;
|
|
36
|
+
background: rgba(0,0,0,0.4);
|
|
37
|
+
border-radius: 100px;
|
|
38
|
+
background-clip: padding-box;
|
|
39
|
+
border: 6px solid transparent;
|
|
40
|
+
min-height: 30px;
|
|
41
|
+
min-width: 30px;
|
|
42
|
+
&:hover {
|
|
43
|
+
background: rgba(0,0,0,0.5);
|
|
44
|
+
background-clip: padding-box;
|
|
45
|
+
border: 6px solid transparent;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
::-webkit-scrollbar-corner {
|
|
49
|
+
background: transparent;
|
|
50
|
+
}
|
|
51
|
+
${media.desktop`
|
|
52
|
+
&.JS-dialog-open {
|
|
53
|
+
right: 2.75rem;
|
|
54
|
+
}
|
|
55
|
+
`}
|
|
56
|
+
${media.mobile`
|
|
57
|
+
left: 0.75rem;
|
|
58
|
+
right: 0.75rem;
|
|
59
|
+
`}
|
|
60
|
+
`
|
|
61
|
+
|
|
62
|
+
const ToastBox = styled.div`
|
|
63
|
+
color: white;
|
|
64
|
+
font-size: 14px;
|
|
65
|
+
font-weight: 500;
|
|
66
|
+
border-radius: 12px;
|
|
67
|
+
flex-shrink: 0;
|
|
68
|
+
margin-top: 0.5rem;
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
transition: 0.3s background ease, 0.4s opacity 1.5s;
|
|
71
|
+
${media.desktop`
|
|
72
|
+
background: ${props => rgba(getColor(props.type)(props),ifUndefined(props.opacity,0.7))};
|
|
73
|
+
max-width: 300px;
|
|
74
|
+
`}
|
|
75
|
+
${media.mobile`
|
|
76
|
+
background: ${props => rgba(getColor(props.type)(props),ifUndefined(props.opacity,0.8))};
|
|
77
|
+
`}
|
|
78
|
+
${props => css`background: ${getColor(props.type)};`}
|
|
79
|
+
`
|
|
80
|
+
const NotificationTitleContainer = styled.div`
|
|
81
|
+
background: rgba(0,0,0,0.1);
|
|
82
|
+
padding: 0.5rem 2.25rem 0.5rem 0.75rem;
|
|
83
|
+
border-radius: 12px 12px 0 0;
|
|
84
|
+
position: relative;
|
|
85
|
+
min-height: 30px;
|
|
86
|
+
font-size: 13px;
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
font-weight: 400;
|
|
90
|
+
${props => props.onClick && "cursor: pointer;"}
|
|
91
|
+
`
|
|
92
|
+
const NotificationTitle = styled.div`
|
|
93
|
+
text-overflow: ellipsis;
|
|
94
|
+
white-space: nowrap;
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
`
|
|
97
|
+
const NotificationContent = styled.div`
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
text-align: left;
|
|
101
|
+
padding: 1rem 1.5rem;
|
|
102
|
+
${media.mobile`
|
|
103
|
+
justify-content: center;
|
|
104
|
+
`}
|
|
105
|
+
`
|
|
106
|
+
const NotificationIconWrapper = styled.div`
|
|
107
|
+
margin-right: 1rem;
|
|
108
|
+
position: relative;
|
|
109
|
+
width: 36px;
|
|
110
|
+
text-align: center;
|
|
111
|
+
flex-shrink: 0;
|
|
112
|
+
${Icon} {
|
|
113
|
+
font-size: ${props => (props.isLoading ? 22 : (props.iconFontSize || 40))}px;
|
|
114
|
+
}
|
|
115
|
+
`
|
|
116
|
+
const CloseIcon = styled(Icon).attrs({icon: 'x'})`
|
|
117
|
+
font-size: 20px;
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
width: 2rem;
|
|
120
|
+
height: 2rem;
|
|
121
|
+
line-height: 2rem;
|
|
122
|
+
text-align: center;
|
|
123
|
+
position: absolute;
|
|
124
|
+
top: 50%;
|
|
125
|
+
right: 0.25rem;
|
|
126
|
+
transform: translateY(-50%);
|
|
127
|
+
border-radius: 100px;
|
|
128
|
+
opacity: 0.8;
|
|
129
|
+
:hover, :active {
|
|
130
|
+
background: rgba(0,0,0,0.1);
|
|
131
|
+
opacity: 1;
|
|
132
|
+
}
|
|
133
|
+
`
|
|
134
|
+
const NotificationDetails = styled.div`
|
|
135
|
+
background: rgba(0,0,0,0.1);
|
|
136
|
+
border-radius: 8px;
|
|
137
|
+
padding: 1rem;
|
|
138
|
+
margin: 0 1rem 1rem;
|
|
139
|
+
font-weight: 400;
|
|
140
|
+
font-size: 13px;
|
|
141
|
+
`
|
|
142
|
+
|
|
143
|
+
const [useToastNotifications, _useToastNotificationsRef,setToastNotifications] = willUseSharedState([])
|
|
144
|
+
export {useToastNotifications}
|
|
145
|
+
const formatNotification = (notification,extra) =>
|
|
146
|
+
_.isObject(notification)
|
|
147
|
+
? {...notification,...extra, _key: getUID()}
|
|
148
|
+
: {type:"success",content:notification,...extra, _key: getUID()}
|
|
149
|
+
|
|
150
|
+
export const createToastNotification = (notification,timeout, closeCallback) => {
|
|
151
|
+
const uid = getUID()
|
|
152
|
+
let currentTimer = -1;
|
|
153
|
+
let hasClosed = false;
|
|
154
|
+
const close = () => {
|
|
155
|
+
clearTimeout(currentTimer);
|
|
156
|
+
if (hasClosed) return
|
|
157
|
+
setToastNotifications(_.differenceBy('uid',_, [{uid}]))
|
|
158
|
+
hasClosed = true
|
|
159
|
+
callIfFunction(closeCallback)
|
|
160
|
+
}
|
|
161
|
+
const notificationObj = formatNotification(notification,{uid,close})
|
|
162
|
+
setToastNotifications(_.concat(notificationObj))
|
|
163
|
+
if (timeout>0) {
|
|
164
|
+
currentTimer = setTimeout(close,timeout)
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
close,
|
|
168
|
+
openWith:(obj,timeout) => {
|
|
169
|
+
clearTimeout(currentTimer);
|
|
170
|
+
if (timeout > 0) {
|
|
171
|
+
currentTimer = setTimeout(close,timeout)
|
|
172
|
+
}
|
|
173
|
+
const notificationObj = formatNotification(obj,{uid,close})
|
|
174
|
+
if (hasClosed) {
|
|
175
|
+
hasClosed = false
|
|
176
|
+
setToastNotifications(_.concat(notificationObj))
|
|
177
|
+
} else {
|
|
178
|
+
setToastNotifications(_.map(n => n.uid === uid ? {...notificationObj, _key: n._key} : n))
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export const useToastNotificationToggle = (notification,timeout) => {
|
|
184
|
+
const toggle = useToggle()
|
|
185
|
+
const extraProps = useMemo(() => notification.content || notification.element ? {} : notification,[])
|
|
186
|
+
const make = (notification,timeout) => notification && createToastNotification(formatNotification(notification,extraProps),timeout,() => toggle.close())
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
if (notification.content || notification.element) {
|
|
189
|
+
toggle.openWith(make(notification))
|
|
190
|
+
}
|
|
191
|
+
},[])
|
|
192
|
+
|
|
193
|
+
return useMemo(() => ({
|
|
194
|
+
close: () => toggle.get()?.close(),
|
|
195
|
+
openWith: (notification,timeout) => toggle.get() ? toggle.get().openWith(formatNotification(notification,extraProps),timeout) : toggle.openWith(make(notification,timeout)),
|
|
196
|
+
open: () => toggle.get() ? toggle.get().openWith(formatNotification(notification,extraProps),timeout) : toggle.openWith(make(notification,timeout))
|
|
197
|
+
}),[toggle.isOpen])
|
|
198
|
+
}
|
|
199
|
+
export const willShowToastNotification = (str) => () => showToastNotification(str)
|
|
200
|
+
|
|
201
|
+
const EXIT_ANIMATION_MS = 300
|
|
202
|
+
const TOAST_EASING = 'cubic-bezier(0.22, 0.61, 0.36, 1)'
|
|
203
|
+
const TOAST_TIMING = `${EXIT_ANIMATION_MS}ms ${TOAST_EASING}`
|
|
204
|
+
|
|
205
|
+
const animation = oneWayAnimation(
|
|
206
|
+
'transform: translateY(0); opacity: 1;',
|
|
207
|
+
'transform: translateY(0.25rem); opacity: 1;',
|
|
208
|
+
'transform: translateY(1rem); opacity: 0;'
|
|
209
|
+
)
|
|
210
|
+
const removeAnimationReinsertPosition = e => e.style.position=''
|
|
211
|
+
export const ToastNotifications = () => {
|
|
212
|
+
const [notifications,setNotifications] = useToastNotifications()
|
|
213
|
+
const containerRef = useRef(null)
|
|
214
|
+
const {lockWidth, unlockWidth} = useNotificationContainerWidthLock(containerRef)
|
|
215
|
+
|
|
216
|
+
return (<Fill name="Notifications">
|
|
217
|
+
<NotificationContainer ref={containerRef} className="JS-on-open-dialog">
|
|
218
|
+
<FlipList animation={animation} timing={TOAST_TIMING} onReinsert={removeAnimationReinsertPosition} skipParentMorph anchoredDownList>
|
|
219
|
+
{
|
|
220
|
+
notifications.map(notification => {
|
|
221
|
+
const closeWithLock = () => {
|
|
222
|
+
lockWidth()
|
|
223
|
+
notification.close()
|
|
224
|
+
setTimeout(unlockWidth, EXIT_ANIMATION_MS)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
callIfFunction(notification.element,closeWithLock) ||
|
|
229
|
+
<ToastBox
|
|
230
|
+
key={`toast-${notification.uid}`}
|
|
231
|
+
type={notification.type}
|
|
232
|
+
onClick={notification.onClick}
|
|
233
|
+
opacity={notification.opacity}
|
|
234
|
+
>
|
|
235
|
+
{notification.title ?
|
|
236
|
+
<NotificationTitleContainer onClick={notification.onClickTitle ? (e) => notification.onClickTitle(e,closeWithLock) : undefined}>
|
|
237
|
+
<NotificationTitle>{notification.title}</NotificationTitle>
|
|
238
|
+
{notification.closeOnX && <CloseIcon onClick={constrainEvent(closeWithLock)}/>}
|
|
239
|
+
</NotificationTitleContainer>
|
|
240
|
+
:
|
|
241
|
+
notification.closeOnX && <CloseIcon onClick={closeWithLock}/>
|
|
242
|
+
}
|
|
243
|
+
<SwitchDiv on={notification._key}>
|
|
244
|
+
<NotificationContent>
|
|
245
|
+
{notification.icon &&
|
|
246
|
+
<NotificationIconWrapper
|
|
247
|
+
isLoading={notification.isLoading}
|
|
248
|
+
iconFontSize={notification.iconFontSize}
|
|
249
|
+
>
|
|
250
|
+
<Icon icon={notification.icon}/>
|
|
251
|
+
{notification.isLoading && <Loading size={36} white absolute/>}
|
|
252
|
+
</NotificationIconWrapper>
|
|
253
|
+
}
|
|
254
|
+
<div css="min-width: 0;">{callIfFunction(notification.content,closeWithLock)}</div>
|
|
255
|
+
</NotificationContent>
|
|
256
|
+
{notification.details &&
|
|
257
|
+
<NotificationDetails onClick={constrainEvent()}>
|
|
258
|
+
{notification.details}
|
|
259
|
+
</NotificationDetails>
|
|
260
|
+
}
|
|
261
|
+
</SwitchDiv>
|
|
262
|
+
</ToastBox>
|
|
263
|
+
)
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
</FlipList>
|
|
267
|
+
</NotificationContainer>
|
|
268
|
+
</Fill>)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const useNotificationContainerWidthLock = (containerRef) => {
|
|
272
|
+
const lockCountRef = useRef(0)
|
|
273
|
+
const prevWidthRef = useRef(null)
|
|
274
|
+
|
|
275
|
+
const lockWidth = () => {
|
|
276
|
+
const el = containerRef.current
|
|
277
|
+
if (!el) return
|
|
278
|
+
if (lockCountRef.current === 0) {
|
|
279
|
+
prevWidthRef.current = el.style.width
|
|
280
|
+
const measured = el.getBoundingClientRect().width
|
|
281
|
+
el.style.width = measured + 'px'
|
|
282
|
+
}
|
|
283
|
+
lockCountRef.current = lockCountRef.current + 1
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const unlockWidth = () => {
|
|
287
|
+
const el = containerRef.current
|
|
288
|
+
if (!el) return
|
|
289
|
+
const next = Math.max(0, lockCountRef.current - 1)
|
|
290
|
+
lockCountRef.current = next
|
|
291
|
+
if (next === 0) {
|
|
292
|
+
el.style.width = prevWidthRef.current || ''
|
|
293
|
+
prevWidthRef.current = null
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return {lockWidth, unlockWidth}
|
|
298
|
+
}
|
package/src/Tooltip.jsx
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import React, {useEffect, useMemo} from 'react'
|
|
2
|
+
import _ from 'lodash/fp'
|
|
3
|
+
import styled, {css} from 'styled-components'
|
|
4
|
+
import {Popup} from './Popup'
|
|
5
|
+
import {useRefState, useToggle} from '@startlibs/core'
|
|
6
|
+
import {customTheme,twoWayAnimation} from '@startlibs/utils'
|
|
7
|
+
|
|
8
|
+
const TooltipContainer = styled.div`
|
|
9
|
+
display: inline-block;
|
|
10
|
+
${props => props.isWrapper ?
|
|
11
|
+
`position:relative;` :
|
|
12
|
+
css`
|
|
13
|
+
position:absolute;
|
|
14
|
+
top:0;
|
|
15
|
+
left:0;
|
|
16
|
+
bottom:0;
|
|
17
|
+
right:0;
|
|
18
|
+
${props => props.isOpen && 'pointer-events: none;'}
|
|
19
|
+
`}
|
|
20
|
+
`
|
|
21
|
+
const TooltipPopup = styled(Popup)`
|
|
22
|
+
font-size: 12px;
|
|
23
|
+
pointer-events: none;
|
|
24
|
+
background-color: rgba(0,0,0,0.6);
|
|
25
|
+
box-shadow: none;
|
|
26
|
+
color: white;
|
|
27
|
+
padding: 0.25rem 1rem;
|
|
28
|
+
border-radius: 100px;
|
|
29
|
+
${customTheme('Tooltip')}
|
|
30
|
+
`
|
|
31
|
+
|
|
32
|
+
const [fadeIn, fadeOut] = twoWayAnimation(`opacity:0;`, `opacity:1;`)
|
|
33
|
+
|
|
34
|
+
const isEllipsis = (event) => event.currentTarget.firstChild && event.currentTarget.firstChild.offsetWidth < event.currentTarget.firstChild.scrollWidth
|
|
35
|
+
|
|
36
|
+
const RECENTLY_CLOSED_TIMEOUT = 200
|
|
37
|
+
let recentlyClosed = false
|
|
38
|
+
const openRef = {current:null}
|
|
39
|
+
|
|
40
|
+
export const Tooltip = ({children, delay = 400, instant, whenEllipsis, content, showOnClick, showOnMount, clickTimeout = 3000, ...rest}) => {
|
|
41
|
+
|
|
42
|
+
const tooltip = useToggle()
|
|
43
|
+
const timeout = useRefState(-1)
|
|
44
|
+
|
|
45
|
+
// showOnMount (ms): abre sozinho ao montar (hint) e some após o tempo dado. O
|
|
46
|
+
// hover continua normal — se o usuário passa o mouse durante a janela, o open
|
|
47
|
+
// cancela o closeAfter (clearClosing) e o tooltip fica até o mouseleave.
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!showOnMount) return
|
|
50
|
+
tooltip.open()
|
|
51
|
+
tooltip.closeAfter(showOnMount)
|
|
52
|
+
// Dispensa o hint na primeira interação do usuário: clique em qualquer lugar
|
|
53
|
+
// (o popup tem pointer-events:none, então todo clique é "fora") ou scroll.
|
|
54
|
+
// O assentamento da página é resize (não scroll), então não dispara isso.
|
|
55
|
+
const dismiss = () => tooltip.close()
|
|
56
|
+
document.addEventListener('click', dismiss, {once: true})
|
|
57
|
+
window.addEventListener('scroll', dismiss, {once: true, passive: true})
|
|
58
|
+
// O conteúdo da página pode assentar depois do mount (imagens/dados async),
|
|
59
|
+
// mudando a posição do âncora — mas o popup fixed só reposiciona em scroll/
|
|
60
|
+
// resize, então ficava travado no lugar antigo. Enquanto o hint está visível,
|
|
61
|
+
// observa o crescimento da página e reposiciona (portalReposition → setPosition).
|
|
62
|
+
const ro = new ResizeObserver(() => window.dispatchEvent(new CustomEvent('portalReposition')))
|
|
63
|
+
ro.observe(document.body)
|
|
64
|
+
const stopRO = setTimeout(() => ro.disconnect(), showOnMount)
|
|
65
|
+
return () => {
|
|
66
|
+
document.removeEventListener('click', dismiss)
|
|
67
|
+
window.removeEventListener('scroll', dismiss)
|
|
68
|
+
ro.disconnect()
|
|
69
|
+
clearTimeout(stopRO)
|
|
70
|
+
}
|
|
71
|
+
}, [])
|
|
72
|
+
|
|
73
|
+
// Modo clique: abre no clique e some sozinho após clickTimeout (closeAfter é
|
|
74
|
+
// à prova de unmount). Não borbulha stopPropagation — quem usa decide isso.
|
|
75
|
+
const openOnClick = () => {
|
|
76
|
+
tooltip.open()
|
|
77
|
+
tooltip.closeAfter(clickTimeout)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Long-press no mobile: hover não existe e "segurar" não dispara mouseenter, então
|
|
81
|
+
// sem isso o tooltip não reaparece no toque. touchstart arma um timer; se segurou
|
|
82
|
+
// além do delay, reusa o openOnClick. Se o press disparou, preventDefault no touchend
|
|
83
|
+
// suprime o clique sintético (segurar espia, não aciona).
|
|
84
|
+
const touchTimer = useRefState(-1)
|
|
85
|
+
const didLongPress = useRefState(false)
|
|
86
|
+
const onTouchStart = () => {
|
|
87
|
+
didLongPress.set(false)
|
|
88
|
+
touchTimer.set(setTimeout(() => {
|
|
89
|
+
didLongPress.set(true)
|
|
90
|
+
openOnClick()
|
|
91
|
+
// Dispensa no scroll em QUALQUER lugar (como o hint do showOnMount): o touchmove do
|
|
92
|
+
// container só pegaria scroll com o dedo ainda no alvo — soltar e rolar não fecharia.
|
|
93
|
+
// tooltip.close é estável (useMemo []) e no-op após unmount; {once} se auto-remove.
|
|
94
|
+
window.addEventListener('scroll', tooltip.close, {once: true, passive: true})
|
|
95
|
+
}, delay))
|
|
96
|
+
}
|
|
97
|
+
const onTouchEnd = (e) => {
|
|
98
|
+
clearTimeout(touchTimer.get())
|
|
99
|
+
if (didLongPress.get()) e.preventDefault()
|
|
100
|
+
}
|
|
101
|
+
// Só cancela o press pendente — não abrir no meio de um scroll iniciado sobre o alvo.
|
|
102
|
+
const onTouchMove = () => clearTimeout(touchTimer.get())
|
|
103
|
+
|
|
104
|
+
// Remove o listener de scroll pendente se desmontar antes de rolar (evita leak da closure).
|
|
105
|
+
useEffect(() => () => window.removeEventListener('scroll', tooltip.close), [])
|
|
106
|
+
|
|
107
|
+
const mouseEnter = (e) => {
|
|
108
|
+
if (!whenEllipsis || (_.isFunction(whenEllipsis) ? whenEllipsis : isEllipsis)(e)) {
|
|
109
|
+
timeout.set(setTimeout(() => {
|
|
110
|
+
|
|
111
|
+
tooltip.open()
|
|
112
|
+
if (recentlyClosed && openRef.current) {
|
|
113
|
+
openRef.current.remove()
|
|
114
|
+
}
|
|
115
|
+
},(instant || recentlyClosed) ? 0 : delay))
|
|
116
|
+
!children && e.currentTarget.parentNode.addEventListener('mouseleave', onMouseLeave)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const onMouseLeave = (e) => {
|
|
121
|
+
clearTimeout(timeout.get())
|
|
122
|
+
if (tooltip.get()) {
|
|
123
|
+
timeout.set(false)
|
|
124
|
+
!children && e.currentTarget.removeEventListener('mouseleave', onMouseLeave)
|
|
125
|
+
const currentlyClosed = recentlyClosed = setTimeout(() => {
|
|
126
|
+
recentlyClosed = recentlyClosed === currentlyClosed ? false : recentlyClosed
|
|
127
|
+
},RECENTLY_CLOSED_TIMEOUT)
|
|
128
|
+
tooltip.close()
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const getAnimation = () => {
|
|
133
|
+
if (recentlyClosed && timeout.get()!==false) {
|
|
134
|
+
return ''
|
|
135
|
+
} else {
|
|
136
|
+
return fadeIn
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const canLongPress = content && !showOnClick
|
|
141
|
+
|
|
142
|
+
return <TooltipContainer isOpen={tooltip.isOpen} isWrapper={!!children}
|
|
143
|
+
onMouseUp={showOnClick ? undefined : onMouseLeave}
|
|
144
|
+
onMouseLeave={(children && !showOnClick) ? onMouseLeave : undefined}
|
|
145
|
+
onMouseEnter={showOnClick ? undefined : mouseEnter}
|
|
146
|
+
onTouchStart={canLongPress ? onTouchStart : undefined}
|
|
147
|
+
onTouchEnd={canLongPress ? onTouchEnd : undefined}
|
|
148
|
+
onTouchMove={canLongPress ? onTouchMove : undefined}
|
|
149
|
+
onClick={showOnClick ? openOnClick : undefined}>
|
|
150
|
+
{
|
|
151
|
+
tooltip.isOpen && content &&
|
|
152
|
+
<TooltipPopup animation={getAnimation} animationOptions={{ref:openRef}} top={!(rest.bottom || rest.left || rest.right || rest.at || rest.my)} {...rest}>
|
|
153
|
+
{content}
|
|
154
|
+
</TooltipPopup>
|
|
155
|
+
}
|
|
156
|
+
{children}
|
|
157
|
+
</TooltipContainer>
|
|
158
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React, {useRef} from 'react'
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
import {FlipList} from './FlipList'
|
|
4
|
+
import {oneWayAnimation} from '@startlibs/utils'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const DEFAULT_ANIMATION = oneWayAnimation(`opacity: 1;`, `opacity: 0;`)
|
|
8
|
+
|
|
9
|
+
export const AnimationBlockContainer = styled.div`
|
|
10
|
+
${props => props.inline ? 'display: inline-block; ' : ''}
|
|
11
|
+
position:relative;
|
|
12
|
+
`
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
export const TransitionDiv = ({children,animation, skipChromeFix, className, block, inline,...rest}) => {
|
|
16
|
+
|
|
17
|
+
const blockRef = useRef()
|
|
18
|
+
|
|
19
|
+
const overflowEffect = () => {
|
|
20
|
+
if (blockRef.current) {
|
|
21
|
+
const prevOverflow = blockRef.current.style.overflow
|
|
22
|
+
blockRef.current.style.overflow = 'hidden'
|
|
23
|
+
return () => {
|
|
24
|
+
blockRef.current.style.overflow = prevOverflow
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const content = children
|
|
30
|
+
? <FlipList smallJumpFix={inline && -3} effect={overflowEffect} skipChromeFix={skipChromeFix} animation={animation || DEFAULT_ANIMATION} {...rest}>{[<div className={(block || inline) ? "" : className} key="first">{children}</div>]}</FlipList>
|
|
31
|
+
: <FlipList smallJumpFix={inline && -3} effect={overflowEffect} skipChromeFix={skipChromeFix} animation={animation || DEFAULT_ANIMATION} {...rest}>{[]}</FlipList>
|
|
32
|
+
|
|
33
|
+
return (block || inline) ? <AnimationBlockContainer inline={inline} ref={blockRef} className={className}>{content}</AnimationBlockContainer> : content
|
|
34
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {willUseSharedState} from '@startlibs/core'
|
|
2
|
+
import {Button} from './Button'
|
|
3
|
+
import {Dialog} from './Dialog'
|
|
4
|
+
import {Icon} from './Icon'
|
|
5
|
+
import {useDialogWithToggle} from './useDialogWithToggle'
|
|
6
|
+
import { getColor, callIfFunction } from '@startlibs/utils';
|
|
7
|
+
import React from 'react'
|
|
8
|
+
import _ from 'lodash/fp'
|
|
9
|
+
import styled from 'styled-components';
|
|
10
|
+
|
|
11
|
+
export const WarningDialog = styled(({content, footer, withX ,closeDialog,leftButton,additionalContent,title,rightButton,className, color = "alert", icon = "warning",confirmLabel = "Entendi",noActions,children}) => {
|
|
12
|
+
return <Dialog className={className} closeDialog={withX && closeDialog}>
|
|
13
|
+
{_.isString(icon) ? <WarningDialogIcon icon={icon} color={color}/> : icon}
|
|
14
|
+
{title && <WarningDialogTitle color={color}>{title}</WarningDialogTitle>}
|
|
15
|
+
{content && <Content noTitle={!title} noActions={noActions}>{content}</Content>}
|
|
16
|
+
{additionalContent}
|
|
17
|
+
{callIfFunction(leftButton,closeDialog)}
|
|
18
|
+
{confirmLabel !== false && <Button highlight onClick={closeDialog}>{confirmLabel}</Button>}
|
|
19
|
+
{callIfFunction(rightButton,closeDialog)}
|
|
20
|
+
{callIfFunction(footer,closeDialog)}
|
|
21
|
+
{children}
|
|
22
|
+
</Dialog>
|
|
23
|
+
})`
|
|
24
|
+
text-align: center;
|
|
25
|
+
max-width: 36rem;
|
|
26
|
+
padding: 2.5rem;
|
|
27
|
+
${Button} {
|
|
28
|
+
margin: 0.5rem !important;
|
|
29
|
+
}
|
|
30
|
+
`
|
|
31
|
+
export const WarningDialogTitle = styled.h3 `
|
|
32
|
+
color: ${props => getColor(props.color)(props)};
|
|
33
|
+
font-weight: 500;
|
|
34
|
+
font-size: 20px;
|
|
35
|
+
`
|
|
36
|
+
const Content = styled.div `
|
|
37
|
+
font-size: 15px;
|
|
38
|
+
max-width: 27rem;
|
|
39
|
+
margin: 0 auto;
|
|
40
|
+
line-height: 130%;
|
|
41
|
+
${props => !props.noActions && `
|
|
42
|
+
margin: 0 auto 1.5rem;
|
|
43
|
+
`}
|
|
44
|
+
${props => props.noTitle &&`
|
|
45
|
+
font-weight: 500;
|
|
46
|
+
font-size: 16px;
|
|
47
|
+
`}
|
|
48
|
+
`
|
|
49
|
+
export const WarningDialogIcon = styled(Icon)`
|
|
50
|
+
color: ${props => getColor(props.color)(props)};;
|
|
51
|
+
font-size: 46px;
|
|
52
|
+
display: block;
|
|
53
|
+
margin-bottom: 1.25rem;
|
|
54
|
+
`
|
|
55
|
+
|
|
56
|
+
export const useWarningDialog = () => useDialogWithToggle((toggle) =>
|
|
57
|
+
<WarningDialog {...toggle.isOpen} closeDialog={toggle.close} />
|
|
58
|
+
|
|
59
|
+
)
|
|
60
|
+
const [useRenderDefaultWarningDialog,,_setWarningDialog] = willUseSharedState()
|
|
61
|
+
|
|
62
|
+
export const setWarningDialog = ({as: Dialog = WarningDialog,...props}) => _setWarningDialog(<Dialog {...props} closeDialog={() => _setWarningDialog(null)}/>)
|
|
63
|
+
|
|
64
|
+
export const DefaultWarningDialog = () => {
|
|
65
|
+
const [warningDialog] = useRenderDefaultWarningDialog()
|
|
66
|
+
if (warningDialog) {
|
|
67
|
+
return warningDialog
|
|
68
|
+
}
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {useToggle} from '@startlibs/core'
|
|
3
|
+
import {callIfFunction} from '@startlibs/utils'
|
|
4
|
+
import {Button} from '../../Button'
|
|
5
|
+
|
|
6
|
+
export const OnClickLoadingButton = ({onClick,children,onSuccess, as = Button, onFailure, className,...props}) => {
|
|
7
|
+
const loading = useToggle()
|
|
8
|
+
const Element = as
|
|
9
|
+
return <Element className={className} isLoading={loading.isOpen} {...props} onClick={(e) => loading.wait(onClick(e).then(async (e) => {
|
|
10
|
+
await callIfFunction(onSuccess,e)
|
|
11
|
+
return e
|
|
12
|
+
},async (e) => {
|
|
13
|
+
await callIfFunction(onFailure,e)
|
|
14
|
+
throw e
|
|
15
|
+
}))}>{children}</Element>
|
|
16
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
import {media, getColor} from '@startlibs/utils'
|
|
4
|
+
import {CloseIcon, Dialog} from '../../Dialog'
|
|
5
|
+
|
|
6
|
+
export const DialogWithImageHeader = styled(Dialog)`
|
|
7
|
+
max-width: 51rem;
|
|
8
|
+
|
|
9
|
+
${CloseIcon} {
|
|
10
|
+
background: ${getColor('gray240')};
|
|
11
|
+
text-shadow: none;
|
|
12
|
+
color: rgba(120,120,120,1);
|
|
13
|
+
font-size: 20px;
|
|
14
|
+
box-shadow: rgba(0,0,0,0.2) 0px 1px 3px 0px, rgba(0,0,0,0.05) 0px 0px 0px 1px;
|
|
15
|
+
width: 36px;
|
|
16
|
+
height: 36px;
|
|
17
|
+
line-height: 36px;
|
|
18
|
+
transition: 0.1s ease-out;
|
|
19
|
+
:hover {
|
|
20
|
+
background-color: rgb(230,230,230);
|
|
21
|
+
}
|
|
22
|
+
:active {
|
|
23
|
+
background-color: rgb(220,220,220);
|
|
24
|
+
transition: 0s ease-out;
|
|
25
|
+
}
|
|
26
|
+
@supports (position: sticky) {
|
|
27
|
+
position: sticky;
|
|
28
|
+
top: -2.5rem;
|
|
29
|
+
right: 0rem;
|
|
30
|
+
float: right;
|
|
31
|
+
margin-right: -1.25rem;
|
|
32
|
+
margin-top: 0.75rem;
|
|
33
|
+
}
|
|
34
|
+
${media.mobile`
|
|
35
|
+
@supports (position: sticky) {
|
|
36
|
+
top: 0;
|
|
37
|
+
margin-right: -1rem;
|
|
38
|
+
}
|
|
39
|
+
`}
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
`
|