generated from Klectr/KTemplate
feat/context-menu #10
@ -11,6 +11,7 @@
|
|||||||
"commit": "cz"
|
"commit": "cz"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@kaioken-core/hooks": "^0.0.8",
|
||||||
"@tauri-apps/api": "^1",
|
"@tauri-apps/api": "^1",
|
||||||
"caniuse-lite": "^1.0.30001667",
|
"caniuse-lite": "^1.0.30001667",
|
||||||
"kaioken": "^0.32.1",
|
"kaioken": "^0.32.1",
|
||||||
|
@ -9,6 +9,7 @@ export function App() {
|
|||||||
return (
|
return (
|
||||||
<ToastContextProvider>
|
<ToastContextProvider>
|
||||||
<MemoInfyCanvas />
|
<MemoInfyCanvas />
|
||||||
|
<div id="context-menu-portal" />
|
||||||
</ToastContextProvider>
|
</ToastContextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
44
src/components/ContextMenuPortal.tsx
Normal file
44
src/components/ContextMenuPortal.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { useClickOutside, 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 }
|
||||||
|
}, [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 images, { ImageCardType } from "../signals/images"
|
||||||
import { updateLocalStorage } from "../utils/localStorage"
|
import { updateLocalStorage } from "../utils/localStorage"
|
||||||
import { useThemeDetector } from "../utils/useThemeDetector"
|
import { useThemeDetector } from "../utils/useThemeDetector"
|
||||||
|
import { ContextMenuPortal } from "./ContextMenuPortal"
|
||||||
|
|
||||||
namespace ImageCard {
|
namespace ImageCard {
|
||||||
export interface ImageCardProps {
|
export interface ImageCardProps {
|
||||||
@ -22,6 +23,7 @@ export function ImageCard({ key: itemKey, data: item }: ImageCard.ImageCardProps
|
|||||||
const offsetY = useRef(0)
|
const offsetY = useRef(0)
|
||||||
const initialResizeX = useRef(0)
|
const initialResizeX = useRef(0)
|
||||||
const initialResizeY = useRef(0)
|
const initialResizeY = useRef(0)
|
||||||
|
const openContextMenu = signal(false)
|
||||||
|
|
||||||
function debounceLSUpdate(time?: number) {
|
function debounceLSUpdate(time?: number) {
|
||||||
debounce(() => {
|
debounce(() => {
|
||||||
@ -93,8 +95,19 @@ export function ImageCard({ key: itemKey, data: item }: ImageCard.ImageCardProps
|
|||||||
ImagesSignal.default.images.notify()
|
ImagesSignal.default.images.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _handleMouseClick(e: MouseEvent) {
|
||||||
|
e.preventDefault()
|
||||||
|
openContextMenu.value = !openContextMenu.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function _handleContextClose() {
|
||||||
|
openContextMenu.value = false
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div
|
<div
|
||||||
|
oncontextmenu={_handleMouseClick}
|
||||||
onmousedown={_handleMouseDown}
|
onmousedown={_handleMouseDown}
|
||||||
className="select-none transition flex flex-col justify-stretch shadow-md rounded border border-[#1c1c1c] absolute"
|
className="select-none transition flex flex-col justify-stretch shadow-md rounded border border-[#1c1c1c] absolute"
|
||||||
style={{
|
style={{
|
||||||
@ -117,6 +130,31 @@ export function ImageCard({ key: itemKey, data: item }: ImageCard.ImageCardProps
|
|||||||
<ExpandIcon cb={_handleResizeMouseDown} />
|
<ExpandIcon cb={_handleResizeMouseDown} />
|
||||||
</div >
|
</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 { Divider } from "./Divider"
|
||||||
import { ExportIcon } from "./icons/ExportIcon"
|
import { ExportIcon } from "./icons/ExportIcon"
|
||||||
import { createFileAndExport } from "../utils/createFileAndExport"
|
import { createFileAndExport } from "../utils/createFileAndExport"
|
||||||
|
import { ContextMenuPortal } from "./ContextMenuPortal"
|
||||||
|
|
||||||
namespace NoteCard {
|
namespace NoteCard {
|
||||||
export interface NoteCardProps {
|
export interface NoteCardProps {
|
||||||
@ -26,6 +27,7 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
|
|||||||
const offsetY = useRef(0)
|
const offsetY = useRef(0)
|
||||||
const initialResizeX = useRef(0)
|
const initialResizeX = useRef(0)
|
||||||
const initialResizeY = useRef(0)
|
const initialResizeY = useRef(0)
|
||||||
|
const openContextMenu = signal(false)
|
||||||
|
|
||||||
const { debounce } = useDebounce()
|
const { debounce } = useDebounce()
|
||||||
|
|
||||||
@ -113,6 +115,15 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
|
|||||||
createFileAndExport("Note", item.contents, "text/markdown")
|
createFileAndExport("Note", item.contents, "text/markdown")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _handleMouseClick(e: MouseEvent) {
|
||||||
|
e.preventDefault()
|
||||||
|
openContextMenu.value = !openContextMenu.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function _handleContextClose() {
|
||||||
|
openContextMenu.value = false
|
||||||
|
}
|
||||||
|
|
||||||
const cardPositionStyle = {
|
const cardPositionStyle = {
|
||||||
zIndex: `${focusedItem.value == itemKey ? LayerEnum.CARD_ELEVATED : LayerEnum.CARD}`,
|
zIndex: `${focusedItem.value == itemKey ? LayerEnum.CARD_ELEVATED : LayerEnum.CARD}`,
|
||||||
width: `${item.dimensions.w}px`,
|
width: `${item.dimensions.w}px`,
|
||||||
@ -127,6 +138,7 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
oncontextmenu={_handleMouseClick}
|
||||||
onmousedown={_handleFocusCard}
|
onmousedown={_handleFocusCard}
|
||||||
style={cardPositionStyle}
|
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"
|
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} />
|
<MarkDownEditor initial={item.contents} onChange={_handleMdChange} />
|
||||||
<ExpandIcon cb={_handleResizeMouseDown} />
|
<ExpandIcon cb={_handleResizeMouseDown} />
|
||||||
</div>
|
</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 >
|
</div >
|
||||||
|
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user