Files
Termix/src/hooks/use-confirmation.ts
Nunzio Marfè 69f3f88ae5 Handle enter button (#481)
* Update Crowdin configuration file

* Update Crowdin configuration file

* Update Linux Portable section with AUR link (#474)

* fix: file manager incorrectly decoding/encoding when editing files (#476)

* fix: electron build errors and skip macos job

* fix: testflight submit failure

* fix: made submit job match build type

* fix: resolve Vite build warnings for mixed static/dynamic imports (#473)

* Update Crowdin configuration file

* Update Crowdin configuration file

* fix: resolve Vite build warnings for mixed static/dynamic imports

- Convert all dynamic imports of main-axios.ts to static imports (10 files)
- Convert all dynamic imports of sonner to static imports (4 files)
- Add manual chunking configuration to vite.config.ts for better bundle splitting
  - react-vendor: React and React DOM
  - ui-vendor: Radix UI, lucide-react, clsx, tailwind-merge
  - monaco: Monaco Editor
  - codemirror: CodeMirror and related packages
- Increase chunkSizeWarningLimit to 1000kB

This resolves Vite warnings about mixed import strategies preventing
proper code-splitting.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: Termix CI <ci@termix.dev>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: file manager incorrectly decoding/encoding when editing files (made base64/utf8 dependent)

---------

Co-authored-by: Jefferson Nunn <89030989+jeffersonwarrior@users.noreply.github.com>
Co-authored-by: Termix CI <ci@termix.dev>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: build error on docker (#477)

* fix: electron build errors and skip macos job

* fix: testflight submit failure

* fix: made submit job match build type

* fix: resolve Vite build warnings for mixed static/dynamic imports (#473)

* Update Crowdin configuration file

* Update Crowdin configuration file

* fix: resolve Vite build warnings for mixed static/dynamic imports

- Convert all dynamic imports of main-axios.ts to static imports (10 files)
- Convert all dynamic imports of sonner to static imports (4 files)
- Add manual chunking configuration to vite.config.ts for better bundle splitting
  - react-vendor: React and React DOM
  - ui-vendor: Radix UI, lucide-react, clsx, tailwind-merge
  - monaco: Monaco Editor
  - codemirror: CodeMirror and related packages
- Increase chunkSizeWarningLimit to 1000kB

This resolves Vite warnings about mixed import strategies preventing
proper code-splitting.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: Termix CI <ci@termix.dev>
Co-authored-by: Claude <noreply@anthropic.com>

* fix: file manager incorrectly decoding/encoding when editing files (made base64/utf8 dependent)

* fix: build error on docker

---------

Co-authored-by: Jefferson Nunn <89030989+jeffersonwarrior@users.noreply.github.com>
Co-authored-by: Termix CI <ci@termix.dev>
Co-authored-by: Claude <noreply@anthropic.com>

* Increase max old space size for npm builds

* Increase Node.js memory limit in Dockerfile

* Remove NODE_OPTIONS from build commands in Dockerfile

* Change runner to blacksmith-4vcpu-ubuntu-2404

* fix: build error on docker

* Add handle on enter button;

---------

Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: Gaylord Julien <g.j@mailbox.org>
Co-authored-by: Jefferson Nunn <89030989+jeffersonwarrior@users.noreply.github.com>
Co-authored-by: Termix CI <ci@termix.dev>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
2026-01-04 10:29:11 -05:00

151 lines
4.3 KiB
TypeScript

import { useState, useEffect, useCallback } from "react";
import { toast } from "sonner";
interface ConfirmationOptions {
title: string;
description: string;
confirmText?: string;
cancelText?: string;
variant?: "default" | "destructive";
}
interface ToastConfirmOptions {
confirmOnEnter?: boolean;
duration?: number;
}
export function useConfirmation() {
const [isOpen, setIsOpen] = useState(false);
const [options, setOptions] = useState<ConfirmationOptions | null>(null);
const [onConfirm, setOnConfirm] = useState<(() => void) | null>(null);
const [activeToastId, setActiveToastId] = useState<string | number | null>(null);
const [pendingConfirmCallback, setPendingConfirmCallback] = useState<(() => void) | null>(null);
const [pendingResolve, setPendingResolve] = useState<((value: boolean) => void) | null>(null);
const handleEnterKey = useCallback((event: KeyboardEvent) => {
if (event.key === "Enter" && activeToastId !== null) {
event.preventDefault();
event.stopPropagation();
if (pendingConfirmCallback) {
pendingConfirmCallback();
}
if (pendingResolve) {
pendingResolve(true);
}
toast.dismiss(activeToastId);
setActiveToastId(null);
setPendingConfirmCallback(null);
setPendingResolve(null);
}
}, [activeToastId, pendingConfirmCallback, pendingResolve]);
useEffect(() => {
if (activeToastId !== null) {
// Use capture phase to intercept Enter before terminal receives it
window.addEventListener("keydown", handleEnterKey, true);
return () => {
window.removeEventListener("keydown", handleEnterKey, true);
};
}
}, [activeToastId, handleEnterKey]);
const confirm = (opts: ConfirmationOptions, callback: () => void) => {
setOptions(opts);
setOnConfirm(() => callback);
setIsOpen(true);
};
const handleConfirm = () => {
if (onConfirm) {
onConfirm();
}
setIsOpen(false);
setOptions(null);
setOnConfirm(null);
};
const handleCancel = () => {
setIsOpen(false);
setOptions(null);
setOnConfirm(null);
};
const confirmWithToast = (
opts: ConfirmationOptions | string,
callback?: () => void,
variantOrConfirmLabel: "default" | "destructive" | string = "Confirm",
cancelLabel: string = "Cancel",
toastOptions: ToastConfirmOptions = { confirmOnEnter: false },
): Promise<boolean> => {
return new Promise((resolve) => {
const isVariant =
variantOrConfirmLabel === "default" ||
variantOrConfirmLabel === "destructive";
const confirmLabel = isVariant ? "Confirm" : variantOrConfirmLabel;
const { confirmOnEnter = false, duration = 8000 } = toastOptions;
const handleToastConfirm = () => {
if (callback) callback();
resolve(true);
setActiveToastId(null);
setPendingConfirmCallback(null);
setPendingResolve(null);
};
const handleToastCancel = () => {
resolve(false);
setActiveToastId(null);
setPendingConfirmCallback(null);
setPendingResolve(null);
};
const message = typeof opts === "string" ? opts : opts.description;
const actualConfirmLabel = typeof opts === "object" && opts.confirmText ? opts.confirmText : confirmLabel;
const actualCancelLabel = typeof opts === "object" && opts.cancelText ? opts.cancelText : cancelLabel;
const toastId = toast(message, {
duration,
action: {
label: confirmOnEnter ? `${actualConfirmLabel}` : actualConfirmLabel,
onClick: handleToastConfirm,
},
cancel: {
label: actualCancelLabel,
onClick: handleToastCancel,
},
onDismiss: () => {
setActiveToastId(null);
setPendingConfirmCallback(null);
setPendingResolve(null);
},
onAutoClose: () => {
resolve(false);
setActiveToastId(null);
setPendingConfirmCallback(null);
setPendingResolve(null);
},
} as any);
if (confirmOnEnter) {
setActiveToastId(toastId);
setPendingConfirmCallback(() => () => {
if (callback) callback();
});
setPendingResolve(() => resolve);
}
});
};
return {
isOpen,
options,
confirm,
handleConfirm,
handleCancel,
confirmWithToast,
};
}