@startlibs/components 1.0.3 → 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.
- 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 +44 -29
- 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 -5196
- package/rollup.config.js +0 -52
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import React, {useEffect, useLayoutEffect, useRef, useState} from 'react'
|
|
2
|
+
import _ from 'lodash/fp'
|
|
3
|
+
import styled from 'styled-components'
|
|
4
|
+
import {getClosestOffsetScroll} from '@startlibs/utils'
|
|
5
|
+
import {useEnsureRef} from '@startlibs/core'
|
|
6
|
+
import {getDialogLayersSize} from './DialogContainer'
|
|
7
|
+
|
|
8
|
+
const Placeholder = styled.span`
|
|
9
|
+
top:0;
|
|
10
|
+
left:0;
|
|
11
|
+
padding: 0 !important;
|
|
12
|
+
position: absolute;
|
|
13
|
+
`
|
|
14
|
+
/*
|
|
15
|
+
type Alignment = 'top left' | 'top center' | 'top right' |
|
|
16
|
+
'center left' | 'center center' | 'center right' |
|
|
17
|
+
'bottom left' | 'bottom center' | 'bottom right'
|
|
18
|
+
|
|
19
|
+
type Collision = 'fit fit' | 'fit flip' | 'fit fitflip' | 'fit none' |
|
|
20
|
+
'flip fit' | 'flip flip' | 'flip fitflip' | 'flip none' |
|
|
21
|
+
'flipfit fit' | 'flipfit flip' | 'flipfit fitflip' | 'flipfit none' |
|
|
22
|
+
'none fit' | 'none flip' | 'none fitflip' | 'none none'
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const HORIZONTAL_MAPPING = ['left', 'center', 'right']
|
|
26
|
+
const VERTICAL_MAPPING = ['top', 'center', 'bottom']
|
|
27
|
+
const ARROW_KEY_POSITIONS = {left: '0', right: '-0', top: '0', bottom: '-0'}
|
|
28
|
+
|
|
29
|
+
const POSITIONING_BOUNDS = {left:0,top:0,right:0,bottom:0}
|
|
30
|
+
export const addPositioningBound = (key,value) => {
|
|
31
|
+
POSITIONING_BOUNDS[key] += value
|
|
32
|
+
return () => POSITIONING_BOUNDS[key] -= value
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getPosition(origin, offset, max, originSize, selfSize, originalAlignment, selfAlignment, onCollision, mapping, boundMargin) {
|
|
36
|
+
|
|
37
|
+
// 0 pra left ou top, 1/2 pra center, 1 pra right ou bottom
|
|
38
|
+
const originDiff = originSize * (mapping.indexOf(originalAlignment) / 2)
|
|
39
|
+
// 0 pra left ou top, -1/2 pra center, -1 pra right ou bottom
|
|
40
|
+
const selfDiff = -(selfSize * (mapping.indexOf(selfAlignment) / 2))
|
|
41
|
+
|
|
42
|
+
const respectingBounds = boundMargin !== false
|
|
43
|
+
const upperBound = respectingBounds ? (POSITIONING_BOUNDS[mapping[0]] + boundMargin) : Number.MIN_SAFE_INTEGER
|
|
44
|
+
const lowerBound = respectingBounds ? (POSITIONING_BOUNDS[mapping[2]] + boundMargin) : 0
|
|
45
|
+
const newPosition = Math.max(upperBound, origin + selfDiff + originDiff + offset)
|
|
46
|
+
|
|
47
|
+
const collision = Math.max(-newPosition, newPosition + selfSize - max + lowerBound)
|
|
48
|
+
if (collision > 0 && onCollision !== 'none') {
|
|
49
|
+
if (onCollision.indexOf('flip') >= 0) {
|
|
50
|
+
const flippedPosition = getPosition(origin, -offset, max, originSize, selfSize, originalAlignment, selfAlignment, onCollision.indexOf('fit') >= 0 ? 'fit' : 'none', mapping.slice().reverse(), boundMargin)
|
|
51
|
+
const flippedCollision = Math.max(-flippedPosition[0], flippedPosition[0] + selfSize - max)
|
|
52
|
+
if ((collision > 0 && flippedCollision > 0) || flippedPosition[0] < upperBound) {
|
|
53
|
+
return getPosition(origin, offset, max, originSize, selfSize, originalAlignment, selfAlignment, 'fit', mapping, boundMargin)
|
|
54
|
+
}
|
|
55
|
+
return collision < flippedCollision ? [newPosition, 'none'] : [flippedPosition[0], flippedPosition[1] === 'fit' ? 'flipfit' : 'flip']
|
|
56
|
+
} else {
|
|
57
|
+
const minBound = respectingBounds ? upperBound : 0
|
|
58
|
+
const fittedPosition = Math.max(minBound, Math.min(newPosition, max - selfSize - lowerBound))
|
|
59
|
+
return [fittedPosition, fittedPosition !== newPosition ? 'fit' : 'none']
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
return [newPosition, 'none']
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const arrowShadow = {
|
|
67
|
+
top: '-1px -1px 2px -1px rgba(0,0,0,.25)',
|
|
68
|
+
right: '1px -1px 2px -1px rgba(0,0,0,.25)',
|
|
69
|
+
bottom: '1px 1px 2px -1px rgba(0,0,0,.25)',
|
|
70
|
+
left: '-1px 1px 2px -1px rgba(0,0,0,.25)'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const translate = (position, offset) => (position === 'left' ? '-50%' : position === 'right' ? '50%' : offset) + ',' +
|
|
74
|
+
(position === 'top' ? '-50%' : position === 'bottom' ? '50%' : offset)
|
|
75
|
+
|
|
76
|
+
const ARROW_DEFAULT_SIZE = 14
|
|
77
|
+
|
|
78
|
+
export const DefaultArrow = styled.div`
|
|
79
|
+
background-color: #fff;
|
|
80
|
+
position: absolute;
|
|
81
|
+
width: ${ARROW_DEFAULT_SIZE}px;
|
|
82
|
+
height: ${ARROW_DEFAULT_SIZE}px;
|
|
83
|
+
transform: translate(-50%,50%) rotate(45deg);
|
|
84
|
+
bottom: 0;
|
|
85
|
+
left: 50%;
|
|
86
|
+
box-shadow: ${arrowShadow.bottom};
|
|
87
|
+
`
|
|
88
|
+
|
|
89
|
+
export const usePositioning = (
|
|
90
|
+
element,
|
|
91
|
+
{
|
|
92
|
+
my = 'top right',
|
|
93
|
+
offset = '0 0', // x y
|
|
94
|
+
at = 'bottom right',
|
|
95
|
+
onCollision = 'flip flip',
|
|
96
|
+
arrow,
|
|
97
|
+
arrowPadding = 10,
|
|
98
|
+
fill,
|
|
99
|
+
useLayers = true,
|
|
100
|
+
fixed,
|
|
101
|
+
bottom: onBottom,
|
|
102
|
+
left: onLeft,
|
|
103
|
+
right: onRight,
|
|
104
|
+
top: onTop,
|
|
105
|
+
keepWidth = true,
|
|
106
|
+
boundMargin = 6,
|
|
107
|
+
respectBounds,
|
|
108
|
+
respectOffsetScroll,
|
|
109
|
+
on,
|
|
110
|
+
placeholderAs
|
|
111
|
+
},
|
|
112
|
+
arrowRefOpt
|
|
113
|
+
) => {
|
|
114
|
+
const placeholder = useRef()
|
|
115
|
+
const arrowRef = useEnsureRef(arrowRefOpt)
|
|
116
|
+
const [positionInfo, setPositionInfo] = useState()
|
|
117
|
+
|
|
118
|
+
const setPosition = (retry=0) => {
|
|
119
|
+
if (retry>10) { return }
|
|
120
|
+
if (!element.current?.style || !(placeholder.current?.offsetParent || placeholder.current?.parentNode?.offsetParent)) {
|
|
121
|
+
setTimeout(()=>setPosition(retry+1),100)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
if (keepWidth) {
|
|
125
|
+
element.current.style.width = ''
|
|
126
|
+
} else {
|
|
127
|
+
element.current.style.maxWidth = ''
|
|
128
|
+
}
|
|
129
|
+
const placeholderOffsetParent = (placeholder.current?.offsetParent || placeholder.current?.parentNode?.offsetParent)
|
|
130
|
+
const usingLayer = respectOffsetScroll || useLayers && getDialogLayersSize() > 0
|
|
131
|
+
const scrollOwner =
|
|
132
|
+
usingLayer
|
|
133
|
+
? (_.isFunction(respectOffsetScroll) ? respectOffsetScroll(element.current.offsetParent) : getClosestOffsetScroll(element.current.offsetParent))
|
|
134
|
+
: {scrollTop: window.pageYOffset, scrollLeft: window.pageXOffset}
|
|
135
|
+
|
|
136
|
+
// console.log({scrollOwner,scrollTop:scrollOwner.scrollTop,pageYOffset:window.pageYOffset})
|
|
137
|
+
|
|
138
|
+
const scrollY = !isNaN(scrollOwner.scrollTop) ? scrollOwner.scrollTop : window.pageYOffset
|
|
139
|
+
const scrollX = !isNaN(scrollOwner.scrollLeft) ? scrollOwner.scrollLeft : window.pageXOffset
|
|
140
|
+
const getRespectOffsetScrollTopLeft = () => {
|
|
141
|
+
const {top,left} = placeholder.current.getBoundingClientRect()
|
|
142
|
+
const {top:scrollOwnerTop, left: scrollOwnerLeft} = scrollOwner.getBoundingClientRect()
|
|
143
|
+
return {top: top-scrollOwnerTop,left: left-scrollOwnerLeft}
|
|
144
|
+
}
|
|
145
|
+
const {left: leftPlusBorder, top: topPlusBorder} =
|
|
146
|
+
respectOffsetScroll
|
|
147
|
+
? getRespectOffsetScrollTopLeft()
|
|
148
|
+
:placeholder.current.getBoundingClientRect()
|
|
149
|
+
|
|
150
|
+
const {borderLeftWidth, borderTopWidth} = window.getComputedStyle(placeholderOffsetParent)
|
|
151
|
+
const left = leftPlusBorder - parseInt(borderLeftWidth, 10)
|
|
152
|
+
const top = topPlusBorder - parseInt(borderTopWidth, 10)
|
|
153
|
+
const {offsetWidth: parentOffsetWidth, offsetHeight: parentOffsetHeight} = (placeholderOffsetParent)
|
|
154
|
+
const {offsetWidth: popupOffsetWidth, offsetHeight: popupOffsetHeight} = element.current
|
|
155
|
+
const {width:popupCurrentWidth} = element.current.getBoundingClientRect()
|
|
156
|
+
const [offsetX, offsetY] = [].concat(offset).join(' ').split(' ').map(_ => Number(_ || 0))
|
|
157
|
+
const simplePositioning = [onTop, onRight, onBottom, onLeft].map(_ => _ === true ? 'center' : _)
|
|
158
|
+
const simplePositioningIndex = simplePositioning.findIndex(_ => _)
|
|
159
|
+
const bodyWidth = document.body.offsetWidth - (usingLayer ? parseInt(getComputedStyle(document.body).paddingRight, 10) : 0)
|
|
160
|
+
// Barra fixa inferior (footer mobile) registrada em POSITIONING_BOUNDS conta como
|
|
161
|
+
// borda da tela: sem isso o popup se posiciona atrás dela. Com respectBounds o
|
|
162
|
+
// desconto já vem do lowerBound em getPosition, não descontar duas vezes.
|
|
163
|
+
const windowHeight = window.innerHeight - (respectBounds ? 0 : POSITIONING_BOUNDS.bottom)
|
|
164
|
+
|
|
165
|
+
const [verticalCollision, horizontalCollision] = onCollision.split(' ')
|
|
166
|
+
const [verticalMy, horizontalMy] = [my, 'bottom $', '$ left', 'top $', '$ right'][simplePositioningIndex + 1].replace('$', simplePositioning[simplePositioningIndex]).split(' ')
|
|
167
|
+
const [verticalAt, horizontalAt] = [at, 'top $', '$ right', 'bottom $', '$ left'][simplePositioningIndex + 1].replace('$', simplePositioning[simplePositioningIndex]).split(' ')
|
|
168
|
+
|
|
169
|
+
const [processedLeft, collisionH] = getPosition(left, offsetX, bodyWidth, parentOffsetWidth, fill ? parentOffsetWidth : popupOffsetWidth, horizontalAt, horizontalMy, horizontalCollision, HORIZONTAL_MAPPING,!!respectBounds && boundMargin)
|
|
170
|
+
const [processedTop, collisionV] = getPosition(top, offsetY, windowHeight, parentOffsetHeight, popupOffsetHeight, verticalAt, verticalMy, verticalCollision, VERTICAL_MAPPING,!!respectBounds && boundMargin)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
const flippedV = collisionV.indexOf('flip') >= 0
|
|
174
|
+
const flippedH = collisionH.indexOf('flip') >= 0
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
//arrow
|
|
178
|
+
const arrowSide = Math.abs(VERTICAL_MAPPING.indexOf(verticalMy) - VERTICAL_MAPPING.indexOf(verticalAt)) === 2 ? (flippedV ? verticalAt : verticalMy) :
|
|
179
|
+
Math.abs(HORIZONTAL_MAPPING.indexOf(horizontalMy) - HORIZONTAL_MAPPING.indexOf(horizontalAt)) === 2 ? (flippedH ? horizontalAt : horizontalMy) : ''
|
|
180
|
+
const arrowElement = arrowRef.current
|
|
181
|
+
const arrowOnVerticalSide = arrowSide === 'left' || arrowSide === 'right'
|
|
182
|
+
const arrowSize = arrowElement ? arrowElement.getBoundingClientRect()[arrowOnVerticalSide ? 'height' : 'width'] : (ARROW_DEFAULT_SIZE * Math.sqrt(2))
|
|
183
|
+
const [arrowOffset, popupArrowOffset] = arrowOnVerticalSide ?
|
|
184
|
+
processArrow(ARROW_KEY_POSITIONS[arrow] || arrow, arrowSize, arrowOnVerticalSide, parentOffsetHeight, popupOffsetHeight, processedTop - top, flippedV) :
|
|
185
|
+
processArrow(ARROW_KEY_POSITIONS[arrow] || arrow, arrowSize, arrowOnVerticalSide, parentOffsetWidth, popupOffsetWidth, processedLeft - left, flippedV)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
const maxHeight = window.innerHeight - (POSITIONING_BOUNDS.top + POSITIONING_BOUNDS.bottom) - (respectBounds ? boundMargin*2 : 0)
|
|
189
|
+
const maxWidth = window.innerWidth - (POSITIONING_BOUNDS.left + POSITIONING_BOUNDS.right) - (respectBounds ? boundMargin*2 : 0)
|
|
190
|
+
|
|
191
|
+
element.current.style.maxHeight = ''
|
|
192
|
+
if (parseInt(window.getComputedStyle(element.current).maxHeight) > maxHeight) {
|
|
193
|
+
element.current.style.maxHeight = maxHeight+'px'
|
|
194
|
+
}
|
|
195
|
+
if (keepWidth) {
|
|
196
|
+
element.current.style.width = Math.ceil(popupCurrentWidth)+"px"
|
|
197
|
+
} else {
|
|
198
|
+
element.current.style.maxWidth = maxWidth+'px'
|
|
199
|
+
}
|
|
200
|
+
// console.log({processedTop,top,offsetY,scrollY})
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
element.current.style.top = (processedTop + (arrowOnVerticalSide ? popupArrowOffset : 0) + (fixed ? 0 : scrollY)) + 'px'
|
|
204
|
+
element.current.style.left = (processedLeft + (arrowOnVerticalSide ? 0 : popupArrowOffset) + (fixed ? 0 : scrollX)) + 'px'
|
|
205
|
+
|
|
206
|
+
const arrowStyle = {
|
|
207
|
+
transform: `translate(${translate(arrowSide, arrowOffset[0] === 'top' || arrowOffset[0] === 'left' ? '-50%' : '50%')}) rotate(45deg)`,
|
|
208
|
+
bottom:"auto",
|
|
209
|
+
left:"auto",
|
|
210
|
+
[arrowSide]: '0',
|
|
211
|
+
boxShadow: arrowShadow[arrowSide],
|
|
212
|
+
[arrowOffset[0]]: arrowOffset[1]+'px'
|
|
213
|
+
}
|
|
214
|
+
if (arrowRef.current) {
|
|
215
|
+
Object.assign(arrowRef.current.style,arrowStyle)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
setPositionInfo({
|
|
219
|
+
atTop: (verticalAt === 'top' && !flippedV) || (verticalAt === 'bottom' && flippedV),
|
|
220
|
+
atBottom: (verticalAt === 'bottom' && !flippedV) || (verticalAt === 'top' && flippedV),
|
|
221
|
+
atLeft: (horizontalAt === 'left' && !flippedH) || (verticalAt === 'right' && flippedH),
|
|
222
|
+
atRight: (horizontalAt === 'right' && !flippedH) || (verticalAt === 'left' && flippedH),
|
|
223
|
+
fullHeight: popupOffsetHeight > maxHeight,
|
|
224
|
+
fullWidth: popupOffsetWidth > maxWidth,
|
|
225
|
+
arrowSide,
|
|
226
|
+
arrowOffset,
|
|
227
|
+
arrowStyle
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
if (fill) {
|
|
231
|
+
element.current.style.width = parentOffsetWidth + 'px'
|
|
232
|
+
}
|
|
233
|
+
if (fixed) {
|
|
234
|
+
element.current.style.position = 'fixed'
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
useLayoutEffect(setPosition, [on])
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
window.addEventListener('portalReposition', setPosition)
|
|
243
|
+
window.addEventListener('stepResize', setPosition)
|
|
244
|
+
window.addEventListener('scroll', setPosition, {passive:true})
|
|
245
|
+
return () => {
|
|
246
|
+
window.removeEventListener('portalReposition', setPosition)
|
|
247
|
+
window.removeEventListener('stepResize', setPosition)
|
|
248
|
+
window.removeEventListener('scroll', setPosition)
|
|
249
|
+
}
|
|
250
|
+
}, []
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
const processArrow = (arrow, arrowSize, onVerticalSide, arrowContainerWidth, popupSize, deltaPopupPosition, flipped) => {
|
|
255
|
+
if (!arrow) {
|
|
256
|
+
return ['', 0]
|
|
257
|
+
}
|
|
258
|
+
const margin = arrowPadding + (arrowSize / 2)
|
|
259
|
+
const [invertDirection, percent, px] = (arrow === true ? '50%' : arrow).replace(/(-)?(\d+%)?\s?(-?\d+)?/, '$1:$2:$3').split(':')
|
|
260
|
+
const invertAndFlipDirection = flipped ? !invertDirection : invertDirection
|
|
261
|
+
const arrowPosition = onVerticalSide ? (invertAndFlipDirection ? 'bottom' : 'top') : (invertAndFlipDirection ? 'right' : 'left')
|
|
262
|
+
const fromOriginCorner = arrowPosition === 'left' || arrowPosition === 'top'
|
|
263
|
+
|
|
264
|
+
const popupOffset = fromOriginCorner ? deltaPopupPosition : (arrowContainerWidth - deltaPopupPosition - popupSize)
|
|
265
|
+
|
|
266
|
+
/* ____________________________
|
|
267
|
+
| arrow container |
|
|
268
|
+
|___________________________|
|
|
269
|
+
..../\_______
|
|
270
|
+
| popup |
|
|
271
|
+
|____________|
|
|
272
|
+
|
|
273
|
+
|-------| = delta popup position (popupOffset)
|
|
274
|
+
|
|
275
|
+
.... = arrowPadding prop, applies to both sides, moves popup or does nothing
|
|
276
|
+
|
|
277
|
+
arrow prop = invert percent px // relative to arrow container
|
|
278
|
+
invert = -?
|
|
279
|
+
percent = (\d+%)?
|
|
280
|
+
px = (-?\d+)?
|
|
281
|
+
|
|
282
|
+
procedure:
|
|
283
|
+
1- position arrow according to container - arrowInitialPos
|
|
284
|
+
2- constrain position inside container - constrainedArrowPosition
|
|
285
|
+
3- move popup according to arrowPadding (margin) - popupDelta
|
|
286
|
+
4- final arrow position, relative to final popup position - finalArrowPosition
|
|
287
|
+
*/
|
|
288
|
+
|
|
289
|
+
const arrowInitialPos = (parseInt(percent || 0, 10) * arrowContainerWidth / 100) + parseInt(px || 0, 10)
|
|
290
|
+
const constrainedArrowPosition = (Math.min(Math.max(arrowInitialPos, 0), arrowContainerWidth))
|
|
291
|
+
const arrowPositionRelativeToPopup = fromOriginCorner ? constrainedArrowPosition - popupOffset : (arrowContainerWidth - popupOffset - constrainedArrowPosition)
|
|
292
|
+
const popupDelta = arrowPositionRelativeToPopup < margin ?
|
|
293
|
+
(arrowPositionRelativeToPopup - margin) :
|
|
294
|
+
arrowPositionRelativeToPopup > (popupSize - margin) ?
|
|
295
|
+
(margin + arrowPositionRelativeToPopup - popupSize) :
|
|
296
|
+
0
|
|
297
|
+
const finalPopupPosition = fromOriginCorner ? popupOffset + popupDelta : (arrowContainerWidth - popupOffset - popupDelta - popupSize)
|
|
298
|
+
return [[arrowPosition,(constrainedArrowPosition - finalPopupPosition)], popupDelta]
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const arrowElement = arrow && <DefaultArrow ref={arrowRef} />
|
|
302
|
+
|
|
303
|
+
const placeholderElement = <Placeholder as={placeholderAs} ref={placeholder}/>
|
|
304
|
+
return [positionInfo,placeholderElement,arrowElement]
|
|
305
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export * from './Icon'
|
|
2
|
+
export * from './Loading'
|
|
3
|
+
export * from './Button'
|
|
4
|
+
export * from './alt/buttons/OnClickLoadingButton'
|
|
5
|
+
export * from './TextButton'
|
|
6
|
+
export * from './LoadingDiv'
|
|
7
|
+
export * from './Dialog'
|
|
8
|
+
export * from './useDialogWithToggle'
|
|
9
|
+
export * from './WarningDialog'
|
|
10
|
+
export * from './Notifications'
|
|
11
|
+
export * from './ToastNotifications'
|
|
12
|
+
export * from './PersistentNotifications'
|
|
13
|
+
export * from './containers/usePositioning'
|
|
14
|
+
export * from './containers/Reposition'
|
|
15
|
+
export * from './containers/DialogContainer'
|
|
16
|
+
export * from './Draggable'
|
|
17
|
+
export * from './Popup'
|
|
18
|
+
export * from './Tooltip'
|
|
19
|
+
export * from './SplitColumnsContainer'
|
|
20
|
+
export * from './ContextMenu'
|
|
21
|
+
export * from './ErrorBoundary'
|
|
22
|
+
export * from './PageLoader'
|
|
23
|
+
export * from './FillLastLineResizer'
|
|
24
|
+
export * from './Flip'
|
|
25
|
+
export * from './FlipList'
|
|
26
|
+
export * from './TransitionDiv'
|
|
27
|
+
export * from './SwitchDiv'
|
|
28
|
+
export * from './willUseIsSticky'
|
|
29
|
+
export * from './FormatDate'
|
|
30
|
+
export * from './Image'
|
|
31
|
+
export * from './alt/dialogs/DialogWithImageHeader'
|
|
32
|
+
export * from './alt/dialogs/DialogWithSidebar'
|
|
33
|
+
export * from './alt/dialogs/MobileBubbleDialog'
|
|
34
|
+
export * from './Link'
|
|
35
|
+
export * from './BasicBulletList'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {useToggle,useFill} from '@startlibs/core'
|
|
2
|
+
|
|
3
|
+
export const useDialog = (dialog, args = [], toggleCallback) => {
|
|
4
|
+
const toggle = useToggle(false,toggleCallback)
|
|
5
|
+
useFill("Dialog", toggle.isOpen !== false && dialog(toggle.close), [toggle.isOpen, ...args])
|
|
6
|
+
return toggle.open
|
|
7
|
+
}
|
|
8
|
+
export const useDialogWithToggle = (dialog, args = []) => {
|
|
9
|
+
const toggle = useToggle()
|
|
10
|
+
useFill("Dialog", toggle.isOpen !== false && dialog(toggle), [toggle.isOpen, ...args])
|
|
11
|
+
return toggle
|
|
12
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React, {useEffect, useState} from 'react'
|
|
2
|
+
import {useEnsureRef, willUseSharedState, willUseSharedCallback} from '@startlibs/core'
|
|
3
|
+
import styled from 'styled-components'
|
|
4
|
+
import _ from 'lodash/fp'
|
|
5
|
+
import {callIfFunction} from '@startlibs/utils'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const TOP_TRIGGER = (posInfo,refInfo) => {
|
|
9
|
+
// Started sticking.
|
|
10
|
+
if (posInfo.bottom < refInfo.top) {
|
|
11
|
+
return true
|
|
12
|
+
}
|
|
13
|
+
// Stopped sticking.
|
|
14
|
+
if (posInfo.bottom >= refInfo.top &&
|
|
15
|
+
posInfo.bottom < refInfo.bottom) {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const BOTTOM_TRIGGER = (posInfo,refInfo) => {
|
|
20
|
+
// Started sticking.
|
|
21
|
+
if (posInfo.top > refInfo.bottom) {
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
24
|
+
if (posInfo.top <= refInfo.bottom &&
|
|
25
|
+
posInfo.top > refInfo.top) {
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const SentinelStyle = styled.div`
|
|
31
|
+
position:absolute;
|
|
32
|
+
visibility: hidden;
|
|
33
|
+
height:1px;
|
|
34
|
+
width: 1px;
|
|
35
|
+
${props => props.addCss}
|
|
36
|
+
`
|
|
37
|
+
|
|
38
|
+
export const willUseIsSticky = (options = {}) => {
|
|
39
|
+
|
|
40
|
+
const [useRegister, onChange] = willUseSharedCallback()
|
|
41
|
+
const [useSharedIsSticky,, setSharedIsSticky] = willUseSharedState()
|
|
42
|
+
|
|
43
|
+
const createObserver = (root) => window.IntersectionObserver ? new window.IntersectionObserver((records, observer) => {
|
|
44
|
+
for (const record of records) {
|
|
45
|
+
const targetInfo = record.boundingClientRect
|
|
46
|
+
const rootBoundsInfo = record.rootBounds
|
|
47
|
+
if (!rootBoundsInfo) {
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
onChange(targetInfo,rootBoundsInfo, record.target, record.isIntersecting)
|
|
51
|
+
}
|
|
52
|
+
},{root,...options}) : {observe:_.identity,unobserve:_.identity}
|
|
53
|
+
|
|
54
|
+
const observerMap = new Map()
|
|
55
|
+
const getObserver = (root = null) => {
|
|
56
|
+
const result = observerMap.get(root)
|
|
57
|
+
if (result) {
|
|
58
|
+
return result
|
|
59
|
+
}
|
|
60
|
+
const observer = createObserver(root)
|
|
61
|
+
observerMap.set(root,observer)
|
|
62
|
+
return observer
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let activeObserved = []
|
|
66
|
+
const resetObservers = (root = null) => {
|
|
67
|
+
const observer = getObserver(root)
|
|
68
|
+
activeObserved.forEach((observed) => {
|
|
69
|
+
observer.unobserve(observed)
|
|
70
|
+
observer.observe(observed)
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const WithIsSticky = React.forwardRef(({className, id, css, children,bottom, root, intersection,onChange},ref) => {
|
|
75
|
+
const ensuredRef = useEnsureRef(ref)
|
|
76
|
+
const [isSticky,setIsSticky] = useState(false)
|
|
77
|
+
|
|
78
|
+
useRegister((posInfo,refInfo,target, isIntersecting) => {
|
|
79
|
+
if (ensuredRef.current === target) {
|
|
80
|
+
const value = intersection ? isIntersecting : (bottom ? BOTTOM_TRIGGER(posInfo,refInfo) : TOP_TRIGGER(posInfo,refInfo))
|
|
81
|
+
if (value!==undefined) {
|
|
82
|
+
setIsSticky(value)
|
|
83
|
+
callIfFunction(onChange,value)
|
|
84
|
+
if (!id) {
|
|
85
|
+
setSharedIsSticky(value)
|
|
86
|
+
} else if (value) {
|
|
87
|
+
setSharedIsSticky(id)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
setIsSticky(false)
|
|
95
|
+
getObserver(callIfFunction(root)).observe(ensuredRef.current)
|
|
96
|
+
activeObserved = activeObserved.concat(ensuredRef.current)
|
|
97
|
+
return () => {
|
|
98
|
+
// observer.unobserve(ref.current)
|
|
99
|
+
activeObserved = _.without([ensuredRef.current],activeObserved)
|
|
100
|
+
}
|
|
101
|
+
},[])
|
|
102
|
+
|
|
103
|
+
const renderSentinel = (css = "") => <SentinelStyle addCss={css} className={className} ref={ensuredRef} />
|
|
104
|
+
|
|
105
|
+
return children
|
|
106
|
+
? children(renderSentinel,isSticky)
|
|
107
|
+
: renderSentinel(css)
|
|
108
|
+
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
return [WithIsSticky, useSharedIsSticky, resetObservers]
|
|
113
|
+
}
|
package/.babelrc
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// .babelrc
|
|
2
|
-
{
|
|
3
|
-
"presets": [
|
|
4
|
-
"@babel/preset-react",
|
|
5
|
-
"@babel/preset-flow",
|
|
6
|
-
[
|
|
7
|
-
"@babel/preset-env",
|
|
8
|
-
{
|
|
9
|
-
"modules": false
|
|
10
|
-
}
|
|
11
|
-
]
|
|
12
|
-
],
|
|
13
|
-
"plugins": [
|
|
14
|
-
"lodash",
|
|
15
|
-
"macros",
|
|
16
|
-
"fbjs-scripts/babel-6/dev-expression",
|
|
17
|
-
"@babel/plugin-proposal-export-namespace-from",
|
|
18
|
-
"@babel/plugin-proposal-throw-expressions",
|
|
19
|
-
"@babel/plugin-syntax-import-meta",
|
|
20
|
-
"@babel/plugin-proposal-json-strings",
|
|
21
|
-
"@babel/plugin-syntax-dynamic-import",
|
|
22
|
-
"@babel/plugin-transform-arrow-functions",
|
|
23
|
-
"@babel/plugin-transform-spread",
|
|
24
|
-
"@babel/plugin-proposal-object-rest-spread",
|
|
25
|
-
["@babel/plugin-proposal-class-properties", {"loose": true}]
|
|
26
|
-
]
|
|
27
|
-
}
|