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} />
</>
);
}