diff --git a/.changeset/strange-otters-heal.md b/.changeset/strange-otters-heal.md new file mode 100644 index 000000000..06b3f61d9 --- /dev/null +++ b/.changeset/strange-otters-heal.md @@ -0,0 +1,5 @@ +--- +'@codeimage/ui': minor +--- + +refactor snackbar with solid-toast diff --git a/apps/codeimage/src/components/Toolbar/ExportButton.tsx b/apps/codeimage/src/components/Toolbar/ExportButton.tsx index d39c4fa4f..544b52849 100644 --- a/apps/codeimage/src/components/Toolbar/ExportButton.tsx +++ b/apps/codeimage/src/components/Toolbar/ExportButton.tsx @@ -15,7 +15,7 @@ import { SegmentedField, SegmentedFieldItem, TextField, - useSnackbarStore, + toast, VStack, } from '@codeimage/ui'; import {useModality} from '@core/hooks/isMobile'; @@ -25,7 +25,14 @@ import { createOverlayTriggerState, OverlayContainer, } from '@solid-aria/overlays'; -import {Component, createEffect, createSignal, onMount, Show} from 'solid-js'; +import { + Component, + createEffect, + createSignal, + onMount, + Show, + untrack, +} from 'solid-js'; import { ExportExtension, ExportMode, @@ -43,7 +50,6 @@ interface ExportButtonProps { export const ExportButton: Component = props => { let openButtonRef: HTMLButtonElement | undefined; - const snackbarStore = useSnackbarStore(); const [t] = useI18n(); const modality = useModality(); const overlayState = createOverlayTriggerState({}); @@ -62,12 +68,16 @@ export const ExportButton: Component = props => { createEffect(() => { if (data.error) { - snackbarStore.create({ - closeable: true, - message: () => { - const [t] = useI18n(); - return <>{t('export.genericSaveError')}; - }, + untrack(() => { + toast.error( + () => { + const [t] = useI18n(); + return <>{t('export.genericSaveError')}; + }, + { + position: 'bottom-center', + }, + ); }); } }); diff --git a/apps/codeimage/src/components/Toolbar/ExportNewTabButton.tsx b/apps/codeimage/src/components/Toolbar/ExportNewTabButton.tsx index 48978b1ea..348815e39 100644 --- a/apps/codeimage/src/components/Toolbar/ExportNewTabButton.tsx +++ b/apps/codeimage/src/components/Toolbar/ExportNewTabButton.tsx @@ -1,6 +1,6 @@ import {useI18n} from '@codeimage/locale'; -import {Button, useSnackbarStore} from '@codeimage/ui'; -import {Component, createEffect} from 'solid-js'; +import {Button, toast} from '@codeimage/ui'; +import {Component, createEffect, untrack} from 'solid-js'; import { ExportExtension, ExportMode, @@ -15,7 +15,6 @@ interface ExportButtonProps { } export const ExportInNewTabButton: Component = props => { - const snackbarStore = useSnackbarStore(); const [t] = useI18n(); const [data, notify] = useExportImage(); @@ -39,12 +38,16 @@ export const ExportInNewTabButton: Component = props => { createEffect(() => { if (data.error) { - snackbarStore.create({ - closeable: true, - message: () => { - const [t] = useI18n(); - return <>{t('export.genericSaveError')}; - }, + untrack(() => { + toast.error( + () => { + const [t] = useI18n(); + return <>{t('export.genericSaveError')}; + }, + { + position: 'bottom-center', + }, + ); }); } }); diff --git a/apps/codeimage/src/core/hooks/async-action.ts b/apps/codeimage/src/core/hooks/async-action.ts index a810f53fe..acd6e3f08 100644 --- a/apps/codeimage/src/core/hooks/async-action.ts +++ b/apps/codeimage/src/core/hooks/async-action.ts @@ -1,22 +1,21 @@ import { + Accessor, createResource, createSignal, - from, mergeProps, Resource, } from 'solid-js'; -import {Observable, Subject} from 'rxjs'; import {ResourceActions} from 'solid-js/types/reactive/signal'; interface AsyncResourceActions extends ResourceActions { - notify: (value: T) => void; + notify: (value?: T) => void; } -export function useAsyncAction( +export function createAsyncAction( fetcher: (ref: T) => Promise, - notifier?: Subject, + notifier?: Accessor, ): [Resource, AsyncResourceActions] { - const [internalNotifier, setNotifier] = createSignal( + const [internalNotifier, setNotifier] = createSignal( undefined, { equals: false, @@ -24,13 +23,12 @@ export function useAsyncAction( ); const [resource, _resourceActions] = createResource( - (notifier instanceof Observable ? from(notifier) : notifier) || - internalNotifier, - fetcher, + notifier || internalNotifier, + async args => fetcher(args as T), ); - const notify = (value: T) => { - return setNotifier(() => value); + const notify = (value?: T) => { + return setNotifier(() => value ?? Symbol()); }; const resourceActions = mergeProps( diff --git a/apps/codeimage/src/hooks/use-export-image.ts b/apps/codeimage/src/hooks/use-export-image.ts index 2704784df..f98c51bb2 100644 --- a/apps/codeimage/src/hooks/use-export-image.ts +++ b/apps/codeimage/src/hooks/use-export-image.ts @@ -7,7 +7,7 @@ import { toSvg, } from '@codeimage/dom-export'; import {EXPORT_EXCLUDE} from '@core/directives/exportExclude'; -import {useAsyncAction} from '@core/hooks/async-action'; +import {createAsyncAction} from '@core/hooks/async-action'; import {useWebshare} from '@core/hooks/use-webshare'; import {isIOS} from '@solid-primitives/platform'; import download from 'downloadjs'; @@ -43,11 +43,13 @@ export function useExportImage(): [ Resource, (data: ExportImagePayload) => void, ] { - const [data, {notify}] = useAsyncAction(async (ref: ExportImagePayload) => { - // @bad Find another way to prevent flickering - await new Promise(r => setTimeout(r, 50)); - return exportImage(ref); - }); + const [data, {notify}] = createAsyncAction( + async (ref: ExportImagePayload) => { + // @bad Find another way to prevent flickering + await new Promise(r => setTimeout(r, 50)); + return exportImage(ref); + }, + ); return [data, notify]; } diff --git a/apps/codeimage/src/i18n/dashboard.ts b/apps/codeimage/src/i18n/dashboard.ts index 42fbf5e64..e422bbf90 100644 --- a/apps/codeimage/src/i18n/dashboard.ts +++ b/apps/codeimage/src/i18n/dashboard.ts @@ -3,6 +3,11 @@ export const dashboard = { dashboard: { myProjects: 'I miei progetti', new: 'Nuovo', + errorCreatingProject: 'Si è verificato un errore!', + projectCreateSuccess: 'Progetto creato con successo', + projectCloneSuccess: 'Progetto {{name}} clonato con successo', + projectDeleteSuccess: 'Il progetto {{name}} è stato eliminato', + projectDeleteError: 'Impossibile eliminare il progetto {{name}}', deleteProject: { dropdownLabel: 'Rimuovi', confirmTitle: 'Rimuovi progetto', @@ -39,6 +44,11 @@ export const dashboard = { dashboard: { myProjects: 'My projects', new: 'New', + errorCreatingProject: 'An error occurred!', + projectCreateSuccess: 'Project created successfully', + projectCloneSuccess: 'Project {{name}} cloned successfully', + projectDeleteSuccess: 'Project {{name}} has been deleted', + projectDeleteError: 'Cannot delete project {{name}}', empty: { title: 'No projects yet', description: @@ -74,6 +84,10 @@ export const dashboard = { dashboard: { myProjects: 'My projects', new: 'New', + errorCreatingProject: 'An error occurred!', + projectCloneSuccess: 'Project {{name}} cloned successfully', + projectDeleteSuccess: 'Project {{name}} has been deleted', + projectDeleteError: 'Cannot delete project {{name}}', created: 'Created', updated: 'Updated', empty: { @@ -111,6 +125,10 @@ export const dashboard = { new: 'New', created: 'Created', updated: 'Updated', + errorCreatingProject: 'An error occurred!', + projectCloneSuccess: 'Project {{name}} cloned successfully', + projectDeleteSuccess: 'Project {{name}} has been deleted', + projectDeleteError: 'Cannot delete project {{name}}', empty: { title: 'No projects yet', description: diff --git a/apps/codeimage/src/index.tsx b/apps/codeimage/src/index.tsx index 5c9f1ab31..c9056ae0e 100644 --- a/apps/codeimage/src/index.tsx +++ b/apps/codeimage/src/index.tsx @@ -2,11 +2,16 @@ import {createI18nContext, I18nContext, useI18n} from '@codeimage/locale'; import {getAuth0State} from '@codeimage/store/auth/auth0'; import {getRootEditorStore} from '@codeimage/store/editor'; import {uiStore} from '@codeimage/store/ui'; -import {backgroundColorVar, CodeImageThemeProvider} from '@codeimage/ui'; +import { + backgroundColorVar, + CodeImageThemeProvider, + SnackbarHost, +} from '@codeimage/ui'; +import {ThemeProviderProps} from '@codeimage/ui/src'; import {enableUmami} from '@core/constants/umami'; import {OverlayProvider} from '@solid-aria/overlays'; -import {setElementVars} from '@vanilla-extract/dynamic'; import {Router, useRoutes} from '@solidjs/router'; +import {setElementVars} from '@vanilla-extract/dynamic'; import { Component, createEffect, @@ -41,7 +46,7 @@ function lazyWithNoLauncher(cp: () => Promise<{default: Component}>) { }); } -const theme: Parameters[0]['theme'] = { +const tokens: ThemeProviderProps['tokens'] = { text: { weight: 'medium', }, @@ -116,7 +121,6 @@ export function Bootstrap() { if (scheme) { const color = theme === 'dark' ? darkGrayScale.gray1 : '#FFFFFF'; scheme.setAttribute('content', color); - document.body.setAttribute('data-codeimage-theme', theme); setElementVars(document.body, { [backgroundColorVar]: color, }); @@ -126,15 +130,16 @@ export function Bootstrap() { return ( - - - + + + + - - - + + + ); diff --git a/apps/codeimage/src/pages/Dashboard/components/CreateNewProjectButton/CreateNewProjectButton.tsx b/apps/codeimage/src/pages/Dashboard/components/CreateNewProjectButton/CreateNewProjectButton.tsx index 07b0de003..b05bfbfbc 100644 --- a/apps/codeimage/src/pages/Dashboard/components/CreateNewProjectButton/CreateNewProjectButton.tsx +++ b/apps/codeimage/src/pages/Dashboard/components/CreateNewProjectButton/CreateNewProjectButton.tsx @@ -1,6 +1,6 @@ import {useI18n} from '@codeimage/locale'; -import {Box, Button, Loading} from '@codeimage/ui'; -import {useAsyncAction} from '@core/hooks/async-action'; +import {Box, Button, Loading, toast} from '@codeimage/ui'; +import {createAsyncAction} from '@core/hooks/async-action'; import {useNavigate} from '@solidjs/router'; import {Show} from 'solid-js'; import {PlusIcon} from '../../../../components/Icons/PlusIcon'; @@ -11,16 +11,31 @@ export function CreateNewProjectButton() { const navigate = useNavigate(); const dashboard = getDashboardState()!; const [t] = useI18n(); - const [data, {notify}] = useAsyncAction(createNew); + const [data, {notify}] = createAsyncAction(createNew); async function createNew() { - const result = await dashboard?.createNewProject(); - if (!result) return; - navigate(`/${result.id}`); + try { + const result = await dashboard?.createNewProject(); + if (!result) return; + toast.success(t('dashboard.projectCreateSuccess'), { + position: 'bottom-center', + duration: 100000, + }); + navigate(`/${result.id}`); + } catch (e) { + toast.error(t('dashboard.errorCreatingProject')); + } } return ( -