@startlibs/components 1.0.4 → 1.1.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.
Files changed (115) hide show
  1. package/lib/BasicBulletList.mjs +9 -0
  2. package/lib/BasicBulletList.mjs.map +1 -0
  3. package/lib/Button.mjs +95 -0
  4. package/lib/Button.mjs.map +1 -0
  5. package/lib/ContextMenu.mjs +452 -0
  6. package/lib/ContextMenu.mjs.map +1 -0
  7. package/lib/Dialog.mjs +232 -0
  8. package/lib/Dialog.mjs.map +1 -0
  9. package/lib/Draggable.mjs +880 -0
  10. package/lib/Draggable.mjs.map +1 -0
  11. package/lib/ErrorBoundary.mjs +108 -0
  12. package/lib/ErrorBoundary.mjs.map +1 -0
  13. package/lib/FillLastLineResizer.mjs +238 -0
  14. package/lib/FillLastLineResizer.mjs.map +1 -0
  15. package/lib/Flip.mjs +604 -0
  16. package/lib/Flip.mjs.map +1 -0
  17. package/lib/FlipList.mjs +186 -0
  18. package/lib/FlipList.mjs.map +1 -0
  19. package/lib/FormatDate.mjs +25 -0
  20. package/lib/FormatDate.mjs.map +1 -0
  21. package/lib/Icon.mjs +50 -0
  22. package/lib/Icon.mjs.map +1 -0
  23. package/lib/Image.mjs +41 -0
  24. package/lib/Image.mjs.map +1 -0
  25. package/lib/Link.mjs +116 -0
  26. package/lib/Link.mjs.map +1 -0
  27. package/lib/Loading.mjs +10 -0
  28. package/lib/Loading.mjs.map +1 -0
  29. package/lib/LoadingDiv.mjs +83 -0
  30. package/lib/LoadingDiv.mjs.map +1 -0
  31. package/lib/Notifications.mjs +63 -0
  32. package/lib/Notifications.mjs.map +1 -0
  33. package/lib/PageLoader.mjs +42 -0
  34. package/lib/PageLoader.mjs.map +1 -0
  35. package/lib/PersistentNotifications.mjs +130 -0
  36. package/lib/PersistentNotifications.mjs.map +1 -0
  37. package/lib/Popup.mjs +49 -0
  38. package/lib/Popup.mjs.map +1 -0
  39. package/lib/SplitColumnsContainer.mjs +43 -0
  40. package/lib/SplitColumnsContainer.mjs.map +1 -0
  41. package/lib/SwitchDiv.mjs +61 -0
  42. package/lib/SwitchDiv.mjs.map +1 -0
  43. package/lib/TextButton.mjs +41 -0
  44. package/lib/TextButton.mjs.map +1 -0
  45. package/lib/ToastNotifications.mjs +215 -0
  46. package/lib/ToastNotifications.mjs.map +1 -0
  47. package/lib/Tooltip.mjs +160 -0
  48. package/lib/Tooltip.mjs.map +1 -0
  49. package/lib/TransitionDiv.mjs +56 -0
  50. package/lib/TransitionDiv.mjs.map +1 -0
  51. package/lib/WarningDialog.mjs +84 -0
  52. package/lib/WarningDialog.mjs.map +1 -0
  53. package/lib/alt/buttons/OnClickLoadingButton.mjs +36 -0
  54. package/lib/alt/buttons/OnClickLoadingButton.mjs.map +1 -0
  55. package/lib/alt/dialogs/DialogWithImageHeader.mjs +14 -0
  56. package/lib/alt/dialogs/DialogWithImageHeader.mjs.map +1 -0
  57. package/lib/alt/dialogs/DialogWithSidebar.mjs +106 -0
  58. package/lib/alt/dialogs/DialogWithSidebar.mjs.map +1 -0
  59. package/lib/alt/dialogs/MobileBubbleDialog.mjs +49 -0
  60. package/lib/alt/dialogs/MobileBubbleDialog.mjs.map +1 -0
  61. package/lib/containers/DialogContainer.mjs +413 -0
  62. package/lib/containers/DialogContainer.mjs.map +1 -0
  63. package/lib/containers/Reposition.mjs +35 -0
  64. package/lib/containers/Reposition.mjs.map +1 -0
  65. package/lib/containers/usePositioning.mjs +294 -0
  66. package/lib/containers/usePositioning.mjs.map +1 -0
  67. package/lib/index.cjs +5000 -0
  68. package/lib/index.cjs.map +1 -0
  69. package/lib/index.mjs +37 -0
  70. package/lib/index.mjs.map +1 -0
  71. package/lib/useDialogWithToggle.mjs +18 -0
  72. package/lib/useDialogWithToggle.mjs.map +1 -0
  73. package/lib/willUseIsSticky.mjs +123 -0
  74. package/lib/willUseIsSticky.mjs.map +1 -0
  75. package/package.json +44 -29
  76. package/src/BasicBulletList.jsx +21 -0
  77. package/src/Button.jsx +288 -0
  78. package/src/ContextMenu.jsx +621 -0
  79. package/src/Dialog.jsx +413 -0
  80. package/src/Draggable.jsx +801 -0
  81. package/src/ErrorBoundary.jsx +75 -0
  82. package/src/FillLastLineResizer.jsx +226 -0
  83. package/src/Flip.jsx +560 -0
  84. package/src/FlipList.jsx +136 -0
  85. package/src/FormatDate.jsx +9 -0
  86. package/src/Icon.jsx +49 -0
  87. package/src/Image.jsx +23 -0
  88. package/src/Link.jsx +68 -0
  89. package/src/Loading.jsx +29 -0
  90. package/src/LoadingDiv.jsx +75 -0
  91. package/src/Notifications.jsx +89 -0
  92. package/src/PageLoader.jsx +48 -0
  93. package/src/PersistentNotifications.jsx +182 -0
  94. package/src/Popup.jsx +49 -0
  95. package/src/SplitColumnsContainer.jsx +63 -0
  96. package/src/SwitchDiv.jsx +30 -0
  97. package/src/TextButton.jsx +34 -0
  98. package/src/ToastNotifications.jsx +298 -0
  99. package/src/Tooltip.jsx +158 -0
  100. package/src/TransitionDiv.jsx +34 -0
  101. package/src/WarningDialog.jsx +70 -0
  102. package/src/alt/buttons/OnClickLoadingButton.jsx +16 -0
  103. package/src/alt/dialogs/DialogWithImageHeader.jsx +42 -0
  104. package/src/alt/dialogs/DialogWithSidebar.jsx +135 -0
  105. package/src/alt/dialogs/MobileBubbleDialog.jsx +56 -0
  106. package/src/containers/DialogContainer.jsx +423 -0
  107. package/src/containers/Reposition.jsx +24 -0
  108. package/src/containers/usePositioning.jsx +305 -0
  109. package/src/index.js +35 -0
  110. package/src/useDialogWithToggle.jsx +12 -0
  111. package/src/willUseIsSticky.jsx +113 -0
  112. package/.babelrc +0 -27
  113. package/lib/index.js +0 -5429
  114. package/rollup.config.js +0 -52
  115. 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
+ }
@@ -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
+ `