diff --git a/app.vue b/app.vue index 3272e9c..36e1647 100644 --- a/app.vue +++ b/app.vue @@ -10,6 +10,7 @@ + @@ -19,6 +20,7 @@ import { convert } from "html-to-text"; import "iconify-icon"; import NotificationsRenderer from "./components/notifications/notifications-renderer.vue"; import { SettingIds } from "./settings"; +import ConfirmationModal from "./components/modals/confirmation.vue"; // Use SSR-safe IDs for Headless UI provideHeadlessUseId(() => useId()); diff --git a/components/modals/composable.ts b/components/modals/composable.ts new file mode 100644 index 0000000..d5682fe --- /dev/null +++ b/components/modals/composable.ts @@ -0,0 +1,25 @@ +import { + confirmModalService, + confirmModalWithInputService, +} from "./service.ts"; +import type { ConfirmModalOptions, ConfirmModalResult } from "./types.ts"; + +export function useConfirmModal() { + const confirm = ( + options: ConfirmModalOptions, + ): Promise => { + return confirmModalService.confirm(options); + }; + + const confirmWithInput = ( + options: ConfirmModalOptions, + placeholder?: string, + ): Promise => { + return confirmModalWithInputService.confirm(options, placeholder); + }; + + return { + confirm, + confirmWithInput, + }; +} diff --git a/components/modals/confirmation.vue b/components/modals/confirmation.vue new file mode 100644 index 0000000..63bb461 --- /dev/null +++ b/components/modals/confirmation.vue @@ -0,0 +1,124 @@ + + + + + + + + + + + + + {{ modalOptions.title || 'Confirm Action' }} + + + + {{ modalOptions.message }} + + + + + + + + + {{ modalOptions.cancelText || 'Cancel' }} + + + {{ modalOptions.confirmText || 'Confirm' }} + + + + + + + + + + + \ No newline at end of file diff --git a/components/modals/service.ts b/components/modals/service.ts new file mode 100644 index 0000000..06025f7 --- /dev/null +++ b/components/modals/service.ts @@ -0,0 +1,52 @@ +import { ref } from "vue"; +import type { ConfirmModalOptions, ConfirmModalResult } from "./types.ts"; + +class ConfirmModalService { + private modalRef = ref<{ + open: (options: ConfirmModalOptions) => Promise; + } | null>(null); + + register(modal: { + open: (options: ConfirmModalOptions) => Promise; + }) { + this.modalRef.value = modal; + } + + confirm(options: ConfirmModalOptions): Promise { + if (!this.modalRef.value) { + throw new Error("Confirmation modal not initialized"); + } + return this.modalRef.value.open(options); + } +} + +class ConfirmModalWithInputService { + private modalRef = ref<{ + open: ( + options: ConfirmModalOptions, + placeholder?: string, + ) => Promise; + } | null>(null); + + register(modal: { + open: ( + options: ConfirmModalOptions, + placeholder?: string, + ) => Promise; + }) { + this.modalRef.value = modal; + } + + confirm( + options: ConfirmModalOptions, + placeholder?: string, + ): Promise { + if (!this.modalRef.value) { + throw new Error("Confirmation modal not initialized"); + } + return this.modalRef.value.open(options, placeholder); + } +} + +export const confirmModalService = new ConfirmModalService(); +export const confirmModalWithInputService = new ConfirmModalWithInputService(); diff --git a/components/modals/types.ts b/components/modals/types.ts new file mode 100644 index 0000000..34f40e6 --- /dev/null +++ b/components/modals/types.ts @@ -0,0 +1,11 @@ +export interface ConfirmModalOptions { + title?: string; + message: string; + confirmText?: string; + cancelText?: string; +} + +export interface ConfirmModalResult { + confirmed: boolean; + value?: string; +} diff --git a/components/sidebars/account-picker.vue b/components/sidebars/account-picker.vue index 1d5b51b..2401b09 100644 --- a/components/sidebars/account-picker.vue +++ b/components/sidebars/account-picker.vue @@ -61,14 +61,14 @@ - + Settings - + Add new account diff --git a/components/sidebars/navigation.vue b/components/sidebars/navigation.vue index 04a09e3..b4abf30 100644 --- a/components/sidebars/navigation.vue +++ b/components/sidebars/navigation.vue @@ -13,7 +13,7 @@ Timelines - + {{ timeline.name }} @@ -28,7 +28,7 @@ loadingAuth = false)" @sign-out="id => signOut(id).finally(() => loadingAuth = false)" /> - + Register @@ -102,10 +102,10 @@ import ButtonBase from "~/packages/ui/components/buttons/button.vue"; import Icon from "~/packages/ui/components/icons/icon.vue"; import ButtonDropdown from "../buttons/button-dropdown.vue"; import ButtonMobileNavbar from "../buttons/button-mobile-navbar.vue"; -import Button from "../composer/button.vue"; import AdaptiveDropdown from "../dropdowns/AdaptiveDropdown.vue"; import AccountPicker from "./account-picker.vue"; const { $pwa } = useNuxtApp(); + const timelines = ref([ { href: "/home", diff --git a/packages/ui/components/buttons/button.vue b/packages/ui/components/buttons/button.vue index eb17efa..14fe205 100644 --- a/packages/ui/components/buttons/button.vue +++ b/packages/ui/components/buttons/button.vue @@ -14,14 +14,15 @@ import type { ButtonHTMLAttributes } from "vue"; const themes = { primary: - "[--btn-border:theme(colors.primary.950/90%)] [--btn-bg:theme(colors.primary.600)] [--btn-hover-overlay:theme(colors.white/30%)] [--btn-icon:theme(colors.primary.200)] active:[--btn-icon:theme(colors.primary.100)] hover:[--btn-icon:theme(colors.primary.100)] after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)] border border-white/5", + "[--btn-bg:theme(colors.primary.600)] [--btn-hover-overlay:theme(colors.white/30%)] [--btn-icon:theme(colors.primary.200)] active:[--btn-icon:theme(colors.primary.100)] hover:[--btn-icon:theme(colors.primary.100)] after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)] border border-white/5", secondary: - "[--btn-border:theme(colors.zinc.950/90%)] [--btn-bg:theme(colors.zinc.800)] [--btn-hover-overlay:theme(colors.white/5%)] [--btn-icon:theme(colors.zinc.400)] active:[--btn-icon:theme(colors.zinc.300)] hover:[--btn-icon:theme(colors.zinc.300)] after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)] border border-white/5", + "[--btn-bg:theme(colors.zinc.800)] [--btn-hover-overlay:theme(colors.white/5%)] [--btn-icon:theme(colors.zinc.400)] active:[--btn-icon:theme(colors.zinc.300)] hover:[--btn-icon:theme(colors.zinc.300)] after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)] border border-white/5", // Gradient: bg-gradient-to-tr from-primary-300 via-purple-300 to-indigo-400 gradient: - "bg-[image:--btn-bg] before:bg-[image:--btn-bg] [--btn-border:theme(colors.primary.950/90%)] [--btn-bg:linear-gradient(to_right,theme(colors.primary.300),theme(colors.purple.300),theme(colors.indigo.400))] [--btn-hover-overlay:theme(colors.white/10%)] [--btn-icon:theme(colors.gray.100)] active:[--btn-icon:theme(colors.gray.50)] hover:[--btn-icon:theme(colors.gray.50)] after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)] [&>[data-spinner]]:bg-[image:--btn-bg]", + "bg-[image:--btn-bg] before:bg-[image:--btn-bg] [--btn-bg:linear-gradient(to_right,theme(colors.primary.300),theme(colors.purple.300),theme(colors.indigo.400))] [--btn-hover-overlay:theme(colors.white/10%)] [--btn-icon:theme(colors.gray.100)] active:[--btn-icon:theme(colors.gray.50)] hover:[--btn-icon:theme(colors.gray.50)] after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)] [&>[data-spinner]]:bg-[image:--btn-bg]", outline: - "[--btn-border:theme(colors.zinc.950/90%)] [--btn-bg:transparent] [--btn-hover-overlay:theme(colors.white/5%)] [--btn-icon:theme(colors.zinc.200)] active:[--btn-icon:theme(colors.zinc.300)] hover:[--btn-icon:theme(colors.zinc.300)] hover:ring-1 ring-white/5", + "[--btn-bg:transparent] [--btn-hover-overlay:theme(colors.white/5%)] [--btn-icon:theme(colors.zinc.200)] active:[--btn-icon:theme(colors.zinc.300)] hover:[--btn-icon:theme(colors.zinc.300)] ring-1 ring-white/5", + ghost: "[--btn-bg:transparent] [--btn-hover-overlay:theme(colors.white/5%)] [--btn-icon:theme(colors.zinc.200)] active:[--btn-icon:theme(colors.zinc.300)] hover:[--btn-icon:theme(colors.zinc.300)] hover:ring-1 ring-white/5", }; interface Props extends /* @vue-ignore */ ButtonHTMLAttributes {} diff --git a/tsconfig.json b/tsconfig.json index 5c81a27..aff85fe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "extends": "./.nuxt/tsconfig.json", "compilerOptions": { "target": "esnext", - "module": "esnext" + "module": "esnext", + "allowImportingTsExtensions": true } }