From 2b0b640ff2058f1ea52b5b1d2799116bbcfb0918 Mon Sep 17 00:00:00 2001 From: fredzia Date: Wed, 29 Oct 2025 11:25:50 -0300 Subject: [PATCH 1/3] feat: add useEscapeClose hook for handling escape key events --- packages/shira-ui/src/hooks/index.ts | 1 + .../shira-ui/src/hooks/useEscapeClose.tsx | 29 +++++++++++++++++++ packages/shira-ui/src/index.ts | 1 + 3 files changed, 31 insertions(+) create mode 100644 packages/shira-ui/src/hooks/index.ts create mode 100644 packages/shira-ui/src/hooks/useEscapeClose.tsx diff --git a/packages/shira-ui/src/hooks/index.ts b/packages/shira-ui/src/hooks/index.ts new file mode 100644 index 00000000..38beddf8 --- /dev/null +++ b/packages/shira-ui/src/hooks/index.ts @@ -0,0 +1 @@ +export * from './useEscapeClose'; \ No newline at end of file diff --git a/packages/shira-ui/src/hooks/useEscapeClose.tsx b/packages/shira-ui/src/hooks/useEscapeClose.tsx new file mode 100644 index 00000000..d89c3fdc --- /dev/null +++ b/packages/shira-ui/src/hooks/useEscapeClose.tsx @@ -0,0 +1,29 @@ +import { useEffect } from "react"; + +type UseEscapeCloseOptions = { + when: boolean; + onClose: () => void; + target?: Document | HTMLElement | Window | null; + stopPropagation?: boolean; +}; + +export function useEscapeClose({ + when, + onClose, + target, + stopPropagation = true, +}: UseEscapeCloseOptions) { + useEffect(() => { + if (!when || !target) return; + + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape" || e.key === "Esc") { + if (stopPropagation) e.stopPropagation(); + onClose(); + } + }; + + target.addEventListener("keydown", onKeyDown); + return () => target.removeEventListener("keydown", onKeyDown); + }, [when, onClose, target, stopPropagation]); +} diff --git a/packages/shira-ui/src/index.ts b/packages/shira-ui/src/index.ts index a850ceb9..06713ebf 100644 --- a/packages/shira-ui/src/index.ts +++ b/packages/shira-ui/src/index.ts @@ -1,4 +1,5 @@ // shira-ui/src/index.ts export * from './components' export * from './theme' +export * from './hooks'; export { default as styled, createGlobalStyle } from 'styled-components'; \ No newline at end of file From d9e0d5eb617d2524dbbcb627b93b73db82c7a47d Mon Sep 17 00:00:00 2001 From: fredzia Date: Wed, 29 Oct 2025 11:26:29 -0300 Subject: [PATCH 2/3] feat: implement escape key handling for modal close functionality --- .../modals/CreateQuizModal/index.tsx | 24 ++++++++++--------- .../shira-ui/src/components/Modal/Modal.tsx | 21 ++++++++++------ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/apps/spaces/src/components/modals/CreateQuizModal/index.tsx b/apps/spaces/src/components/modals/CreateQuizModal/index.tsx index 2b91b3a6..0668757e 100644 --- a/apps/spaces/src/components/modals/CreateQuizModal/index.tsx +++ b/apps/spaces/src/components/modals/CreateQuizModal/index.tsx @@ -16,6 +16,11 @@ export const CreateQuizModal: FunctionComponent = ({ const [title, handleTitle] = useState('') + const handleClose = () => { + setIsModalOpen(false); + handleTitle(""); + }; + return ( = ({ onPrimaryClick={() => { onCreate(title) setIsModalOpen(false); - handleTitle('') - }} - onSecondaryClick={() => { - setIsModalOpen(false) - handleTitle('') }} + onSecondaryClick={handleClose} + onClose={handleClose} > - - handleTitle(e.target.value)} - /> + + handleTitle(e.target.value)} + /> ) diff --git a/packages/shira-ui/src/components/Modal/Modal.tsx b/packages/shira-ui/src/components/Modal/Modal.tsx index 75ea6da5..582103d3 100644 --- a/packages/shira-ui/src/components/Modal/Modal.tsx +++ b/packages/shira-ui/src/components/Modal/Modal.tsx @@ -2,6 +2,7 @@ import React, { useEffect } from 'react'; import styled from 'styled-components'; import { Button } from '../Button'; import { SubHeading2 } from '../Typography'; +import { useEscapeClose } from '../../hooks/useEscapeClose'; export interface ModalProps { isOpen: boolean; @@ -17,6 +18,8 @@ export interface ModalProps { onLeftClick?: () => void leftButtonText?: string className?: string; + + onClose?: () => void; } export enum ModalType { @@ -29,7 +32,6 @@ const modalTypeColors = { 'primary': '#849D29' } - export const Modal: React.FC = ({ isOpen, title, @@ -43,7 +45,8 @@ export const Modal: React.FC = ({ onLeftClick, leftButtonText, className, - type = 'primary' + type = 'primary', + onClose }) => { useEffect(() => { @@ -58,6 +61,11 @@ export const Modal: React.FC = ({ }; }, [isOpen]); + useEscapeClose({ + when: isOpen, + onClose: onClose ?? onSecondaryClick ?? (() => { }), + }); + if (!isOpen) return null; return ( @@ -65,7 +73,7 @@ export const Modal: React.FC = ({
- { titleIcon } + {titleIcon} {title}
@@ -75,8 +83,8 @@ export const Modal: React.FC = ({