Last active
March 5, 2020 23:18
-
-
Save painedpineapple/c57c3776f4eca315baef07b01f184114 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This is deprecated. I recommend using https://gist.github.com/thislogancall/8d13eac0cc9a9435921817be071650a4 | |
*/ | |
open Utils; | |
[%raw {| | |
require("react-notifications-component/dist/theme.css") | |
|}]; | |
type transition = { | |
duration: int, | |
timingFunction: string, | |
delay: int, | |
}; | |
type touchSlidingExitTransition = { | |
swipe: transition, | |
fade: transition, | |
}; | |
type dismiss; | |
[@bs.obj] | |
external dismiss: | |
( | |
~duration: int=?, | |
~pauseOnHover: bool=?, | |
~onScreen: bool=?, | |
~waitForAnimation: bool=?, | |
~click: bool=?, | |
~touch: bool=?, | |
~showIcon: bool=?, | |
unit | |
) => | |
dismiss = | |
""; | |
type notification; | |
[@bs.obj] | |
external notification: | |
( | |
~title: string, | |
~message: React.element, | |
~_type: [@bs.string] [ | `success | `danger | `info | `default | `warning], | |
~insert: [@bs.string] [ | `top | `bottom]=?, | |
~container: [@bs.string] [ | |
| [@bs.as "top-left"] `topLeft | |
| [@bs.as "top-right"] `topRight | |
| [@bs.as "top-center"] `topCenter | |
| [@bs.as "bottom-left"] `bottomLeft | |
| [@bs.as "bottom-right"] `bottomRight | |
| [@bs.as "bottom-center"] `bottomCenter | |
], | |
~dismiss: dismiss=?, | |
~animationIn: array(string)=?, | |
~animationOut: array(string)=?, | |
~slidingEnter: transition=?, | |
~slidingExit: transition=?, | |
~touchRevert: transition=?, | |
~touchSlidingExit: touchSlidingExitTransition=?, | |
~width: int=?, | |
unit | |
) => | |
notification = | |
""; | |
type contentProps = {id: string}; | |
[@bs.obj] | |
external customNotification: | |
( | |
~content: (~props: contentProps) => React.element=?, | |
~insert: [@bs.string] [ | `top | `bottom]=?, | |
~container: [@bs.string] [ | |
| [@bs.as "top-left"] `topLeft | |
| [@bs.as "top-right"] `topRight | |
| [@bs.as "top-center"] `topCenter | |
| [@bs.as "bottom-left"] `bottomLeft | |
| [@bs.as "bottom-right"] `bottomRight | |
| [@bs.as "bottom-center"] `bottomCenter | |
], | |
~dismiss: dismiss=?, | |
~animationIn: array(string)=?, | |
~animationOut: array(string)=?, | |
~slidingEnter: transition=?, | |
~slidingExit: transition=?, | |
~touchRevert: transition=?, | |
~touchSlidingExit: touchSlidingExitTransition=?, | |
~width: int=?, | |
unit | |
) => | |
notification = | |
""; | |
[@bs.module "react-notifications-component"] [@react.component] | |
external make: | |
( | |
~isMobile: bool=?, | |
~breakpoint: int=?, | |
~types: array('t)=?, | |
~className: string=?, | |
~id: string=? | |
) => | |
React.element = | |
"default"; | |
type store; | |
[@bs.module "react-notifications-component"] external store: store = "store"; | |
[@bs.module "react-notifications-component"] [@bs.scope "store"] | |
external add: notification => string = "addNotification"; | |
[@bs.module "react-notifications-component"] [@bs.scope "store"] | |
external remove: string => unit = "removeNotification"; | |
module Style = { | |
open Css; | |
let root = | |
style([ | |
selector(".notification-item", [boxShadow(`none)]), | |
selector( | |
".notification-loader", | |
[ | |
backgroundColor(`transparent)->important, | |
selector( | |
".notification-content", | |
[padding(0->px), height(64->px), width(64->px)], | |
), | |
], | |
), | |
selector( | |
".notification-item > div:not(.notification-loader)", | |
[ | |
boxShadow( | |
Shadow.box(~x=1->px, ~y=3->px, ~blur=4->px, rgba(0, 0, 0, 0.2)), | |
), | |
], | |
), | |
selector( | |
".notification-content .notification-title", | |
[ | |
fontWeight(`bold), | |
textTransform(`uppercase), | |
marginTop(0->rem_of_px), | |
marginBottom(10->rem_of_px), | |
], | |
), | |
selector( | |
".notification-success", | |
[ | |
borderLeftColor(Color.green3), | |
selector(".timer-filler", [backgroundColor(Color.green3)]), | |
selector( | |
"&, .notification-close, .timer", | |
[backgroundColor(Color.greenLight)], | |
), | |
selector( | |
"&, .notification-title, .notification-message, .notification-close::after", | |
[color(Color.green3)], | |
), | |
], | |
), | |
selector( | |
".notification-danger", | |
[ | |
borderLeftColor(Color.red), | |
selector(".timer-filler", [backgroundColor(Color.red)]), | |
selector( | |
"&, .notification-close, .timer", | |
[backgroundColor(Color.redLight)], | |
), | |
selector( | |
"&, .notification-title, .notification-message, .notification-close::after", | |
[color(Color.red)], | |
), | |
], | |
), | |
selector( | |
".notification-info, .notification-loader", | |
[ | |
borderLeftColor(Color.blue2), | |
selector(".timer-filler", [backgroundColor(Color.blue)]), | |
selector( | |
"&, .notification-close, .timer", | |
[backgroundColor(Color.blueLight)], | |
), | |
selector( | |
"&, .notification-title, .notification-message, .notification-close::after", | |
[color(Color.blue)], | |
), | |
], | |
), | |
selector( | |
".notification-default", | |
[ | |
borderLeftColor(Color.gray2), | |
selector( | |
"&, .notification-close, .timer", | |
[backgroundColor(Color.gray)], | |
), | |
], | |
), | |
selector( | |
".notification-warning", | |
[ | |
borderLeftColor(Color.black2), | |
selector(".timer-filler", [backgroundColor(Color.black2)]), | |
selector( | |
"&, .notification-close, .timer", | |
[backgroundColor(Color.yellowLight)], | |
), | |
selector( | |
"&, .notification-title, .notification-message, .notification-close::after", | |
[color(Color.black2)], | |
), | |
], | |
), | |
]); | |
let loaderContainer = | |
style([ | |
`flex->display, | |
alignItems(`center), | |
paddingBottom(15->px), | |
// transforms([translateX((-43)->px)]), | |
selector( | |
".inner-container", | |
[ | |
backgroundColor(Color.blue)->important, | |
position(`relative), | |
margin(0->px)->important, | |
// bottom((-116)->px), | |
transforms([scale(0.75, 0.75)]), | |
], | |
), | |
]); | |
}; | |
let duration = 5000; | |
module Notification = { | |
type _type = [ | `success | `danger | `info | `default | `warning | `loader]; | |
/**If a delay is provided then that notification will not start showing until the delay is over. This is useful if you have a `loading notification that you want to delay or _not_ show (it won't show if there is a delay and then the `.make` method is called again on the) */ | |
[@react.component] | |
let make = | |
( | |
~message, | |
~title, | |
~_type: _type, | |
~id="", | |
~duration=duration, | |
~handleRemove as customRemove=?, | |
~showTimer=true, | |
~delay=0, | |
) => { | |
let (playState, setPlayState) = React.useState(() => `running); | |
let (isReady, setIsReady) = React.useState(() => false); | |
let (elHeight, setElHeight) = React.useState(() => 0); | |
let heightRef = React.useRef(Js.Nullable.null); | |
let timestamp = | |
React.useMemo2( | |
() => { | |
let date = Js.Date.make(); | |
date->Js.Date.setMilliseconds( | |
date->Js.Date.getMilliseconds | |
+. duration->float_of_int | |
+. delay->float_of_int, | |
); | |
}, | |
(duration, delay), | |
); | |
let handleRemove = | |
React.useCallback2( | |
() => { | |
customRemove->( | |
fun | |
| Some(customRemove) => customRemove() | |
| None => id->remove | |
) | |
}, | |
(customRemove, id), | |
); | |
let timer = | |
ReactTimerHook.useTimer({ | |
expiryTimestamp: timestamp, | |
onExpire: handleRemove, | |
}); | |
React.useEffect5( | |
() => { | |
let timeoutId = | |
Js.Global.setTimeout( | |
() => { | |
timer.start(); | |
heightRef | |
->React.Ref.current | |
->Js.Nullable.toOption | |
->( | |
fun | |
| Some(el) => | |
setElHeight(_ => el->Document.domElToDomEl_##offsetHeight) | |
| None => () | |
); | |
setIsReady(_ => true); | |
}, | |
delay, | |
); | |
Some( | |
() => { | |
timeoutId->Js.Global.clearTimeout; | |
id->remove; | |
}, | |
); | |
}, | |
(delay, heightRef, setIsReady, setElHeight, id), | |
); | |
<div | |
className=Css.( | |
merge([ | |
style([100.->pct->width, elHeight->Utils.rem_of_px->height]), | |
style(isReady ? [] : [opacity(0.)]), | |
"notification-" | |
++ ( | |
switch (_type) { | |
| `success => "success" | |
| `danger => "danger" | |
| `loader => "loader" | |
| `info => "info" | |
| `default => "default" | |
| `warning => "warning" | |
} | |
), | |
]) | |
) | |
onMouseEnter={_ => | |
showTimer | |
? { | |
timer.pause(); | |
setPlayState(_ => `paused); | |
} | |
: () | |
} | |
onMouseLeave={_ => | |
showTimer | |
? { | |
timer.resume(); | |
setPlayState(_ => `running); | |
} | |
: () | |
}> | |
<div | |
className="notification-content" | |
ref={heightRef->ReactDOMRe.Ref.domRef}> | |
{switch (_type) { | |
| `loader => message | |
| _ => | |
<> | |
<div | |
className="notification-close" | |
onClick={_ => handleRemove()} | |
/> | |
<div className="notification-title"> title </div> | |
<div className="notification-message"> <div> message </div> </div> | |
{showTimer | |
? <div className="timer"> | |
<div | |
className=Css.( | |
merge([ | |
"timer-filler", | |
style([ | |
/**Animation already exists within "react-notifications-component/dist/theme.css" */ | |
unsafe("animationName", "timer"), | |
animationDuration(duration), | |
animationTimingFunction(`linear), | |
animationFillMode(`forwards), | |
animationPlayState(playState), | |
]), | |
]) | |
) | |
/> | |
</div> | |
: React.null} | |
</> | |
}} | |
</div> | |
</div>; | |
}; | |
}; | |
let transition = {duration: 300, timingFunction: "ease-in-out", delay: 0}; | |
let dismiss = | |
dismiss( | |
~duration, | |
~onScreen=true, | |
~pauseOnHover=true, | |
~click=false, | |
~touch=false, | |
~showIcon=false, | |
(), | |
); | |
type notificationHookType = [ | |
| `success | |
| `error | |
| `info | |
| `loader | |
| `warning | |
]; | |
type notificationHook = { | |
id: string, | |
make: (~content: React.element=?, notificationHookType) => unit, | |
remove: unit => unit, | |
isActive: bool, | |
}; | |
let useNotification = (~useLogout, ()) => { | |
let logout = useLogout(); | |
let (id, setId) = React.useState(() => ""); | |
let previousId = Hooks.usePrevious(id); | |
React.useEffect2( | |
() => { | |
if (previousId != "" && previousId != id) { | |
previousId->remove; | |
}; | |
None; | |
}, | |
(id, previousId), | |
); | |
let remove = () => { | |
id->remove; | |
setId(_ => ""); | |
}; | |
let make = (~content=React.null, notificationHookType) => { | |
let newId = | |
customNotification( | |
~container= | |
switch (notificationHookType) { | |
| `loader => `bottomLeft | |
| _ => `bottomRight | |
}, | |
~slidingEnter=transition, | |
~slidingExit=transition, | |
~dismiss, | |
~animationIn=[|"animated", "fadeIn"|], | |
~animationOut=[|"animated", "fadeOut"|], | |
~content= | |
(~props) => | |
<Notification | |
id={props.id} | |
handleRemove=remove | |
delay={ | |
switch (notificationHookType) { | |
| `loader => 1000 | |
| _ => 0 | |
} | |
} | |
message={ | |
switch (notificationHookType) { | |
| `error => | |
<div> | |
<div> content </div> | |
<div> | |
{str("Our development team has been notified.")} | |
<ButtonGroup | |
className=Css.( | |
style([marginTop(px(10)), marginBottom(px(10))]) | |
) | |
renderLeft={<SentryReportForm message="Send Report" />} | |
renderRight={ | |
<Button | |
color=`red | |
horz=`sm | |
vert=`sm | |
onClick={_ => logout()}> | |
{str("Restart")} | |
</Button> | |
} | |
/> | |
</div> | |
</div> | |
| `info => <div> content </div> | |
| `success => <div> content </div> | |
| `loader => | |
<Loaders.Spinner | |
color=Color.white | |
className=Style.loaderContainer | |
/> | |
| `warning => <div> content </div> | |
} | |
} | |
title={ | |
( | |
switch (notificationHookType) { | |
| `error => "Error" | |
| `info => "Information" | |
| `success => "Success" | |
| `loader => "Loading" | |
| `warning => "Warning" | |
} | |
) | |
->str | |
} | |
_type={ | |
switch (notificationHookType) { | |
| `error => `danger | |
| `info => `info | |
| `success => `success | |
| `loader => `loader | |
| `warning => `warning | |
} | |
} | |
/>, | |
(), | |
) | |
->add; | |
setId(_ => newId); | |
}; | |
// let make = (~content as _c=React.null, _stuff) => Js.log("Make.."); | |
React.useMemo1(() => {make, remove, id, isActive: id != ""}, [|id|]); | |
}; | |
/** EXAMPLE USAGE: | |
* DO NOT use an instance of useNotification as a hook dependency because it is likely rerender each time. It does this b/c each time you call the `make` method the notification `id` changes and thus the instance changes. | |
* | |
let submitNotification = AppStoreHooks.useNotification(); | |
let handleSubmit = () => { | |
submitNotification.make(`loader); | |
data->someAsyncRequest->( | |
fun | |
| Error(message) => | |
submitNotification.make( | |
"Sorry, there was a problem updating your project"->str, | |
`error, | |
); | |
| Ok(updatedProject) => | |
submitNotification.make( | |
"Your project was successfully updated!"->str, | |
`success, | |
) | |
} | |
*/; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment