generated from Klectr/KTemplate
Merge pull request 'feat/context-menu' (#10) from feat/context-menu into main
Reviewed-on: https://git.tristonarmstrong.com/Klectr/KSlab/pulls/10
This commit is contained in:
commit
98fc00bac1
@ -11,6 +11,7 @@
|
||||
"commit": "cz"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kaioken-core/hooks": "^0.0.8",
|
||||
"@tauri-apps/api": "^1",
|
||||
"caniuse-lite": "^1.0.30001667",
|
||||
"kaioken": "^0.32.1",
|
||||
|
@ -9,6 +9,7 @@ export function App() {
|
||||
return (
|
||||
<ToastContextProvider>
|
||||
<MemoInfyCanvas />
|
||||
<div id="context-menu-portal" />
|
||||
</ToastContextProvider>
|
||||
)
|
||||
}
|
||||
|
52
src/components/ContextMenuPortal.tsx
Normal file
52
src/components/ContextMenuPortal.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import { useClickOutside, useKeyStroke, useMouse } from "@kaioken-core/hooks";
|
||||
import { Portal, signal, useEffect, useRef } from "kaioken";
|
||||
|
||||
namespace ContextMenuPortal {
|
||||
export interface Props {
|
||||
children: JSX.Children
|
||||
open: boolean
|
||||
closeAction: (() => void) | null | undefined
|
||||
}
|
||||
}
|
||||
export function ContextMenuPortal({ children, open, closeAction }: ContextMenuPortal.Props) {
|
||||
const { mouse } = useMouse()
|
||||
const pos = signal({ x: 0, y: 0 })
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
useClickOutside(ref, () => {
|
||||
closeAction?.()
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
pos.value = { x: mouse.x, y: mouse.y }
|
||||
function _handleEscapeKey(e: KeyboardEvent) {
|
||||
if (e.key !== "Escape") return
|
||||
closeAction?.()
|
||||
}
|
||||
document.addEventListener("keydown", _handleEscapeKey)
|
||||
return () => {
|
||||
document.removeEventListener("keydown", _handleEscapeKey)
|
||||
}
|
||||
}, [open])
|
||||
|
||||
if (!open) return null
|
||||
|
||||
return (
|
||||
<Portal
|
||||
container={() => document.querySelector("#context-menu-portal")!}
|
||||
>
|
||||
<div
|
||||
ref={ref}
|
||||
className="fixed z-10 select-none shadow-md rounded min-w-[150px]"
|
||||
style={{
|
||||
left: `${pos.value.x}px`,
|
||||
top: `${pos.value.y}px`,
|
||||
zIndex: `1000`
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</Portal>
|
||||
)
|
||||
}
|
@ -5,6 +5,7 @@ import { LayerEnum } from "../utils/enums"
|
||||
import images, { ImageCardType } from "../signals/images"
|
||||
import { updateLocalStorage } from "../utils/localStorage"
|
||||
import { useThemeDetector } from "../utils/useThemeDetector"
|
||||
import { ContextMenuPortal } from "./ContextMenuPortal"
|
||||
|
||||
namespace ImageCard {
|
||||
export interface ImageCardProps {
|
||||
@ -22,6 +23,7 @@ export function ImageCard({ key: itemKey, data: item }: ImageCard.ImageCardProps
|
||||
const offsetY = useRef(0)
|
||||
const initialResizeX = useRef(0)
|
||||
const initialResizeY = useRef(0)
|
||||
const openContextMenu = signal(false)
|
||||
|
||||
function debounceLSUpdate(time?: number) {
|
||||
debounce(() => {
|
||||
@ -93,8 +95,19 @@ export function ImageCard({ key: itemKey, data: item }: ImageCard.ImageCardProps
|
||||
ImagesSignal.default.images.notify()
|
||||
}
|
||||
|
||||
function _handleMouseClick(e: MouseEvent) {
|
||||
e.preventDefault()
|
||||
openContextMenu.value = !openContextMenu.value
|
||||
}
|
||||
|
||||
function _handleContextClose() {
|
||||
openContextMenu.value = false
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
oncontextmenu={_handleMouseClick}
|
||||
onmousedown={_handleMouseDown}
|
||||
className="select-none transition flex flex-col justify-stretch shadow-md rounded border border-[#1c1c1c] absolute"
|
||||
style={{
|
||||
@ -117,6 +130,31 @@ export function ImageCard({ key: itemKey, data: item }: ImageCard.ImageCardProps
|
||||
<ExpandIcon cb={_handleResizeMouseDown} />
|
||||
</div >
|
||||
|
||||
<ContextMenuPortal open={openContextMenu.value} closeAction={_handleContextClose}>
|
||||
<div className="bg-[#3c3c3c] flex flex-col rounded">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-md dark:text-[#999] text-black px-2 py-1">
|
||||
{item.title}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="border dark:border-[#2c2c2c] border-[#ddd] m-0 p-0" />
|
||||
|
||||
<div>
|
||||
<ul>
|
||||
<li className="flex items-center gap-2 hover:bg-[#fff] dark:hover:bg-[#1a1a1a] cursor-pointer px-2 py-1">
|
||||
<button onclick={_handleClose} className="text-md dark:text-[#999] text-black">
|
||||
Delete
|
||||
</button>
|
||||
</li>
|
||||
<li className="flex items-center gap-2 hover:bg-[#fff] dark:hover:bg-[#1a1a1a] cursor-pointer px-2 py-1">
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenuPortal>
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import { ChangeEvent } from "tiny-markdown-editor"
|
||||
import { Divider } from "./Divider"
|
||||
import { ExportIcon } from "./icons/ExportIcon"
|
||||
import { createFileAndExport } from "../utils/createFileAndExport"
|
||||
import { ContextMenuPortal } from "./ContextMenuPortal"
|
||||
|
||||
namespace NoteCard {
|
||||
export interface NoteCardProps {
|
||||
@ -26,6 +27,7 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
|
||||
const offsetY = useRef(0)
|
||||
const initialResizeX = useRef(0)
|
||||
const initialResizeY = useRef(0)
|
||||
const openContextMenu = signal(false)
|
||||
|
||||
const { debounce } = useDebounce()
|
||||
|
||||
@ -113,6 +115,15 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
|
||||
createFileAndExport("Note", item.contents, "text/markdown")
|
||||
}
|
||||
|
||||
function _handleMouseClick(e: MouseEvent) {
|
||||
e.preventDefault()
|
||||
openContextMenu.value = !openContextMenu.value
|
||||
}
|
||||
|
||||
function _handleContextClose() {
|
||||
openContextMenu.value = false
|
||||
}
|
||||
|
||||
const cardPositionStyle = {
|
||||
zIndex: `${focusedItem.value == itemKey ? LayerEnum.CARD_ELEVATED : LayerEnum.CARD}`,
|
||||
width: `${item.dimensions.w}px`,
|
||||
@ -127,6 +138,7 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
|
||||
|
||||
return (
|
||||
<div
|
||||
oncontextmenu={_handleMouseClick}
|
||||
onmousedown={_handleFocusCard}
|
||||
style={cardPositionStyle}
|
||||
className="overflow-hidden text-[#333] dark:bg-[#1a1a1a] dark:border-[#1c1c1c] bg-[#efeff0] select-none transition flex flex-col justify-stretch shadow-md rounded border border-[#ddd] absolute"
|
||||
@ -158,6 +170,33 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
|
||||
<MarkDownEditor initial={item.contents} onChange={_handleMdChange} />
|
||||
<ExpandIcon cb={_handleResizeMouseDown} />
|
||||
</div>
|
||||
|
||||
<ContextMenuPortal open={openContextMenu.value} closeAction={_handleContextClose}>
|
||||
<div className="bg-[#3c3c3c] flex flex-col rounded">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-md dark:text-[#999] text-black px-2 py-1">
|
||||
{item.title}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="border dark:border-[#2c2c2c] border-[#ddd] m-0 p-0" />
|
||||
|
||||
<div>
|
||||
<ul>
|
||||
<li className="flex items-center gap-2 hover:bg-[#fff] dark:hover:bg-[#1a1a1a] cursor-pointer px-2 py-1">
|
||||
<button onclick={_handleClose} className="text-md dark:text-[#999] text-black">
|
||||
Delete
|
||||
</button>
|
||||
</li>
|
||||
<li className="flex items-center gap-2 hover:bg-[#fff] dark:hover:bg-[#1a1a1a] cursor-pointer px-2 py-1">
|
||||
<button onclick={_handleExportClick} className="text-md dark:text-[#999] text-black">
|
||||
export
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenuPortal>
|
||||
</div >
|
||||
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user