Skip to content

System Dialog

إدارة النوافذ بدون اعتماديات

ts
// global object managment
import { type ConfirmOptions, type ConfirmRef } from '@/components/QimbConfirm'

export const refs = {
  confirm(content: ConfirmOptions) {
    return this.confirmRef?.show(content)
  }
}
tsx
// confirm component
import { t } from '@lingui/macro'
import { ConfirmDialog } from 'primereact/confirmdialog'
import { forwardRef, useImperativeHandle, useState } from 'react'

export interface ConfirmOptions {
  cancelLabel?: string
  message?: string
  okLabel?: string
  type?: 'info' | 'warning' | 'error'
}

export interface ConfirmRef {
  show: (options?: ConfirmOptions) => Promise<boolean>
}

export const Confirm = forwardRef<ConfirmRef>((_, ref) => {
  // Define internal state
  const [modal, setModal] = useState<
    ConfirmOptions & {
      promiseResolver?: (value: boolean) => void
      visible: boolean
    }
  >({
    visible: false,
  })

  // Expose Component functions via ref
  useImperativeHandle(ref, () => ({
    show: (options: ConfirmOptions = {}): Promise<boolean> => {
      return new Promise((resolve) => {
        setModal({
          ...options,
          promiseResolver: resolve,
          visible: true,
        })
      })
    },
  }))

  const handleClose = (result: boolean) => {
    if (modal.promiseResolver) {
      modal.promiseResolver(result)
    }

    setModal((previous) => ({ ...previous, visible: false }))
  }

  let header
  switch (modal.type) {
    case 'error':
      header = t`تحذير`
      break
    case 'info':
      header = t`تأكيد`
      break
    case 'warning':
      header = t`تحذير`
      break
    default:
      break
  }

  if (!modal.visible) return null

  return (
    <ConfirmDialog
      accept={() => handleClose(true)}
      acceptClassName={modal.type === 'error' ? 'p-button-danger' : ''}
      acceptLabel={modal.okLabel || t`تأكيد`}
      group="declarative"
      header={header}
      icon="pi pi-exclamation-triangle"
      message={modal.message}
      onHide={() => handleClose(false)}
      reject={() => handleClose(false)}
      rejectLabel={modal.cancelLabel || t`إلغاء`}
      visible={modal.visible}
    />
  )
})
tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { Confirm } from './components/Confirm'
import App from './app.tsx'

createRoot(document.querySelector('#app')!).render(
  <StrictMode>
    <App />
    <Confirm
      ref={(ref) => {
        refs.confirmRef = ref
      }}
    />
  </StrictMode>
)

إدارة النوافذ باستخدام zustand

ts
// store manage dialogs
import { type ReactNode } from 'react';
import { create } from 'zustand';

export type confirmContetnt = {
  acceptLabel?: string;
  header: ReactNode | string;
  message: ReactNode | string;
  rejectivInvisible?: boolean;
};
export type AppStoreType = {
  actions: {
    confirm: (
      data: confirmContetnt & { onAccept?: () => void; onCancel?: () => void },
    ) => Promise<void>;

    toast: (data: ToastMessage | ToastMessage[]) => void;
  };

  confirm: (
    data: confirmContetnt & { onAccept?: () => void; onCancel?: () => void },
  ) => Promise<void>;

  toast: ToastMessage | ToastMessage[] | null;
};
export const useAppStore = create<AppStoreType>((set) => ({
  actions: {
    async confirm(data) {
      set({ confirm: data });
    },

    async toast(data) {
      set({ toast: data });
    },
  },
  // state
  confirm: null,
  toast: null,
}));

export const { confirm, toast } = useAppStore.getState().actions;
tsx
// confirm component
import { ConfirmDialog } from "primereact/confirmdialog";

import { confirmContetnt } from "@/stores/useAppStore";
import { classNames } from "primereact/utils";
import { t } from "@lingui/macro";

type confirmFun = (confrimed: boolean) => void;
type stateContent = confirmContetnt & { visible: boolean };
type confirmRef = {
  show: (content: confirmContetnt) => Promise<boolean>;
};
export const Confirm = forwardRef<confirmRef>((_, ref) => {
  const [
    { acceptLabel, header, message, rejectivInvisible, visible },
    setContent,
  ] = useState<stateContent>({
    acceptLabel: undefined,
    header: "",
    message: "",
    rejectivInvisible: undefined,
    visible: false,
  });
  const [confirmHandler, setConfirmHandler] = useState<confirmFun | null>(null);
  useImperativeHandle(ref, () => ({
    show: ({
      header: headerContet,
      message: massgeContent,
      ...others
    }: confirmContetnt): Promise<boolean> => {
      setContent({
        acceptLabel: others?.acceptLabel,
        header: headerContet,
        massage: massgeContent,
        rejectivInvisible: others?.rejectivInvisible,
        visible: true,
      });
      return new Promise<boolean>((resolve) => {
        const handle = (confirmed: boolean) => {
          setContent((content) => ({ ...content, visible: false }));
          resolve(confirmed);
        };

        setConfirmHandler(() => handle);
      });
    },
  }));
  const handleConfirm = (confirmed: boolean) => {
    if (confirmHandler) {
      confirmHandler(confirmed);
      setConfirmHandler(null);
    }
  };

  return (
    <ConfirmDialog
      accept={() => handleConfirm(true)}
      acceptLabel={acceptLabel || t`Yes`}
      header={header}
      pt={{
        rejectButton: {
          root: {
            className:
              "w-full px-4 py-2 mt-3 text-zinc-700 shadow-none border-zinc-700 text-base font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm",
          },
        },
        acceptButton: {
          root: {
            className:
              "px-4 py-2 text-base font-medium focus:shadow-none focus:outline-none   text-white bg-zinc-700 border border-transparent rounded-md  shadow-sm hover:bg-zinc-800    sm:ml-3 sm:w-auto sm:text-sm",
          },
        },
      }}
      message={message}
      onHide={() => setContent((content) => ({ ...content, visible: false }))}
      reject={() => handleConfirm(false)}
      rejectClassName={classNames({
        invisible: rejectivInvisible,
      })}
      rejectLabel={t`No`}
      visible={visible}
    />
  );
});
tsx
// entry point
import { NavBar } from './components/NavBar';
import { Notifications } from './components/Notifications';
import { User } from './components/User';
import ChangeLanguage from '@/components/changeLanguage';
import { type confirmContetnt, useAppStore } from '@/stores/useAppStore';
import { Toast } from 'primereact/toast';

type confirmRef = {
  show: (content: confirmContetnt) => Promise<boolean>;
};

export function Component() {
  const { confirm, toast } = useAppStore();

  const confirmRef = useRef<confirmRef>(null);
  const toastRef = useRef<Toast>(null);

  useEffect(() => {
    (async () => {
      if (confirm) {
        const result = await confirmRef?.current?.show({
          header: confirm?.header,
          message: confirm?.message,
        });
        if (result && confirm.onAccept) confirm.onAccept();
        else if (result && confirm.onCancel) confirm.onCancel();
        useAppStore.setState({ confirm: null });
      }
    })();
  }, [confirm]);

  useEffect(() => {
    if (toast) toastRef?.current?.show(toast);

    useAppStore.setState({ toast: null });
  }, [toast]);
  return (
    <>
      <div className="grid grid-cols-[0.1fr_auto] max-w-full">
        <NavBar />
        <div>
          <div className="flex justify-end items-center gap-2 bg-slate-200 p-4">
            <div className="flex items-center gap-2 lg:gap-5 ">
              <User />
              <Notifications />
              <ChangeLanguage />
            </div>
          </div>
          <Outlet />
        </div>
      </div>
      <Toast ref={toastRef} />
      <Confirm ref={confirmRef} />
    </>
  );
}