diff --git a/app/components/Common/Buttons.vue b/app/components/Common/Buttons.vue new file mode 100644 index 0000000..abf7c7d --- /dev/null +++ b/app/components/Common/Buttons.vue @@ -0,0 +1,71 @@ + diff --git a/app/components/Common/ConfirmationModal.vue b/app/components/Common/ConfirmationModal.vue new file mode 100644 index 0000000..c57b2a9 --- /dev/null +++ b/app/components/Common/ConfirmationModal.vue @@ -0,0 +1,57 @@ + + + diff --git a/app/composables/useButtonNavigation.ts b/app/composables/useButtonNavigation.ts index ddd85aa..4413e5e 100644 --- a/app/composables/useButtonNavigation.ts +++ b/app/composables/useButtonNavigation.ts @@ -20,12 +20,16 @@ export const useButtonNavigation = >({ } >; }) => { + const { state: modalState } = useConfirmationModal(); + const selectedButton = ref(initialButton); const selectorPosition = computed(() => buttons[selectedButton.value]!); const nextButton = ref(); useScreenClick((x: number, y: number) => { + if (modalState.value.isOpen) return; + for (const [buttonName, config] of Object.entries(buttons) as [ keyof T, ButtonConfig, @@ -52,6 +56,8 @@ export const useButtonNavigation = >({ }); useKeyDown((key) => { + if (modalState.value.isOpen) return; + const currentButton = selectedButton.value as keyof T; const currentNav = navigation[currentButton]; diff --git a/app/composables/useConfirmationModal.ts b/app/composables/useConfirmationModal.ts new file mode 100644 index 0000000..32adb3e --- /dev/null +++ b/app/composables/useConfirmationModal.ts @@ -0,0 +1,93 @@ +import gsap from "gsap"; + +const MODAL_MAX_Y_OFFSET = 106; +const BUTTONS_MAX_Y_OFFSET = 20; +const BUTTONS_TIME = 0.1; +const MODAL_TIME = 0.225; + +const state = useState("confirmationModal", () => + reactive({ + isOpen: false, + text: "", + onConfirm: null as (() => void) | null, + offsetY: MODAL_MAX_Y_OFFSET, + buttonsYOffset: 0, + modalButtonsYOffset: BUTTONS_MAX_Y_OFFSET, + isVisible: false, + isClosing: false, + }), +); + +const open = (text: string, onConfirm?: (() => void) | null) => { + gsap.killTweensOf(state.value); + state.value.text = text; + state.value.onConfirm = onConfirm || null; + state.value.isVisible = true; + state.value.isClosing = false; + state.value.isOpen = true; + + gsap + .timeline() + // standard buttons down + .fromTo( + state.value, + { buttonsYOffset: 0 }, + { + buttonsYOffset: BUTTONS_MAX_Y_OFFSET, + duration: BUTTONS_TIME, + ease: "none", + }, + ) + // modal up + .fromTo( + state.value, + { offsetY: MODAL_MAX_Y_OFFSET }, + { offsetY: 0, duration: MODAL_TIME, ease: "none" }, + ) + // modal buttons up + .fromTo( + state.value, + { modalButtonsYOffset: BUTTONS_MAX_Y_OFFSET }, + { modalButtonsYOffset: 0, duration: BUTTONS_TIME, ease: "none" }, + ); +}; + +const close = () => { + if (!state.value.isVisible || state.value.isClosing) return; + + state.value.isClosing = true; + + gsap + .timeline() + // modal buttons down + .to(state.value, { + modalButtonsYOffset: BUTTONS_MAX_Y_OFFSET, + duration: BUTTONS_TIME, + ease: "none", + }) + // modal down + .to(state.value, { + offsetY: MODAL_MAX_Y_OFFSET, + duration: MODAL_TIME, + ease: "none", + }) + // standard buttons up + .to(state.value, { + buttonsYOffset: 0, + duration: BUTTONS_TIME, + ease: "none", + }) + .call(() => { + state.value.isVisible = false; + state.value.isClosing = false; + state.value.isOpen = false; + state.value.text = ""; + state.value.onConfirm = null; + }); +}; + +export const useConfirmationModal = () => ({ + open, + close, + state: readonly(state), +}); diff --git a/app/utils/async.ts b/app/utils/async.ts new file mode 100644 index 0000000..7ce4b40 --- /dev/null +++ b/app/utils/async.ts @@ -0,0 +1,2 @@ +export const sleep = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/public/images/common/A.webp b/public/images/common/A.webp new file mode 100644 index 0000000..2b1889c Binary files /dev/null and b/public/images/common/A.webp differ diff --git a/public/images/common/B.webp b/public/images/common/B.webp new file mode 100644 index 0000000..e09d99f Binary files /dev/null and b/public/images/common/B.webp differ diff --git a/public/images/common/button.webp b/public/images/common/button.webp new file mode 100644 index 0000000..85e6f03 Binary files /dev/null and b/public/images/common/button.webp differ diff --git a/public/images/common/confirmation-modal.webp b/public/images/common/confirmation-modal.webp new file mode 100644 index 0000000..deeed97 Binary files /dev/null and b/public/images/common/confirmation-modal.webp differ