Skip to content

Commit

Permalink
Merge pull request #16 from Blankeos/fix/update-existing-toast-and-icons
Browse files Browse the repository at this point in the history
fix: updates on existing toasts and displaying of icons.
  • Loading branch information
wobsoriano committed Jun 9, 2024
2 parents 2e196a8 + 466d3ff commit 79ac7f9
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 57 deletions.
30 changes: 29 additions & 1 deletion dev/components/Types/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ toast.promise(promise, {
action: () =>
toast.promise<{ name: string }>(
() =>
new Promise((resolve) => {
new Promise((resolve, reject) => {
setTimeout(() => {
const random50Percent = Math.floor(Math.random() * 2)
if (random50Percent > 0)
reject(new Error('Something\'s not right!'))
resolve({ name: 'Solid Sonner' })
}, 1500)
}),
Expand All @@ -86,6 +89,31 @@ toast.promise(promise, {
},
),
},
{
name: 'Loading',
snippet: `const promise = () => new Promise((resolve) => setTimeout(resolve, 800));
toast.loading('Uploading...', { id: 'form' });
await promise();
toast.loading('Saving...', { id: 'form'});
await promise();
toast.success('Success!', { id: 'form' });
`,
action: async () => {
const idAlphabet = 'abcdefghijklmnopqrstuvwxyz1234567890'
const getRandomIndex = () => Math.floor(Math.random() * idAlphabet.length)
const toastId = idAlphabet[getRandomIndex()]! + idAlphabet[getRandomIndex()]! + idAlphabet[getRandomIndex()]!

const promise = () => new Promise((resolve) => {
setTimeout(resolve, 1000)
})
toast.loading('Uploading...', { id: toastId })
await promise()
toast.loading('Saving...', { id: toastId })
await promise()
toast.success('Success!', { id: toastId })
},
},
{
name: 'Custom',
snippet: 'toast(<div>A custom toast with default styling</div>)',
Expand Down
114 changes: 58 additions & 56 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
* https://github.com/emilkowalski/sonner/blob/main/src/index.tsx
*/
import './styles.css'
import type { Component, ValidComponent } from 'solid-js'
import { Dynamic } from 'solid-js/web'
import type { Component } from 'solid-js'
import { For, Show, createEffect, createSignal, mergeProps, on, onCleanup, onMount } from 'solid-js'
import { createStore, produce, reconcile } from 'solid-js/store'
import { Loader, getAsset } from './assets'
import type { ExternalToast, HeightT, Position, ToastProps, ToastT, ToastToDismiss, ToasterProps } from './types'
import { ToastState, toast } from './state'
Expand Down Expand Up @@ -340,12 +340,8 @@ const Toast: Component<ToastProps> = (props) => {
<>
<Show when={toastType() || props.toast.icon || props.toast.promise}>
<div data-icon="">
<Show
when={props.toast.icon || props.toast.promise || toastType() === 'loading'}
fallback={<Dynamic component={getAsset(toastType()!)!} />}
>
<Dynamic component={((props.toast.icon && (() => props.toast.icon)) || props.icons?.loading || getLoadingIcon()) as ValidComponent} />
</Show>
{props.toast.promise || (props.toast.type === 'loading' && !props.toast.icon) ? props.toast.icon || getLoadingIcon() : null}
{props.toast.type !== 'loading' ? props.toast.icon || props.icons?.[toastType() as unknown as keyof typeof props.icons] || getAsset(toastType()!)!() : null}
</div>
</Show>

Expand Down Expand Up @@ -427,10 +423,16 @@ const Toaster: Component<ToasterProps> = (props) => {
dir: getDocumentDirection(),
}, props) as ToasterProps & { position: Position; hotkey: string[]; visibleToasts: number }

const [toasts, setToasts] = createSignal<ToastT[]>([])
/**
* Use a store instead of a signal for fine-grained reactivity.
* All the setters only have to change the deepest part of the tree
* to maintain referential integrity when rendered in the DOM.
*/
const [toastsStore, setToastsStore] = createStore<{ toasts: ToastT[] }>({ toasts: [] })

const possiblePositions = () => {
return Array.from(
new Set([propsWithDefaults.position].concat(toasts().filter(toast => toast.position).map(toast => toast.position as Position))),
new Set([propsWithDefaults.position].concat(toastsStore.toasts.filter(toast => toast.position).map(toast => toast.position as Position))),
)
}
const [heights, setHeights] = createSignal<HeightT[]>([])
Expand All @@ -449,29 +451,31 @@ const Toaster: Component<ToasterProps> = (props) => {
: 'light'
: 'light',
)
const removeToast = (toast: ToastT) => setToasts(toasts => toasts.filter(({ id }) => id !== toast.id))
const removeToast = (toast: ToastT) => setToastsStore('toasts', toasts => toasts.filter(({ id }) => id !== toast.id))

onMount(() => {
const unsub = ToastState.subscribe((toast) => {

Check warning on line 457 in src/index.tsx

View workflow job for this annotation

GitHub Actions / lint

This function should be passed to a tracked scope (like createEffect) or an event handler because it contains reactivity, or else changes will be ignored
if ((toast as ToastToDismiss).dismiss) {
setToasts(toasts => toasts.map(t => (t.id === toast.id ? { ...t, delete: true } : t)))
setToastsStore('toasts', produce((_toasts) => {
_toasts.forEach((t) => {
if (t.id === toast.id)
t.delete = true
})
}))
return
}

setToasts((toasts) => {
const indexOfExistingToast = toasts.findIndex(t => t.id === toast.id)

// Update the toast if it already exists
if (indexOfExistingToast !== -1) {
return [
...toasts.slice(0, indexOfExistingToast),
{ ...toasts[indexOfExistingToast], ...toast },
...toasts.slice(indexOfExistingToast + 1),
]
}
// Update (Fine-grained)
const changedIndex = toastsStore.toasts.findIndex(t => t.id === toast.id)
if (changedIndex !== -1) {
setToastsStore('toasts', [changedIndex], reconcile(toast))
return
}

return [toast, ...toasts]
})
// Insert (Fine-grained)
setToastsStore('toasts', produce((_toasts) => {
_toasts.unshift(toast)
}))
})

onCleanup(() => {
Expand Down Expand Up @@ -504,7 +508,7 @@ const Toaster: Component<ToasterProps> = (props) => {

createEffect(() => {
// Ensure expanded is always false when no toasts are present / only one left
if (toasts().length <= 1)
if (toastsStore.toasts.length <= 1)
setExpanded(false)
})

Expand Down Expand Up @@ -549,7 +553,7 @@ const Toaster: Component<ToasterProps> = (props) => {
)

return (
<Show when={toasts().length > 0}>
<Show when={toastsStore.toasts.length > 0}>
{/* Remove item from normal navigation flow, only available via hotkey */}
<section aria-label={`Notifications ${hotkeyLabel()}`} tabIndex={-1}>
<For each={possiblePositions()}>
Expand Down Expand Up @@ -603,36 +607,34 @@ const Toaster: Component<ToasterProps> = (props) => {
onPointerUp={() => setInteracting(false)}
>
<For each={
toasts()
.filter(toast => (!toast.position && index() === 0) || toast.position === position)
}>
toastsStore.toasts.filter(toast => (!toast.position && index() === 0) || toast.position === position)}>
{(toast, index) => (
<Toast
index={index()}
icons={propsWithDefaults.icons}
toast={toast}
duration={propsWithDefaults.toastOptions?.duration ?? props.duration}
class={propsWithDefaults.toastOptions?.class}
classes={propsWithDefaults.toastOptions?.classes}
cancelButtonStyle={propsWithDefaults.toastOptions?.cancelButtonStyle}
actionButtonStyle={propsWithDefaults.toastOptions?.actionButtonStyle}
descriptionClass={propsWithDefaults.toastOptions?.descriptionClass}
invert={Boolean(propsWithDefaults.invert)}
visibleToasts={propsWithDefaults.visibleToasts}
closeButton={Boolean(propsWithDefaults.closeButton)}
interacting={interacting()}
position={propsWithDefaults.position}
style={propsWithDefaults.toastOptions?.style}
unstyled={propsWithDefaults.toastOptions?.unstyled}
removeToast={removeToast}
toasts={toasts()}
heights={heights()}
setHeights={setHeights}
expandByDefault={Boolean(propsWithDefaults.expand)}
gap={propsWithDefaults.gap}
expanded={expanded()}
pauseWhenPageIsHidden={propsWithDefaults.pauseWhenPageIsHidden}
/>
<Toast
index={index()}
icons={propsWithDefaults.icons}
toast={toast}
duration={propsWithDefaults.toastOptions?.duration ?? props.duration}
class={propsWithDefaults.toastOptions?.class}
classes={propsWithDefaults.toastOptions?.classes}
cancelButtonStyle={propsWithDefaults.toastOptions?.cancelButtonStyle}
actionButtonStyle={propsWithDefaults.toastOptions?.actionButtonStyle}
descriptionClass={propsWithDefaults.toastOptions?.descriptionClass}
invert={Boolean(propsWithDefaults.invert)}
visibleToasts={propsWithDefaults.visibleToasts}
closeButton={Boolean(propsWithDefaults.closeButton)}
interacting={interacting()}
position={propsWithDefaults.position}
style={propsWithDefaults.toastOptions?.style}
unstyled={propsWithDefaults.toastOptions?.unstyled}
removeToast={removeToast}
toasts={toastsStore.toasts}
heights={heights()}
setHeights={setHeights}
expandByDefault={Boolean(propsWithDefaults.expand)}
gap={propsWithDefaults.gap}
expanded={expanded()}
pauseWhenPageIsHidden={propsWithDefaults.pauseWhenPageIsHidden}
/>
)}
</For>
</ol>
Expand Down

0 comments on commit 79ac7f9

Please sign in to comment.