Skip to content

Commit

Permalink
feat: support dismissing toast when action is clicked (#763)
Browse files Browse the repository at this point in the history
  • Loading branch information
pedroalves0 authored Apr 27, 2023
1 parent 2ecbb50 commit f1a2f04
Show file tree
Hide file tree
Showing 12 changed files with 50 additions and 167 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Reactist follows [semantic versioning](https://semver.org/) and doesn't introduce breaking changes (API-wise) in minor or patch releases. However, the appearance of a component might change in a minor or patch release so keep an eye on redesigns and make sure your app still looks and feels like you expect it.

# v20.3.0

- [Feat] Toasts wrapped in `ToastsProvider` are now dismissed when their action `onClick` handler fires.

# v20.2.0

- [Feat] Expose CSS variables for theming the `SwitchField` component.
Expand Down
2 changes: 1 addition & 1 deletion docs/iframe.html
Original file line number Diff line number Diff line change
Expand Up @@ -476,4 +476,4 @@



window['STORIES'] = [{"titlePrefix":"","directory":"./src","files":"**/*.stories.@(tsx|mdx)","importPathMatcher":"^\\.[\\\\/](?:src(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(tsx|mdx))$"},{"titlePrefix":"","directory":"./stories","files":"**/*.stories.@(js|jsx|ts|tsx|mdx)","importPathMatcher":"^\\.[\\\\/](?:stories(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(js|jsx|ts|tsx|mdx))$"}];</script><script src="runtime~main.8b1ed5da.iframe.bundle.js"></script><script src="vendors~main.44ce8732.iframe.bundle.js"></script><script src="main.233bd9c7.iframe.bundle.js"></script></body></html>
window['STORIES'] = [{"titlePrefix":"","directory":"./src","files":"**/*.stories.@(tsx|mdx)","importPathMatcher":"^\\.[\\\\/](?:src(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(tsx|mdx))$"},{"titlePrefix":"","directory":"./stories","files":"**/*.stories.@(js|jsx|ts|tsx|mdx)","importPathMatcher":"^\\.[\\\\/](?:stories(?:\\/(?!\\.)(?:(?:(?!(?:^|\\/)\\.).)*?)\\/|\\/|$)(?!\\.)(?=.)[^/]*?\\.stories\\.(js|jsx|ts|tsx|mdx))$"}];</script><script src="runtime~main.8b1ed5da.iframe.bundle.js"></script><script src="vendors~main.698b0ffc.iframe.bundle.js"></script><script src="main.d3e4c92a.iframe.bundle.js"></script></body></html>
1 change: 0 additions & 1 deletion docs/main.233bd9c7.iframe.bundle.js

This file was deleted.

2 changes: 1 addition & 1 deletion docs/project.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"generatedAt":1682536241278,"builder":{"name":"webpack4"},"hasCustomBabel":false,"hasCustomWebpack":true,"hasStaticDirs":false,"hasStorybookEslint":false,"refCount":0,"packageManager":{"type":"npm","version":"8.19.2"},"typescriptOptions":{"check":true,"checkOptions":{},"reactDocgen":"react-docgen-typescript","reactDocgenTypescriptOptions":{"shouldExtractLiteralValuesFromEnum":true}},"features":{"interactionsDebugger":true},"storybookVersion":"^6.5.3","language":"typescript","storybookPackages":{"@storybook/addon-knobs":{"version":"6.3.1"},"@storybook/addon-links":{"version":"6.5.3"},"@storybook/addons":{"version":"6.5.3"},"@storybook/jest":{"version":"0.0.10"},"@storybook/react":{"version":"6.5.3"},"@storybook/testing-library":{"version":"0.0.13"}},"framework":{"name":"react"},"addons":{"@storybook/addon-postcss":{"version":"2.0.0"},"@storybook/addon-actions":{"version":"6.5.3"},"@storybook/addon-docs":{"options":{"configureJSX":true},"version":"6.5.3"},"@storybook/addon-controls":{"version":"6.5.3"},"@geometricpanda/storybook-addon-badges":{"version":"0.2.2"},"@storybook/addon-interactions":{"version":"6.5.15"}}}
{"generatedAt":1682579128237,"builder":{"name":"webpack4"},"hasCustomBabel":false,"hasCustomWebpack":true,"hasStaticDirs":false,"hasStorybookEslint":false,"refCount":0,"packageManager":{"type":"npm","version":"8.19.2"},"typescriptOptions":{"check":true,"checkOptions":{},"reactDocgen":"react-docgen-typescript","reactDocgenTypescriptOptions":{"shouldExtractLiteralValuesFromEnum":true}},"features":{"interactionsDebugger":true},"storybookVersion":"^6.5.3","language":"typescript","storybookPackages":{"@storybook/addon-knobs":{"version":"6.3.1"},"@storybook/addon-links":{"version":"6.5.3"},"@storybook/addons":{"version":"6.5.3"},"@storybook/jest":{"version":"0.0.10"},"@storybook/react":{"version":"6.5.3"},"@storybook/testing-library":{"version":"0.0.13"}},"framework":{"name":"react"},"addons":{"@storybook/addon-postcss":{"version":"2.0.0"},"@storybook/addon-actions":{"version":"6.5.3"},"@storybook/addon-docs":{"options":{"configureJSX":true},"version":"6.5.3"},"@storybook/addon-controls":{"version":"6.5.3"},"@geometricpanda/storybook-addon-badges":{"version":"0.2.2"},"@storybook/addon-interactions":{"version":"6.5.15"}}}
3 changes: 0 additions & 3 deletions docs/vendors~main.44ce8732.iframe.bundle.js

This file was deleted.

140 changes: 0 additions & 140 deletions docs/vendors~main.44ce8732.iframe.bundle.js.LICENSE.txt

This file was deleted.

1 change: 0 additions & 1 deletion docs/vendors~main.44ce8732.iframe.bundle.js.map

This file was deleted.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"email": "[email protected]",
"url": "http://doist.com"
},
"version": "20.2.0",
"version": "20.3.0",
"license": "MIT",
"homepage": "https://github.com/Doist/reactist#readme",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion src/toast/static-toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,5 +156,5 @@ function ToastContentSlot({ children }: { children: React.ReactNode }) {
)
}

export { StaticToast }
export { StaticToast, isActionObject }
export type { StaticToastProps }
5 changes: 4 additions & 1 deletion src/toast/toast.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,16 @@ describe('useToast', () => {
})
})

it('allows to render an action button that performs the action when clicked', () => {
it('allows to render an action button that performs the action when clicked', async () => {
const actionFn = jest.fn()
const { showToast } = renderTestCase()
showToast({ action: { label: 'Undo', onClick: actionFn } })
expect(actionFn).not.toHaveBeenCalled()
userEvent.click(within(screen.getByRole('alert')).getByRole('button', { name: 'Undo' }))
expect(actionFn).toHaveBeenCalledTimes(1)
await waitFor(() => {
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
})
})

it('allows to render something custom in the action slot', () => {
Expand Down
51 changes: 36 additions & 15 deletions src/toast/use-toasts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Portal } from 'ariakit/portal'
import { generateElementId } from '../utils/common-helpers'
import { Box } from '../box'
import { Stack } from '../stack'
import { StaticToast, StaticToastProps } from './static-toast'
import { isActionObject, StaticToast, StaticToastProps } from './static-toast'

import styles from './toast.module.css'

Expand Down Expand Up @@ -80,33 +80,54 @@ const InternalToast = React.forwardRef<HTMLDivElement, InternalToastProps>(funct
timeoutRef.current = undefined
}, [])

const removeToast = React.useCallback(
function removeToast() {
onRemoveToast(toastId)
onDismiss?.()
},
[onDismiss, onRemoveToast, toastId],
)

React.useEffect(
function setupAutoDismiss() {
if (!timeoutRunning || !autoDismissDelay) return
timeoutRef.current = window.setTimeout(() => {
onRemoveToast(toastId)
onDismiss?.()
}, autoDismissDelay * 1000)
timeoutRef.current = window.setTimeout(removeToast, autoDismissDelay * 1000)
return stopTimeout
},
[autoDismissDelay, onDismiss, onRemoveToast, toastId, stopTimeout, timeoutRunning],
[autoDismissDelay, removeToast, stopTimeout, timeoutRunning],
)

/**
* If the action is toast action object and not a custom element,
* the `onClick` property is wrapped in another handler responsible
* for removing the toast when the action is triggered.
*/
const actionWithCustomActionHandler = React.useMemo(() => {
if (!isActionObject(action)) {
return action
}

return {
...action,
onClick: function handleActionClick() {
if (!action) {
return
}

action.onClick()
removeToast()
},
}
}, [action, removeToast])

return (
<StaticToast
ref={ref}
message={message}
description={description}
icon={icon}
action={action}
onDismiss={
showDismissButton
? () => {
onDismiss?.()
onRemoveToast(toastId)
}
: undefined
}
action={actionWithCustomActionHandler}
onDismiss={showDismissButton ? removeToast : undefined}
dismissLabel={dismissLabel}
// @ts-expect-error
onMouseEnter={stopTimeout}
Expand Down

0 comments on commit f1a2f04

Please sign in to comment.