Skip to content

Commit afed04b

Browse files
committed
feat: handle item menu
1 parent fbab0f7 commit afed04b

File tree

9 files changed

+87
-14
lines changed

9 files changed

+87
-14
lines changed

packages/nextra-editor/src/components/sidebar/sidebar-controller/add-icons.tsx renamed to packages/nextra-editor/src/components/sidebar/sidebar-controller/control-icon.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ type Props = {
1212
type: 'page' | 'folder' | 'separator'
1313
}
1414

15-
const AddIcons = ({ className, type }: Props) => {
15+
const ControlIcon = ({ className, type }: Props) => {
1616
const sidebar = useSidebar()
1717
const { bookUrlSlug, pageUrlSlug, fullUrlSlug } = useUrlSlug()
1818

@@ -126,4 +126,4 @@ const AddIcons = ({ className, type }: Props) => {
126126
)
127127
}
128128

129-
export default AddIcons
129+
export default ControlIcon
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import useOutsideClick from '@/hooks/use-outside-click'
2+
import cn from 'clsx'
3+
import { forwardRef, RefObject } from 'react'
4+
5+
type Props = {
6+
isOpen: boolean
7+
}
8+
9+
const style = {
10+
list: cn('nx-w-full nx-p-3 hover:nx-bg-gray-100'),
11+
}
12+
13+
const ControlMenu = forwardRef<HTMLDivElement, Props>(({ isOpen }, ref) => {
14+
return (
15+
<div
16+
ref={ref}
17+
className={cn(
18+
'nx-absolute nx-right-0 nx-top-0 nx-z-10 nx-py-2',
19+
'nx-bg-white',
20+
isOpen ? 'nx-visible' : 'nx-invisible',
21+
)}
22+
>
23+
<ul>
24+
<li className={style.list}>이름 바꾸기</li>
25+
<li className={style.list}>삭제</li>
26+
</ul>
27+
</div>
28+
)
29+
})
30+
31+
export default ControlMenu

packages/nextra-editor/src/components/sidebar/sidebar-controller/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import cn from 'clsx'
22

3-
import AddIcons from './add-icons'
3+
import ControlIcon from './control-icon'
44
import CollapseAllIcon from './collapse-all-icon'
55

66
type Props = {
@@ -23,9 +23,9 @@ function SidebarController({ showSidebar }: Props) {
2323
showSidebar ? 'nx-block' : 'nx-hidden',
2424
)}
2525
>
26-
<AddIcons className={style} type="page" />
27-
<AddIcons className={style} type="folder" />
28-
<AddIcons className={style} type="separator" />
26+
<ControlIcon className={style} type="page" />
27+
<ControlIcon className={style} type="folder" />
28+
<ControlIcon className={style} type="separator" />
2929
<CollapseAllIcon className={style} />
3030
</div>
3131
)

packages/nextra-editor/src/components/sidebar/sidebar-controller/sidebar-controller.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import cn from 'clsx'
22
import CollapseAllIcon from './collapse-all-icon'
3-
import AddIcons from './add-icons'
3+
import AddIcons from './control-icon'
44

55
type Props = {
66
showSidebar: boolean

packages/nextra-editor/src/components/sidebar/sortable-tree/sortable-item-wrapper.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { UniqueIdentifier } from '@dnd-kit/core'
88
import { SortableItem } from './sortable-item'
99
import { FlattenedItem } from '@/types'
1010
import { useDndTree } from '.'
11+
import { useSidebar } from '@/contexts/sidebar'
1112

1213
type Props = {
1314
className?: string
@@ -32,9 +33,10 @@ const SortableTreeMenuNotMemoized = function SortableItemWrapper({
3233
depth,
3334
}: Props) {
3435
const { overItem } = useDndTree()
36+
const { showMenuId } = useSidebar()
3537
const [previousItem, setPreviousItem] = useState<FlattenedItem | null>(null)
3638

37-
const disabled = item.name === 'index'
39+
const disabled = item.name === 'index' || showMenuId === item.id
3840
const {
3941
attributes,
4042
listeners,
@@ -96,6 +98,7 @@ const SortableTreeMenuNotMemoized = function SortableItemWrapper({
9698
...listeners,
9799
},
98100
clone,
101+
disabled,
99102
}
100103

101104
return <SortableItem item={item} {...props} />

packages/nextra-editor/src/components/sidebar/sortable-tree/sortable-item.tsx

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CSSProperties, forwardRef, useEffect } from 'react'
1+
import { CSSProperties, forwardRef, MouseEventHandler, useEffect, useState } from 'react'
22
import cn from 'clsx'
33
import { ActionType, useSidebar } from '@/contexts/sidebar'
44
import { useRouter } from 'next/router'
@@ -8,10 +8,14 @@ import { useDndTree } from '.'
88
import { classes, indentStyle } from '../style'
99
import type { SortableItemProps } from './types'
1010
import ControlInput from '../sidebar-controller/control-input'
11+
import ControlMenu from '../sidebar-controller/control-menu'
12+
import useOutsideClick from '@/hooks/use-outside-click'
1113

1214
export const SortableItem = forwardRef<HTMLDivElement, SortableItemProps>((props, ref) => {
1315
const { isDragging, overItem, setOverItem } = useDndTree()
14-
const { focusedItem, setFocusedItem } = useSidebar()
16+
const { focusedItem, setFocusedItem, showMenuId, setShowMenuId } = useSidebar()
17+
const [isEdit, setIsEdit] = useState<boolean>(false)
18+
1519
const router = useRouter()
1620
const routeOriginal = useFSRoute()
1721
const [route] = routeOriginal.split('#')
@@ -28,6 +32,7 @@ export const SortableItem = forwardRef<HTMLDivElement, SortableItemProps>((props
2832
clone,
2933
isOver,
3034
transform,
35+
disabled,
3136
} = props
3237

3338
const isControlAction = ['newPage', 'newFolder', 'newSeparator'].includes(item.type)
@@ -42,6 +47,22 @@ export const SortableItem = forwardRef<HTMLDivElement, SortableItemProps>((props
4247
setOverItem({ ...item, transform })
4348
}, [isOver, isGhost])
4449

50+
const onOpenMenu = (e: MouseEvent) => {
51+
e.preventDefault()
52+
if (item.name === 'index') return
53+
if (showMenuId === item.id) {
54+
setShowMenuId(null)
55+
} else {
56+
setShowMenuId(item.id)
57+
}
58+
}
59+
60+
const onCloseMenu = (e: MouseEvent) => {
61+
e.preventDefault()
62+
if (item.id !== showMenuId) return
63+
setShowMenuId(null)
64+
}
65+
4566
const isSeparator = item.type === 'separator'
4667

4768
const wrapperStyle: CSSProperties = {
@@ -61,8 +82,17 @@ export const SortableItem = forwardRef<HTMLDivElement, SortableItemProps>((props
6182
newSeparator: 'separator',
6283
}
6384

85+
const { ref: menuRef } = useOutsideClick<HTMLDivElement>(onCloseMenu)
86+
6487
return (
65-
<li {...handleProps} ref={wrapperRef} style={wrapperStyle}>
88+
<li
89+
{...handleProps}
90+
ref={wrapperRef}
91+
style={wrapperStyle}
92+
className={cn('nx-relative')}
93+
onContextMenu={onOpenMenu}
94+
>
95+
<ControlMenu isOpen={showMenuId === item.id} ref={menuRef} />
6696
<div
6797
ref={ref}
6898
{...handleProps}
@@ -79,6 +109,8 @@ export const SortableItem = forwardRef<HTMLDivElement, SortableItemProps>((props
79109
onClick={() => {
80110
if (isGhost) return
81111
if (isSeparator) return
112+
if (isEdit) return
113+
if (isControlAction) return
82114

83115
if (!item.collapsed) {
84116
onCollapse(item.id)
@@ -87,7 +119,6 @@ export const SortableItem = forwardRef<HTMLDivElement, SortableItemProps>((props
87119
}
88120

89121
setFocusedItem(item)
90-
if (isControlAction) return
91122
router.push(item.route, item.route, { shallow: true })
92123
}}
93124
>

packages/nextra-editor/src/components/sidebar/sortable-tree/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export type SortableItemWrapperProps = {
2626
onCollapse: (id: UniqueIdentifier) => void
2727
handleProps: any
2828
clone: boolean
29+
disabled: boolean
2930
}
3031

3132
export type SortableItemProps = {

packages/nextra-editor/src/contexts/sidebar.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ type Sidebar = {
2828
setFocusedItem: (value: SortableItem | null) => void
2929
collapsedTree: Set<string>
3030
setCollapsedTree: (id: string, setter: (value: boolean) => boolean) => void
31+
showMenuId: string | null
32+
setShowMenuId: (value: string | null) => void
3133
}
3234

3335
let collapsedTree = new Set<string>()
@@ -60,6 +62,8 @@ const SidebarContext = createContext<Sidebar>({
6062
setFocusedItem: () => {},
6163
collapsedTree,
6264
setCollapsedTree,
65+
showMenuId: null,
66+
setShowMenuId: () => {},
6367
})
6468

6569
export function useSidebar() {
@@ -71,6 +75,8 @@ export const SidebarProvider = ({ children }: { children: ReactNode }): ReactEle
7175
const [isFolding, setFolding] = useState(false)
7276
const [actionComplete, setActionComplete] = useState(false)
7377
const [actionActive, setActionActive] = useState(false)
78+
const [showMenuId, setShowMenuId] = useState<string | null>(null)
79+
7480
const [focusedItem, setFocusedItem] = useState<null | SortableItem>(null)
7581
const [actionType, setActionType] = useState<ActionType>('')
7682
const [actionInfo, setActionInfo] = useState<ActionInfo>({
@@ -94,7 +100,6 @@ export const SidebarProvider = ({ children }: { children: ReactNode }): ReactEle
94100
if (value) {
95101
collapsedTree = new Set<string>()
96102
}
97-
98103
setFolding(value)
99104
}
100105

@@ -116,6 +121,8 @@ export const SidebarProvider = ({ children }: { children: ReactNode }): ReactEle
116121
setFocusedItem,
117122
collapsedTree,
118123
setCollapsedTree,
124+
showMenuId,
125+
setShowMenuId,
119126
}
120127

121128
return <SidebarContext.Provider value={value}>{children}</SidebarContext.Provider>

packages/nextra-editor/style.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)