chore: ⬆️ Upgrade to Nuxt 4
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 1s
Deploy to GitHub Pages / build (push) Failing after 1s
Deploy to GitHub Pages / deploy (push) Has been skipped
Docker / build (push) Failing after 1s
Mirror to Codeberg / Mirror (push) Failing after 1s

This commit is contained in:
Jesse Wierzbinski 2025-07-16 07:48:39 +02:00
parent 8debe97f63
commit 7f7cf20311
386 changed files with 2376 additions and 2332 deletions

View file

@ -0,0 +1,34 @@
export type ConfirmModalOptions = {
title?: string;
message?: string;
confirmText?: string;
cancelText?: string;
inputType?: "none" | "text" | "textarea" | "url";
defaultValue?: string;
};
export type ConfirmModalResult = {
confirmed: boolean;
value?: string;
};
class ConfirmModalService {
private modalRef = ref<{
open: (options: ConfirmModalOptions) => Promise<ConfirmModalResult>;
} | null>(null);
register(modal: {
open: (options: ConfirmModalOptions) => Promise<ConfirmModalResult>;
}) {
this.modalRef.value = modal;
}
confirm(options: ConfirmModalOptions): Promise<ConfirmModalResult> {
if (!this.modalRef.value) {
throw new Error("Confirmation modal not initialized");
}
return this.modalRef.value.open(options);
}
}
export const confirmModalService = new ConfirmModalService();

View file

@ -0,0 +1,70 @@
<script setup lang="ts">
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import * as m from "~~/paraglide/messages.js";
import type { ConfirmModalOptions, ConfirmModalResult } from "./composable.ts";
defineProps<{
modalOptions: ConfirmModalOptions;
}>();
const emit = defineEmits<{
confirm: [result: ConfirmModalResult];
cancel: [];
}>();
const inputValue = ref<string>("");
</script>
<template>
<Dialog>
<DialogTrigger :as-child="true">
<slot />
</DialogTrigger>
<DialogContent class="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{{ modalOptions.title }}</DialogTitle>
<DialogDescription>
{{ modalOptions.message }}
</DialogDescription>
</DialogHeader>
<div v-if="modalOptions.inputType === 'text'" class="grid gap-4 py-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="confirmInput" class="text-right">{{ m.mean_mean_badger_inspire() }}</Label>
<Input id="confirmInput" v-model="inputValue" class="col-span-3" />
</div>
</div>
<div v-else-if="modalOptions.inputType === 'textarea'" class="grid gap-4 py-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="confirmTextarea" class="text-right">{{ m.mean_mean_badger_inspire() }}</Label>
<Textarea id="confirmTextarea" v-model="inputValue" class="col-span-3" />
</div>
</div>
<DialogFooter>
<Button variant="outline" @click="() => emit('cancel')">
{{ modalOptions.cancelText }}
</Button>
<Button @click="() => emit('confirm', {
confirmed: true,
value: inputValue,
})">
{{ modalOptions.confirmText }}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template>

View file

@ -0,0 +1,107 @@
<script setup lang="ts">
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { Input, UrlInput } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import * as m from "~~/paraglide/messages.js";
import {
type ConfirmModalOptions,
type ConfirmModalResult,
confirmModalService,
} from "./composable.ts";
const isOpen = ref(false);
const modalOptions = ref<ConfirmModalOptions>({
title: m.antsy_whole_alligator_blink(),
message: "",
inputType: "none",
confirmText: m.antsy_whole_alligator_blink(),
cancelText: m.soft_bold_ant_attend(),
});
const inputValue = ref("");
const resolvePromise = ref<((result: ConfirmModalResult) => void) | null>(null);
function open(options: ConfirmModalOptions): Promise<ConfirmModalResult> {
isOpen.value = true;
isValid.value = false;
modalOptions.value = {
title: options.title || m.antsy_whole_alligator_blink(),
message: options.message,
inputType: options.inputType || "none",
confirmText: options.confirmText || m.antsy_whole_alligator_blink(),
cancelText: options.cancelText || m.soft_bold_ant_attend(),
};
inputValue.value = options.defaultValue || "";
return new Promise((resolve) => {
resolvePromise.value = resolve;
});
}
function handleConfirm() {
if (resolvePromise.value) {
resolvePromise.value({
confirmed: true,
value: inputValue.value,
});
}
isOpen.value = false;
}
function handleCancel() {
if (resolvePromise.value) {
resolvePromise.value({
confirmed: false,
});
}
isOpen.value = false;
}
confirmModalService.register({
open,
});
const isValid = ref(false);
</script>
<template>
<AlertDialog :key="String(isOpen)" :open="isOpen" @update:open="isOpen = false">
<AlertDialogContent class="sm:max-w-[425px] flex flex-col">
<AlertDialogHeader>
<AlertDialogTitle>{{ modalOptions.title }}</AlertDialogTitle>
<AlertDialogDescription v-if="modalOptions.message">
{{ modalOptions.message }}
</AlertDialogDescription>
</AlertDialogHeader>
<Input v-if="modalOptions.inputType === 'text'" v-model="inputValue" />
<UrlInput v-if="modalOptions.inputType === 'url'" v-model="inputValue" placeholder="google.com" v-model:is-valid="isValid" />
<Textarea v-else-if="modalOptions.inputType === 'textarea'" v-model="inputValue" rows="6" />
<AlertDialogFooter class="w-full">
<AlertDialogCancel :as-child="true">
<Button variant="outline" @click="handleCancel">
{{ modalOptions.cancelText }}
</Button>
</AlertDialogCancel>
<AlertDialogAction :as-child="true">
<Button @click="handleConfirm" :disabled="!isValid && modalOptions.inputType === 'url'">
{{ modalOptions.confirmText }}
</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</template>

View file

@ -0,0 +1,9 @@
<template>
<DrawerContent class="flex flex-col gap-2 px-4 mb-4 [&>:nth-child(2)]:mt-4">
<slot />
</DrawerContent>
</template>
<script lang="ts" setup>
import { DrawerContent } from "../ui/drawer";
</script>