@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,9 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {StartlibsConsumer} from '@startlibs/core'
|
|
3
|
+
import {formatDate} from '@startlibs/utils'
|
|
4
|
+
|
|
5
|
+
export const FormatDate = ({date,format, noUTC,children}) => <StartlibsConsumer>{({dateMessages}) => {
|
|
6
|
+
if (!date) { return null }
|
|
7
|
+
const formatted = formatDate(new Date(date), format, dateMessages, noUTC)
|
|
8
|
+
return children ? children(formatted) : formatted
|
|
9
|
+
}}</StartlibsConsumer>
|
package/src/Icon.jsx
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
import { withTheme } from 'styled-components'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IconComponent extends React.Component {
|
|
7
|
+
|
|
8
|
+
state = {}
|
|
9
|
+
|
|
10
|
+
componentDidMount() {
|
|
11
|
+
const icon = this.props.icon
|
|
12
|
+
if ((this.props.theme.svgIcons || {})[icon]) {
|
|
13
|
+
this.props.theme.svgLoader(icon).then((i) => this.setState({Svg: i.default}))
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
render() {
|
|
18
|
+
const {icon, children, theme, clickable, customIcon, colorSet, color, ...rest} = this.props
|
|
19
|
+
if ((theme.svgIcons || {})[icon]) {
|
|
20
|
+
if (this.state.Svg) {
|
|
21
|
+
return <this.state.Svg {...rest} />
|
|
22
|
+
} else {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return <span {...rest}>{theme.icons[icon] || (customIcon && icon)}{children}</span>
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const Icon = styled(withTheme(IconComponent))`
|
|
31
|
+
font-family: '${props => props.theme?.iconFont}' !important;
|
|
32
|
+
speak: none;
|
|
33
|
+
font-style: normal;
|
|
34
|
+
font-weight: normal;
|
|
35
|
+
font-variant: normal;
|
|
36
|
+
text-transform: none;
|
|
37
|
+
line-height: 1;
|
|
38
|
+
|
|
39
|
+
/* Better Font Rendering =========== */
|
|
40
|
+
-webkit-font-smoothing: antialiased;
|
|
41
|
+
-moz-osx-font-smoothing: grayscale;
|
|
42
|
+
user-select: none;
|
|
43
|
+
${props => props.clickable && `
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
&:hover {
|
|
46
|
+
opacity: 0.85;
|
|
47
|
+
}
|
|
48
|
+
`}
|
|
49
|
+
`
|
package/src/Image.jsx
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React, {forwardRef, useEffect, useState} from 'react'
|
|
2
|
+
|
|
3
|
+
export const Image = forwardRef(({dom,src,style, ...rest}, ref) => {
|
|
4
|
+
|
|
5
|
+
const [image, setImage] = useState(typeof src === 'string' ? src : (src && URL.createObjectURL(src)))
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (src) {
|
|
9
|
+
const imageStr = typeof src === 'string' ? src : URL.createObjectURL(src)
|
|
10
|
+
const img = document.createElement('img')
|
|
11
|
+
img.onload = () => {
|
|
12
|
+
setImage(imageStr)
|
|
13
|
+
}
|
|
14
|
+
img.src = imageStr
|
|
15
|
+
} else {
|
|
16
|
+
setImage("")
|
|
17
|
+
}
|
|
18
|
+
}, [src])
|
|
19
|
+
|
|
20
|
+
return dom
|
|
21
|
+
? <img {...rest} src={image} ref={ref}/>
|
|
22
|
+
: <div {...rest} style={{...style,backgroundImage:image && `url("${image}")`}} ref={ref}/>
|
|
23
|
+
})
|
package/src/Link.jsx
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React, {useMemo} from 'react'
|
|
2
|
+
import * as Wouter from 'wouter'
|
|
3
|
+
import { useBrowserLocation } from "wouter/use-browser-location"
|
|
4
|
+
import { useCallbackStable } from "@startlibs/core"
|
|
5
|
+
import {callIfFunction, addQueryString} from "@startlibs/utils";
|
|
6
|
+
import _ from 'lodash/fp'
|
|
7
|
+
|
|
8
|
+
export const Link = ({href,...props}) => _.isString(props.to) ? <Wouter.Link {...props} /> : <a {...props} href={href?.replace("~","")} />
|
|
9
|
+
|
|
10
|
+
export const HistoryLink = ({to,onClick,...props}) => {
|
|
11
|
+
return <a onClick={(e) => {
|
|
12
|
+
if (onClick && !callIfFunction(onClick,e)) { return }
|
|
13
|
+
if (e.isDefaultPrevented()) { return }
|
|
14
|
+
if (!to) { return }
|
|
15
|
+
e.preventDefault()
|
|
16
|
+
const {state,pathname,search,hash} = _.isObject(to) ? to : {pathname:to}
|
|
17
|
+
const url = (pathname || "") + (search ? "?" + search.replace(/^\?/,"") : "") + (hash ? "#" + hash.replace(/^#/,"") : "")
|
|
18
|
+
window.history.pushState(state, '', url)
|
|
19
|
+
var popStateEvent = new PopStateEvent('popstate', { state: state });
|
|
20
|
+
window.dispatchEvent(popStateEvent);
|
|
21
|
+
}} {...props} />
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const Route = ({element,children,...props}) => <Wouter.Route {...props}>{element || children}</Wouter.Route>
|
|
25
|
+
export const Routes = Wouter.Switch
|
|
26
|
+
export const Navigate = Wouter.Redirect
|
|
27
|
+
export const useLocation = () => {
|
|
28
|
+
const [path] = Wouter.useLocation()
|
|
29
|
+
const [search] = Wouter.useSearchParams()
|
|
30
|
+
return {pathname:path,search:search ? "?" + search : "",state:window.history.state}
|
|
31
|
+
}
|
|
32
|
+
export const useAbsolutePath = () => {
|
|
33
|
+
const router = Wouter.useRouter();
|
|
34
|
+
const [path] = Wouter.useLocation()
|
|
35
|
+
return router.base + path
|
|
36
|
+
}
|
|
37
|
+
export const useAbsoluteLocation = () => {
|
|
38
|
+
const router = Wouter.useRouter();
|
|
39
|
+
const {pathname,search} = useLocation()
|
|
40
|
+
return router.base + pathname + search
|
|
41
|
+
}
|
|
42
|
+
export const useNavigate = () => Wouter.useLocation()[1]
|
|
43
|
+
export const useParams = Wouter.useParams
|
|
44
|
+
export const useSearchParams = (defaultParams) => {
|
|
45
|
+
const [location, navigate] = Wouter.useLocation()
|
|
46
|
+
const search = Wouter.useSearch()
|
|
47
|
+
const searchParams = useMemo(() => _.assign(defaultParams,Object.fromEntries(new URLSearchParams(search).entries())), [search])
|
|
48
|
+
|
|
49
|
+
let tempSearchParams = searchParams
|
|
50
|
+
|
|
51
|
+
const setSearchParams = useCallbackStable((nextParams, options) => {
|
|
52
|
+
tempSearchParams = _.isFunction(nextParams) ? nextParams(tempSearchParams) : _.assign(tempSearchParams, nextParams)
|
|
53
|
+
const cleanParams = defaultParams
|
|
54
|
+
? _.flow(
|
|
55
|
+
_.toPairs,
|
|
56
|
+
_.filter(([k,v]) => defaultParams[k] !== v),
|
|
57
|
+
_.fromPairs
|
|
58
|
+
)(tempSearchParams)
|
|
59
|
+
: tempSearchParams
|
|
60
|
+
navigate(addQueryString(location, cleanParams), options)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
return [searchParams, setSearchParams]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const Router = Wouter.Router
|
|
67
|
+
|
|
68
|
+
export {useBrowserLocation}
|
package/src/Loading.jsx
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import styled from 'styled-components'
|
|
2
|
+
import {getColor} from '@startlibs/utils'
|
|
3
|
+
|
|
4
|
+
export const Loading = styled.div`
|
|
5
|
+
z-index:1;
|
|
6
|
+
--loading-color: ${props => props.color || (props.white ? 'rgba(255,255,255,0.9)' : getColor("main")(props))};
|
|
7
|
+
--loading-bw: ${props => props.borderWidth || 4}px;
|
|
8
|
+
width: ${props => props.size || 26}px;
|
|
9
|
+
aspect-ratio: 1;
|
|
10
|
+
flex-shrink: 0;
|
|
11
|
+
border-radius: 50%;
|
|
12
|
+
background:
|
|
13
|
+
radial-gradient(farthest-side, var(--loading-color) 94%, #0000) top/var(--loading-bw) var(--loading-bw) no-repeat,
|
|
14
|
+
conic-gradient(#0000 15%, var(--loading-color));
|
|
15
|
+
-webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - var(--loading-bw)), #000 0);
|
|
16
|
+
mask: radial-gradient(farthest-side, #0000 calc(100% - var(--loading-bw)), #000 0);
|
|
17
|
+
animation: loading-spin 0.6s infinite linear;
|
|
18
|
+
${props => props.absolute && `
|
|
19
|
+
position: absolute;
|
|
20
|
+
top: 0;
|
|
21
|
+
bottom: 0;
|
|
22
|
+
right: 0;
|
|
23
|
+
left: 0;
|
|
24
|
+
margin: auto;
|
|
25
|
+
`}
|
|
26
|
+
@keyframes loading-spin {
|
|
27
|
+
100% {transform: rotate(1turn)}
|
|
28
|
+
}
|
|
29
|
+
`
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import _ from 'lodash/fp'
|
|
3
|
+
import styled from 'styled-components'
|
|
4
|
+
import {willUseFlipSwitch} from './Flip'
|
|
5
|
+
import {Loading} from './Loading'
|
|
6
|
+
import {useEnsureRef, useLazyConstant} from '@startlibs/core'
|
|
7
|
+
import {twoWayAnimation} from '@startlibs/utils'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const [loadingEnter, loadingLeave] = twoWayAnimation('opacity:0;', 'opacity:1;')
|
|
11
|
+
const [onEnter, onLeave] = twoWayAnimation('opacity:0; transform:translateY(50%)', 'opacity:1;transform:translateY(0)')
|
|
12
|
+
|
|
13
|
+
const BlockContainer = styled.div`
|
|
14
|
+
position:relative;
|
|
15
|
+
`
|
|
16
|
+
|
|
17
|
+
const FlipDiv = ({useAnimation, outerRef, onEnter = onEnter, onLeave = onLeave, ...props}) => {
|
|
18
|
+
const ensuredRef = useEnsureRef(outerRef)
|
|
19
|
+
useAnimation(ensuredRef, onEnter, onLeave)
|
|
20
|
+
return <BlockContainer {...props} ref={ensuredRef}/>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const PaddingDiv = styled.div`
|
|
24
|
+
padding: ${props => props.height} 0;
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
export const LoadingDiv = ({defaultToSuspense = true, fallback, isLoading, className, height = 12, size = 32, borderWidth = 5, children}) => {
|
|
28
|
+
const flipAnimation = useLazyConstant(willUseFlipSwitch)
|
|
29
|
+
|
|
30
|
+
const loadingBlock = <PaddingDiv height={_.isString(height) ? borderWidth : (height+'rem')}>
|
|
31
|
+
<Loading autoFocus absolute size={size} borderWidth={borderWidth}/>
|
|
32
|
+
</PaddingDiv>
|
|
33
|
+
|
|
34
|
+
return <React.Suspense
|
|
35
|
+
fallback={<FlipDiv
|
|
36
|
+
key="loading"
|
|
37
|
+
padded
|
|
38
|
+
onEnter={loadingEnter}
|
|
39
|
+
onLeave={loadingLeave}
|
|
40
|
+
useAnimation={flipAnimation}
|
|
41
|
+
className={className}
|
|
42
|
+
>
|
|
43
|
+
{fallback}
|
|
44
|
+
{loadingBlock}
|
|
45
|
+
</FlipDiv>}
|
|
46
|
+
>
|
|
47
|
+
{
|
|
48
|
+
(typeof isLoading === 'boolean' || defaultToSuspense)
|
|
49
|
+
? (isLoading
|
|
50
|
+
? <FlipDiv
|
|
51
|
+
key="loading"
|
|
52
|
+
padded
|
|
53
|
+
onEnter={loadingEnter}
|
|
54
|
+
onLeave={loadingLeave}
|
|
55
|
+
useAnimation={flipAnimation}
|
|
56
|
+
className={className}
|
|
57
|
+
>
|
|
58
|
+
{fallback}
|
|
59
|
+
{loadingBlock}
|
|
60
|
+
</FlipDiv>
|
|
61
|
+
: <FlipDiv
|
|
62
|
+
key="loaded"
|
|
63
|
+
loaded
|
|
64
|
+
onEnter={loadingEnter}
|
|
65
|
+
onLeave={loadingLeave}
|
|
66
|
+
useAnimation={flipAnimation}
|
|
67
|
+
className={className}
|
|
68
|
+
>
|
|
69
|
+
{children}
|
|
70
|
+
</FlipDiv>
|
|
71
|
+
)
|
|
72
|
+
: children
|
|
73
|
+
}
|
|
74
|
+
</React.Suspense>
|
|
75
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React, {useEffect, useRef} from 'react'
|
|
2
|
+
import _ from 'lodash/fp'
|
|
3
|
+
import styled from 'styled-components'
|
|
4
|
+
import {Fill, useToggle, willUseSharedState} from '@startlibs/core'
|
|
5
|
+
import {callIfFunction, getColor, oneWayAnimation} from '@startlibs/utils'
|
|
6
|
+
import {useFlipAnimation} from './Flip'
|
|
7
|
+
import {Icon} from './Icon'
|
|
8
|
+
|
|
9
|
+
const NotificationContainer = styled.div `
|
|
10
|
+
position: fixed;
|
|
11
|
+
top: 0;
|
|
12
|
+
height: 1px;
|
|
13
|
+
left: 0;
|
|
14
|
+
right: 0;
|
|
15
|
+
text-align: center;
|
|
16
|
+
`
|
|
17
|
+
|
|
18
|
+
const Notification = styled.div `
|
|
19
|
+
padding: 1rem 4.5rem 1rem 1.5rem;
|
|
20
|
+
font-size: 14px;
|
|
21
|
+
font-weight: 500;
|
|
22
|
+
border-radius: 10px;
|
|
23
|
+
color: white;
|
|
24
|
+
text-align: left;
|
|
25
|
+
background: ${props => getColor(props.type)(props)};
|
|
26
|
+
box-shadow: 0 2px 0px 0px rgba(0,0,0,0.1);
|
|
27
|
+
min-width: 12rem;
|
|
28
|
+
min-height: 48px;
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
position: relative;
|
|
32
|
+
top: 1rem;
|
|
33
|
+
${Icon} {
|
|
34
|
+
color: white;
|
|
35
|
+
display: inline-block;
|
|
36
|
+
background-color: rgba(0,0,0,0.1);
|
|
37
|
+
font-size: 18px;
|
|
38
|
+
width: 24px;
|
|
39
|
+
height: 24px;
|
|
40
|
+
text-align: center;
|
|
41
|
+
line-height: 24px;
|
|
42
|
+
border-radius: 50%;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
position: absolute;
|
|
45
|
+
top: 50%;
|
|
46
|
+
transform: translateY(-50%);
|
|
47
|
+
right: 1rem;
|
|
48
|
+
&:hover {
|
|
49
|
+
background-color: rgba(0,0,0,0.2);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
`
|
|
53
|
+
|
|
54
|
+
const [useNotification, useNotificationRef,setNotification] = willUseSharedState()
|
|
55
|
+
export {useNotification, useNotificationRef, setNotification}
|
|
56
|
+
export const willSetNotification = (str) => () => setNotification(str)
|
|
57
|
+
|
|
58
|
+
const animation = oneWayAnimation('opacity:0; transform:translateY(-20px)', 'opacity:1;transform:translate(0)')
|
|
59
|
+
|
|
60
|
+
export const Notifications = () => {
|
|
61
|
+
const toggle = useToggle()
|
|
62
|
+
const [rawNotification,setNotification] = useNotification()
|
|
63
|
+
|
|
64
|
+
const {msg,type,timeout = 5000} = _.isObject(rawNotification) ? rawNotification : {type:"success",msg:rawNotification}
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (msg) {
|
|
68
|
+
toggle.open()
|
|
69
|
+
setTimeout(toggle.willCloseAfter(timeout), 100)
|
|
70
|
+
}
|
|
71
|
+
},[msg])
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!toggle.isOpen) {
|
|
74
|
+
setNotification(null)
|
|
75
|
+
}
|
|
76
|
+
},[toggle.isOpen])
|
|
77
|
+
|
|
78
|
+
return toggle.isOpen && (<Fill name="Notifications">
|
|
79
|
+
<FlipNotification type={type}>
|
|
80
|
+
{callIfFunction(msg,toggle.close)}
|
|
81
|
+
<Icon icon="x" onClick={toggle.close}/>
|
|
82
|
+
</FlipNotification>
|
|
83
|
+
</Fill>)
|
|
84
|
+
}
|
|
85
|
+
const FlipNotification = (props) => {
|
|
86
|
+
const ref = useRef()
|
|
87
|
+
useFlipAnimation(ref,animation)
|
|
88
|
+
return <NotificationContainer ref={ref}><Notification id="js-share-notification" {...props}/></NotificationContainer>
|
|
89
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, {useEffect} from 'react'
|
|
2
|
+
import styled, {css} from 'styled-components'
|
|
3
|
+
import {oneWayAnimation, getColor} from '@startlibs/utils'
|
|
4
|
+
import {useToggle} from '@startlibs/core'
|
|
5
|
+
import {useFlipLeave} from './Flip'
|
|
6
|
+
import {animationTiming} from '@startlibs/utils'
|
|
7
|
+
|
|
8
|
+
export const LoadingBar = styled.div`
|
|
9
|
+
width: 100%;
|
|
10
|
+
border-radius: 0 1px 1px 0;
|
|
11
|
+
height: 4px;
|
|
12
|
+
position: fixed;
|
|
13
|
+
background: ${props => props.bottom ? css`${getColor('main')}`:`#84D359`};
|
|
14
|
+
transform: translateX(-100%);
|
|
15
|
+
${props => props.bottom ? "bottom:69px;" : "top: 0;"}
|
|
16
|
+
left: 0;
|
|
17
|
+
z-index: 99999;
|
|
18
|
+
box-shadow: ${props => props.bottom ? `0 0 1px 1px rgba(0,132,214,0.2);`:`0 0 2px 0 rgba(0,0,0,0.5);`};
|
|
19
|
+
${props => props.loading && `
|
|
20
|
+
transform: translateX(-30%);
|
|
21
|
+
transition: transform 1.5s ease;
|
|
22
|
+
`}
|
|
23
|
+
${props => props.closing && `
|
|
24
|
+
transform: translateX(0);
|
|
25
|
+
transition: transform 0.3s ease-in;
|
|
26
|
+
`}
|
|
27
|
+
`
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
export const PageLoader = ({isLoading}) => {
|
|
31
|
+
const loader = useToggle()
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (isLoading) {
|
|
34
|
+
loader.open()
|
|
35
|
+
} else {
|
|
36
|
+
loader.closeAfter(500)
|
|
37
|
+
}
|
|
38
|
+
},[isLoading])
|
|
39
|
+
|
|
40
|
+
return <LoadingBar loading={loader.isOpen} closing={loader.closing}/>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const animation = oneWayAnimation('transform:translateX(-30%);','transform:translateX(0);')
|
|
44
|
+
export const SuspenseLoadingBar = () => {
|
|
45
|
+
const ref = React.useRef()
|
|
46
|
+
useFlipLeave(ref,animation,{reverse: false,timing:animationTiming('0.3s ease-in')})
|
|
47
|
+
return <LoadingBar ref={ref} loading/>
|
|
48
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import React, {useRef} from 'react'
|
|
2
|
+
import _ from 'lodash/fp'
|
|
3
|
+
import styled from 'styled-components'
|
|
4
|
+
import {Fill, willUseSharedState} from '@startlibs/core'
|
|
5
|
+
import {media,getColor, callIfFunction, oneWayAnimation, printerMedia} from '@startlibs/utils'
|
|
6
|
+
import {useFlipAnimation} from './Flip'
|
|
7
|
+
import {Icon} from './Icon'
|
|
8
|
+
import {darken, transparentize } from 'polished';
|
|
9
|
+
import {Button} from './Button'
|
|
10
|
+
|
|
11
|
+
export const PersistentNotification = styled.div`
|
|
12
|
+
color: ${getColor('warning')};
|
|
13
|
+
h4 {
|
|
14
|
+
font-size: 15px;
|
|
15
|
+
font-weight: 500;
|
|
16
|
+
margin-bottom: 0.5rem;
|
|
17
|
+
}
|
|
18
|
+
p {
|
|
19
|
+
font-size: 13px;
|
|
20
|
+
margin-bottom: 0;
|
|
21
|
+
}
|
|
22
|
+
`
|
|
23
|
+
|
|
24
|
+
export const PersistentNotificationContent = styled.div`
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
padding: 1.5rem;
|
|
28
|
+
`
|
|
29
|
+
export const PersistentNotificationIcon = styled(Icon)`
|
|
30
|
+
font-size: 38px;
|
|
31
|
+
width: 4rem;
|
|
32
|
+
height: 4rem;
|
|
33
|
+
line-height: 4rem;
|
|
34
|
+
border-radius: 100px;
|
|
35
|
+
text-align: center;
|
|
36
|
+
background: ${props => transparentize(0.88, getColor("warning")(props))};
|
|
37
|
+
margin-right: 1rem;
|
|
38
|
+
flex-shrink: 0;
|
|
39
|
+
${props => props.icon === 'warning' && `font-size:30px;`}
|
|
40
|
+
`
|
|
41
|
+
export const PersistentNotificationActions = styled.div`
|
|
42
|
+
display: flex;
|
|
43
|
+
margin-top: -0.25rem;
|
|
44
|
+
padding: 0 1.5rem 1.5rem;
|
|
45
|
+
${Button} {
|
|
46
|
+
flex-grow: 1;
|
|
47
|
+
}
|
|
48
|
+
${Button} ~ ${Button} {
|
|
49
|
+
margin-left: 0;
|
|
50
|
+
margin-right: 1rem;
|
|
51
|
+
}
|
|
52
|
+
${media.max(390)`
|
|
53
|
+
display: block;
|
|
54
|
+
${Button} {
|
|
55
|
+
width: 100%;
|
|
56
|
+
}
|
|
57
|
+
${Button} ~ ${Button} {
|
|
58
|
+
margin-left: 0;
|
|
59
|
+
margin-top: 0.75rem;
|
|
60
|
+
}
|
|
61
|
+
`}
|
|
62
|
+
`
|
|
63
|
+
export const PersistentNotificationCloseIcon = styled(Icon).attrs({icon: 'x'})`
|
|
64
|
+
position: absolute;
|
|
65
|
+
width: 30px;
|
|
66
|
+
height: 30px;
|
|
67
|
+
font-size: 20px;
|
|
68
|
+
line-height: 30px;
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
border-radius: 50%;
|
|
71
|
+
text-align: center;
|
|
72
|
+
z-index: 1000;
|
|
73
|
+
top: 0.5rem;
|
|
74
|
+
right: 0.5rem;
|
|
75
|
+
color: ${props => props.type && props.type!== 'main' ? getColor(props.type)(props) : darken(0.1, getColor("main")(props))};
|
|
76
|
+
background: ${props => transparentize(0.90, getColor(props.type || "main")(props))};
|
|
77
|
+
:hover {
|
|
78
|
+
background: ${props => transparentize(0.85, getColor(props.type || "main")(props))};
|
|
79
|
+
}
|
|
80
|
+
:active {
|
|
81
|
+
background: ${props => transparentize(0.82, getColor(props.type || "main")(props))};
|
|
82
|
+
}
|
|
83
|
+
`
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
const [useNotifications, ,setNotifications] = willUseSharedState([])
|
|
87
|
+
|
|
88
|
+
export const openPersistentNotification = (notification) => {
|
|
89
|
+
const newNotification = {priority:0,type:"success",...(_.isObject(notification) ? notification : {content:notification})}
|
|
90
|
+
|
|
91
|
+
setNotifications(list => {
|
|
92
|
+
const nextIndex = list.findIndex(n => n.priority < newNotification.priority)
|
|
93
|
+
return nextIndex < 0
|
|
94
|
+
? [...list,newNotification]
|
|
95
|
+
: [...list.slice(0,nextIndex),newNotification,...list.slice(nextIndex)]
|
|
96
|
+
})
|
|
97
|
+
return () => setNotifications(_.without([newNotification]))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
const animation = oneWayAnimation('opacity:0; transform:translateY(-20px)', 'opacity:1;transform:translate(0)')
|
|
102
|
+
|
|
103
|
+
const NotificationContainer = styled.div `
|
|
104
|
+
position: fixed;
|
|
105
|
+
top: 0;
|
|
106
|
+
height: 0;
|
|
107
|
+
right: 0;
|
|
108
|
+
z-index:20;
|
|
109
|
+
`
|
|
110
|
+
|
|
111
|
+
const Notification = styled.div `
|
|
112
|
+
background: ${props => transparentize(0.9, getColor(props.type)(props))};
|
|
113
|
+
position: relative;
|
|
114
|
+
border-radius: 19px;
|
|
115
|
+
`
|
|
116
|
+
const NotificationWrapper = styled.div `
|
|
117
|
+
background: white;
|
|
118
|
+
margin-top: ${props => props.marginTop || "3.5rem"};
|
|
119
|
+
margin-right: 0.75rem;
|
|
120
|
+
margin-left: 0.75rem;
|
|
121
|
+
padding: 5px;
|
|
122
|
+
border-radius: 22px;
|
|
123
|
+
box-shadow: 0 0 10px 0 rgba(0,0,0,0.1), 0 1px 2px 1px rgba(0,0,0,0.15);
|
|
124
|
+
max-width: 364px;
|
|
125
|
+
${media.mobile`
|
|
126
|
+
margin-top: ${props => props.marginTop || "4rem"};
|
|
127
|
+
`}
|
|
128
|
+
${printerMedia`
|
|
129
|
+
display: none;
|
|
130
|
+
`}
|
|
131
|
+
`
|
|
132
|
+
const [useIsHiddenPersistentNotifications,,hidePersistentNotifications] = willUseSharedState(false)
|
|
133
|
+
export {useIsHiddenPersistentNotifications, hidePersistentNotifications}
|
|
134
|
+
export const PersistentNotifications = () => {
|
|
135
|
+
const [notifications,setNotifications] = useNotifications()
|
|
136
|
+
const [isHidden] = useIsHiddenPersistentNotifications()
|
|
137
|
+
|
|
138
|
+
if (!notifications.length || isHidden) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const closeMessage = (e) => {
|
|
142
|
+
setNotifications((list) => list.slice(1))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const {content,type,marginTop,...notification} = notifications[0]
|
|
146
|
+
|
|
147
|
+
return <Fill name="PersistentNotifications">
|
|
148
|
+
<FlipNotification type={type} marginTop={marginTop}>
|
|
149
|
+
{content
|
|
150
|
+
? callIfFunction(content,closeMessage)
|
|
151
|
+
: <DefaultNotification type={type} _onClose={closeMessage} {...notification} />
|
|
152
|
+
}
|
|
153
|
+
</FlipNotification>
|
|
154
|
+
</Fill>
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const DefaultNotification = ({type, title,body,actions, icon, skipClose,_onClose,onClose}) => {
|
|
158
|
+
return <PersistentNotification>
|
|
159
|
+
<PersistentNotificationContent>
|
|
160
|
+
{(_.isString(icon) || !icon) ? <PersistentNotificationIcon icon={icon || type}/> : icon}
|
|
161
|
+
<div>
|
|
162
|
+
<h4>{title}</h4>
|
|
163
|
+
{body}
|
|
164
|
+
</div>
|
|
165
|
+
</PersistentNotificationContent>
|
|
166
|
+
{
|
|
167
|
+
actions &&
|
|
168
|
+
<PersistentNotificationActions>
|
|
169
|
+
{actions}
|
|
170
|
+
</PersistentNotificationActions>
|
|
171
|
+
}
|
|
172
|
+
{!skipClose && <PersistentNotificationCloseIcon type="warning" onClick={_.over([_onClose,onClose])}/>}
|
|
173
|
+
</PersistentNotification>
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const FlipNotification = (props) => {
|
|
177
|
+
const ref = useRef()
|
|
178
|
+
useFlipAnimation(ref,animation)
|
|
179
|
+
return <NotificationContainer className="JS-fix-dialog-scroll" ref={ref}>
|
|
180
|
+
<NotificationWrapper marginTop={props.marginTop}><Notification id="js-share-notification" {...props}/></NotificationWrapper>
|
|
181
|
+
</NotificationContainer>
|
|
182
|
+
}
|
package/src/Popup.jsx
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, {useMemo} from 'react'
|
|
2
|
+
import styled, {css} from 'styled-components'
|
|
3
|
+
import {Fill, useEnsureRef} from '@startlibs/core'
|
|
4
|
+
import {useFlipAnimation} from './Flip'
|
|
5
|
+
import {getDialogLayersSize} from './containers/DialogContainer'
|
|
6
|
+
import {usePositioning} from './containers/usePositioning'
|
|
7
|
+
import {twoWayAnimation, customTheme} from '@startlibs/utils'
|
|
8
|
+
|
|
9
|
+
const [fadeIn, fadeOut] = twoWayAnimation(`opacity:0;`, `opacity:1;`)
|
|
10
|
+
|
|
11
|
+
const PopupDiv = styled.div`
|
|
12
|
+
position: absolute;
|
|
13
|
+
background-color: #FFF;
|
|
14
|
+
box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 10px -2px rgba(0,0,0,0.15), 0 0px 2px 0px rgba(0,0,0,0.2);
|
|
15
|
+
border-radius: 8px;
|
|
16
|
+
padding: 1rem;
|
|
17
|
+
text-align: initial;
|
|
18
|
+
line-height: 140%;
|
|
19
|
+
font-size: 14px;
|
|
20
|
+
z-index: 999;
|
|
21
|
+
${props => props.atTop && css`
|
|
22
|
+
margin-top: -0.25rem;
|
|
23
|
+
`}
|
|
24
|
+
${props => props.atBottom && css`
|
|
25
|
+
margin-top: 0.25rem;
|
|
26
|
+
`}
|
|
27
|
+
${customTheme('Popup')}
|
|
28
|
+
`
|
|
29
|
+
|
|
30
|
+
export const Popup = React.forwardRef(({slot = 'Popup',skipPortal, animation = fadeIn, leaveAnimation = true, animationOptions, arrowRef, className, onClick, children, ...rest},ref) => {
|
|
31
|
+
const popupRef = useEnsureRef(ref)
|
|
32
|
+
const [positionInfo, placeholder, arrow] = usePositioning(popupRef, rest, arrowRef)
|
|
33
|
+
|
|
34
|
+
// leaveAnimation=false → sem animação de saída (nenhum clone no unmount): útil quando
|
|
35
|
+
// o popup é portalado num slot persistente (ex: NavbarPopup) e o dono desmonta na
|
|
36
|
+
// navegação — senão o clone da saída fica órfão no slot. Default true = comportamento atual.
|
|
37
|
+
useFlipAnimation(popupRef, animation, leaveAnimation, animationOptions)
|
|
38
|
+
const slotName = useMemo(() => getDialogLayersSize() === 0 ? slot : (slot + '-' + (getDialogLayersSize() - 1)), [])
|
|
39
|
+
const popupContainer = <PopupDiv {...positionInfo} onClick={onClick} className={className} ref={popupRef}>
|
|
40
|
+
{arrow}
|
|
41
|
+
{children}
|
|
42
|
+
</PopupDiv>
|
|
43
|
+
return skipPortal ? popupContainer : <>
|
|
44
|
+
{placeholder}
|
|
45
|
+
<Fill name={slotName}>
|
|
46
|
+
{popupContainer}
|
|
47
|
+
</Fill>
|
|
48
|
+
</>
|
|
49
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import {getMargin, media} from '@startlibs/utils'
|
|
4
|
+
import styled, {css} from 'styled-components'
|
|
5
|
+
|
|
6
|
+
const getMarginFromFieldChild = (props) => {
|
|
7
|
+
if (!props.last) return 0;
|
|
8
|
+
const fields = React.Children.toArray(props.children).filter(c => c.type.displayName === "Field" || c.type.displayName === "EnhancedInput")
|
|
9
|
+
if (!fields.length) return 0
|
|
10
|
+
const lastField = fields.slice(-1)[0]
|
|
11
|
+
return '-' + getMargin(props.theme.Field.margins)(lastField.props)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
const splitColumnMargins = {small:0.5,'default':1, big:2}
|
|
16
|
+
|
|
17
|
+
export const SplitColumnsContainer = styled.div`
|
|
18
|
+
|
|
19
|
+
margin-bottom: ${getMarginFromFieldChild}rem;
|
|
20
|
+
|
|
21
|
+
${props => css`
|
|
22
|
+
${media.min(props.viewportMinWidth || 0)`
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-wrap: nowrap;
|
|
25
|
+
margin-left: -${props=>getMargin(splitColumnMargins)(props)/2}rem;
|
|
26
|
+
margin-right: -${props=>getMargin(splitColumnMargins)(props)/2}rem;
|
|
27
|
+
margin-top: -${props=>getMargin(splitColumnMargins)(props)}rem;
|
|
28
|
+
> * {
|
|
29
|
+
flex-basis: 0;
|
|
30
|
+
flex-grow: 1;
|
|
31
|
+
margin-left: ${props=>getMargin(splitColumnMargins)(props)/2}rem;
|
|
32
|
+
margin-right: ${props=>getMargin(splitColumnMargins)(props)/2}rem;
|
|
33
|
+
margin-top: ${props=>getMargin(splitColumnMargins)(props)}rem;
|
|
34
|
+
}
|
|
35
|
+
`}
|
|
36
|
+
`}
|
|
37
|
+
${props => props.twoColumnsBelowVW && css`
|
|
38
|
+
${media.max(props.twoColumnsBelowVW)`
|
|
39
|
+
flex-wrap: wrap;
|
|
40
|
+
> * {
|
|
41
|
+
flex-basis: 47%;
|
|
42
|
+
}
|
|
43
|
+
`}
|
|
44
|
+
`}
|
|
45
|
+
`
|
|
46
|
+
|
|
47
|
+
export const fixDoubledLastMarginBottom = (children) => {
|
|
48
|
+
let last = React.Children.count(children)-1
|
|
49
|
+
|
|
50
|
+
function processChild(child, i) {
|
|
51
|
+
if (i === last && child && child.type && child.type.displayName === SplitColumnsContainer.displayName) {
|
|
52
|
+
return React.cloneElement(child, {last: true})
|
|
53
|
+
}
|
|
54
|
+
return child
|
|
55
|
+
}
|
|
56
|
+
if (!Array.isArray(children) || children.length===0) {
|
|
57
|
+
return processChild(children,0)
|
|
58
|
+
}
|
|
59
|
+
while ((!children[last] || (children[last] && children[last].type && children[last].type.displayName === "Errors")) && last > 0) {
|
|
60
|
+
last--
|
|
61
|
+
}
|
|
62
|
+
return React.Children.map(children,processChild)
|
|
63
|
+
}
|