diff --git a/packages/components/snackbar/index.ts b/packages/components/snackbar/index.ts index b3d22709..ebc8d674 100644 --- a/packages/components/snackbar/index.ts +++ b/packages/components/snackbar/index.ts @@ -7,3 +7,4 @@ export default PuikSnackbar; export * from './src/snackbar'; export * from './src/snackbar-provider'; +export * from './src/use-snackbar'; diff --git a/packages/components/snackbar/src/snackbar.ts b/packages/components/snackbar/src/snackbar.ts index 8186ca97..6395426b 100644 --- a/packages/components/snackbar/src/snackbar.ts +++ b/packages/components/snackbar/src/snackbar.ts @@ -1,11 +1,40 @@ import '@prestashopcorp/puik-components/snackbar/style/css'; import type Snackbar from './snackbar.vue'; +import type { Component } from 'vue'; export enum PuikSnackbarVariants { Default = 'default', Danger = 'danger', Success = 'success', } +export enum PuikSnackbarActions { + Add = 'add-snackbar', + Update = 'update-snackbar', + Dismiss = 'dismiss-snackbar', + Remove = 'remove-snackbar', +} + +export type PuikSnackbarDispatchActions = + | { + type: PuikSnackbarActions.Add + snackbar: SnackbarProps + } + | { + type: PuikSnackbarActions.Update + snackbar: Partial + } + | { + type: PuikSnackbarActions.Dismiss + snackbarId?: SnackbarProps['id'] + } + | { + type: PuikSnackbarActions.Remove + snackbarId?: SnackbarProps['id'] + }; + +export interface SnackbarsState { + snackbars: SnackbarProps[] +} export enum PuikSnackbarSwipeAnimations { Right = 'slide-right', @@ -14,6 +43,7 @@ export enum PuikSnackbarSwipeAnimations { Down = 'slide-down', } export interface SnackbarProps { + id: string open?: boolean title?: string description: string @@ -21,6 +51,8 @@ export interface SnackbarProps { variant?: `${PuikSnackbarVariants}` swipeAnimation?: `${PuikSnackbarSwipeAnimations}` hasCloseButton?: boolean + action?: Component + onOpenChange?: ((value: boolean) => void) | undefined } export type SnackbarEmits = { diff --git a/packages/components/snackbar/src/snackbar.vue b/packages/components/snackbar/src/snackbar.vue index 2040be08..e242c86b 100644 --- a/packages/components/snackbar/src/snackbar.vue +++ b/packages/components/snackbar/src/snackbar.vue @@ -2,6 +2,7 @@
(), { + id: `puik-snackbar-${generateId()}`, variant: PuikSnackbarVariants.Default, swipeAnimation: PuikSnackbarSwipeAnimations.Right }); diff --git a/packages/components/snackbar/src/use-snackbar.ts b/packages/components/snackbar/src/use-snackbar.ts new file mode 100644 index 00000000..80207a91 --- /dev/null +++ b/packages/components/snackbar/src/use-snackbar.ts @@ -0,0 +1,84 @@ +import { type SnackbarProps, PuikSnackbarActions, PuikSnackbarDispatchActions, SnackbarsState } from './snackbar'; + +const SNACKBAR_LIMIT = 1; +const SNACKBAR_REMOVE_DELAY = 5000; + +function addToRemoveQueue(snackbarId: string, snackbarTimeouts: Map>, state: SnackbarsState) { + if (snackbarTimeouts.has(snackbarId)) { return; } + + const timeout = setTimeout(() => { + snackbarTimeouts.delete(snackbarId); + dispatch({ + type: PuikSnackbarActions.Remove, + snackbarId + }, snackbarTimeouts, state); + }, SNACKBAR_REMOVE_DELAY); + + snackbarTimeouts.set(snackbarId, timeout); +} + +function dispatch(action: PuikSnackbarDispatchActions, snackbarTimeouts: Map>, state: SnackbarsState) { + switch (action.type) { + case PuikSnackbarActions.Add: + state.snackbars = [action.snackbar, ...state.snackbars].slice(0, SNACKBAR_LIMIT); + break; + + case PuikSnackbarActions.Update: + state.snackbars = state.snackbars.map(s => + s.id === action.snackbar.id ? { ...s, ...action.snackbar } : s + ); + break; + + case PuikSnackbarActions.Dismiss: { + const { snackbarId } = action; + + if (snackbarId) { + addToRemoveQueue(snackbarId, snackbarTimeouts, state); + } else { + state.snackbars.forEach((snackbar) => { + addToRemoveQueue(snackbar.id, snackbarTimeouts, state); + }); + } + + state.snackbars = state.snackbars.map(s => + s.id === snackbarId + ? { ...s, open: false } + : s + ); + break; + } + + case PuikSnackbarActions.Remove: + state.snackbars = state.snackbars.filter(s => s.id !== action.snackbarId); + break; + } +} + +export const useSnackbar = (props: SnackbarProps, snackbarTimeouts: Map>, state: SnackbarsState) => { + const id = props.id; + + const update = (props: SnackbarProps) => + dispatch({ + type: PuikSnackbarActions.Update, + snackbar: { ...props } + }, snackbarTimeouts, state); + + const dismiss = () => dispatch({ type: PuikSnackbarActions.Dismiss, snackbarId: props.id }, snackbarTimeouts, state); + + dispatch({ + type: PuikSnackbarActions.Add, + snackbar: { + ...props, + open: true, + onOpenChange: (open: boolean) => { + if (!open) { dismiss(); } + } + } + }, snackbarTimeouts, state); + + return { + id, + dismiss, + update + }; +};